diff options
35 files changed, 892 insertions, 598 deletions
diff --git a/api/current.txt b/api/current.txt index f2200af..0154dd2 100644 --- a/api/current.txt +++ b/api/current.txt @@ -8666,14 +8666,14 @@ package android.content.pm { field public static final java.lang.String EXTRA_STATUS = "android.content.pm.extra.STATUS"; field public static final java.lang.String EXTRA_STATUS_MESSAGE = "android.content.pm.extra.STATUS_MESSAGE"; field public static final int STATUS_FAILURE = 1; // 0x1 - field public static final int STATUS_FAILURE_ABORTED = 2; // 0x2 - field public static final int STATUS_FAILURE_BLOCKED = 1; // 0x1 - field public static final int STATUS_FAILURE_CONFLICT = 4; // 0x4 - field public static final int STATUS_FAILURE_INCOMPATIBLE = 6; // 0x6 - field public static final int STATUS_FAILURE_INVALID = 3; // 0x3 - field public static final int STATUS_FAILURE_STORAGE = 5; // 0x5 + field public static final int STATUS_FAILURE_ABORTED = 3; // 0x3 + field public static final int STATUS_FAILURE_BLOCKED = 2; // 0x2 + field public static final int STATUS_FAILURE_CONFLICT = 5; // 0x5 + field public static final int STATUS_FAILURE_INCOMPATIBLE = 7; // 0x7 + field public static final int STATUS_FAILURE_INVALID = 4; // 0x4 + field public static final int STATUS_FAILURE_STORAGE = 6; // 0x6 + field public static final int STATUS_PENDING_USER_ACTION = -1; // 0xffffffff field public static final int STATUS_SUCCESS = 0; // 0x0 - field public static final int STATUS_USER_ACTION_REQUIRED = -1; // 0xffffffff } public static class PackageInstaller.Session implements java.io.Closeable { @@ -8681,7 +8681,7 @@ package android.content.pm { method public void close(); method public void commit(android.content.IntentSender); method public void fsync(java.io.OutputStream) throws java.io.IOException; - method public java.lang.String[] getNames(); + method public java.lang.String[] getNames() throws java.io.IOException; method public java.io.InputStream openRead(java.lang.String) throws java.io.IOException; method public java.io.OutputStream openWrite(java.lang.String, long, long) throws java.io.IOException; method public void setProgress(float); diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index e5bffee..f5ac5f7 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -2095,7 +2095,9 @@ public class Activity extends ContextThemeWrapper "by the window decor. Do not request Window.FEATURE_ACTION_BAR and set " + "android:windowActionBar to false in your theme to use a Toolbar instead."); } - mActionBar = new ToolbarActionBar(toolbar, getTitle(), this); + ToolbarActionBar tbab = new ToolbarActionBar(toolbar, getTitle(), this); + mActionBar = tbab; + mWindow.setCallback(tbab.getWrappedWindowCallback()); mActionBar.invalidateOptionsMenu(); } diff --git a/core/java/android/app/backup/BackupDataInput.java b/core/java/android/app/backup/BackupDataInput.java index 03205fb..26f9e3e 100644 --- a/core/java/android/app/backup/BackupDataInput.java +++ b/core/java/android/app/backup/BackupDataInput.java @@ -16,6 +16,8 @@ package android.app.backup; +import android.annotation.SystemApi; + import java.io.FileDescriptor; import java.io.IOException; @@ -70,6 +72,7 @@ public class BackupDataInput { } /** @hide */ + @SystemApi public BackupDataInput(FileDescriptor fd) { if (fd == null) throw new NullPointerException(); mBackupReader = ctor(fd); @@ -79,6 +82,7 @@ public class BackupDataInput { } /** @hide */ + @Override protected void finalize() throws Throwable { try { dtor(mBackupReader); @@ -174,7 +178,7 @@ public class BackupDataInput { * for further processing. This allows a {@link android.app.backup.BackupAgent} to * efficiently discard obsolete or otherwise uninteresting records during the * restore operation. - * + * * @throws IOException if an error occurred when trying to read the restore data stream */ public void skipEntityData() throws IOException { diff --git a/core/java/android/app/backup/BackupDataOutput.java b/core/java/android/app/backup/BackupDataOutput.java index fc5fb3d..048a4bb 100644 --- a/core/java/android/app/backup/BackupDataOutput.java +++ b/core/java/android/app/backup/BackupDataOutput.java @@ -16,6 +16,7 @@ package android.app.backup; +import android.annotation.SystemApi; import android.os.ParcelFileDescriptor; import android.os.Process; @@ -66,6 +67,7 @@ public class BackupDataOutput { long mBackupWriter; /** @hide */ + @SystemApi public BackupDataOutput(FileDescriptor fd) { if (fd == null) throw new NullPointerException(); mBackupWriter = ctor(fd); @@ -115,6 +117,7 @@ public class BackupDataOutput { } /** @hide */ + @Override protected void finalize() throws Throwable { try { dtor(mBackupWriter); diff --git a/core/java/android/app/backup/BackupTransport.java b/core/java/android/app/backup/BackupTransport.java index 6cfabf0..70bb5e4 100644 --- a/core/java/android/app/backup/BackupTransport.java +++ b/core/java/android/app/backup/BackupTransport.java @@ -51,7 +51,7 @@ public class BackupTransport { public static final int AGENT_UNKNOWN = -1004; IBackupTransport mBinderImpl = new TransportImpl(); - /** @hide */ + public IBinder getBinder() { return mBinderImpl.asBinder(); } diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index aa4ea45..4ac701f 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -129,7 +129,7 @@ public class PackageInstaller { * * @see Intent#getParcelableExtra(String) */ - public static final int STATUS_USER_ACTION_REQUIRED = -1; + public static final int STATUS_PENDING_USER_ACTION = -1; /** * The operation succeeded. @@ -152,7 +152,7 @@ public class PackageInstaller { * * @see #EXTRA_STATUS_MESSAGE */ - public static final int STATUS_FAILURE_BLOCKED = 1; + public static final int STATUS_FAILURE_BLOCKED = 2; /** * The operation failed because it was actively aborted. For example, the @@ -161,7 +161,7 @@ public class PackageInstaller { * * @see #EXTRA_STATUS_MESSAGE */ - public static final int STATUS_FAILURE_ABORTED = 2; + public static final int STATUS_FAILURE_ABORTED = 3; /** * The operation failed because one or more of the APKs was invalid. For @@ -170,7 +170,7 @@ public class PackageInstaller { * * @see #EXTRA_STATUS_MESSAGE */ - public static final int STATUS_FAILURE_INVALID = 3; + public static final int STATUS_FAILURE_INVALID = 4; /** * The operation failed because it conflicts (or is inconsistent with) with @@ -183,7 +183,7 @@ public class PackageInstaller { * * @see #EXTRA_STATUS_MESSAGE */ - public static final int STATUS_FAILURE_CONFLICT = 4; + public static final int STATUS_FAILURE_CONFLICT = 5; /** * The operation failed because of storage issues. For example, the device @@ -192,7 +192,7 @@ public class PackageInstaller { * * @see #EXTRA_STATUS_MESSAGE */ - public static final int STATUS_FAILURE_STORAGE = 5; + public static final int STATUS_FAILURE_STORAGE = 6; /** * The operation failed because it is fundamentally incompatible with this @@ -202,7 +202,7 @@ public class PackageInstaller { * * @see #EXTRA_STATUS_MESSAGE */ - public static final int STATUS_FAILURE_INCOMPATIBLE = 6; + public static final int STATUS_FAILURE_INCOMPATIBLE = 7; private final Context mContext; private final PackageManager mPm; @@ -584,9 +584,12 @@ public class PackageInstaller { * This returns all names which have been previously written through * {@link #openWrite(String, long, long)} as part of this session. */ - public @NonNull String[] getNames() { + public @NonNull String[] getNames() throws IOException { try { return mSession.getNames(); + } catch (RuntimeException e) { + ExceptionUtils.maybeUnwrapIOException(e); + throw e; } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index 68b91cb..4cdafe1 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -619,6 +619,16 @@ public class StorageManager { private static final long DEFAULT_FULL_THRESHOLD_BYTES = MB_IN_BYTES; /** + * Return the number of available bytes until the given path is considered + * running low on storage. + * + * @hide + */ + public long getStorageBytesUntilLow(File path) { + return path.getUsableSpace() - getStorageFullBytes(path); + } + + /** * Return the number of available bytes at which the given path is * considered running low on storage. * diff --git a/core/java/android/view/WindowCallbackWrapper.java b/core/java/android/view/WindowCallbackWrapper.java new file mode 100644 index 0000000..35a6a76 --- /dev/null +++ b/core/java/android/view/WindowCallbackWrapper.java @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package android.view; + +import android.view.accessibility.AccessibilityEvent; + +/** + * A simple decorator stub for Window.Callback that passes through any calls + * to the wrapped instance as a base implementation. Call super.foo() to call into + * the wrapped callback for any subclasses. + * + * @hide for internal use + */ +public class WindowCallbackWrapper implements Window.Callback { + private Window.Callback mWrapped; + + public WindowCallbackWrapper(Window.Callback wrapped) { + if (wrapped == null) { + throw new IllegalArgumentException("Window callback may not be null"); + } + mWrapped = wrapped; + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + return mWrapped.dispatchKeyEvent(event); + } + + @Override + public boolean dispatchKeyShortcutEvent(KeyEvent event) { + return mWrapped.dispatchKeyShortcutEvent(event); + } + + @Override + public boolean dispatchTouchEvent(MotionEvent event) { + return mWrapped.dispatchTouchEvent(event); + } + + @Override + public boolean dispatchTrackballEvent(MotionEvent event) { + return mWrapped.dispatchTrackballEvent(event); + } + + @Override + public boolean dispatchGenericMotionEvent(MotionEvent event) { + return mWrapped.dispatchGenericMotionEvent(event); + } + + @Override + public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { + return mWrapped.dispatchPopulateAccessibilityEvent(event); + } + + @Override + public View onCreatePanelView(int featureId) { + return mWrapped.onCreatePanelView(featureId); + } + + @Override + public boolean onCreatePanelMenu(int featureId, Menu menu) { + return mWrapped.onCreatePanelMenu(featureId, menu); + } + + @Override + public boolean onPreparePanel(int featureId, View view, Menu menu) { + return mWrapped.onPreparePanel(featureId, view, menu); + } + + @Override + public boolean onMenuOpened(int featureId, Menu menu) { + return mWrapped.onMenuOpened(featureId, menu); + } + + @Override + public boolean onMenuItemSelected(int featureId, MenuItem item) { + return mWrapped.onMenuItemSelected(featureId, item); + } + + @Override + public void onWindowAttributesChanged(WindowManager.LayoutParams attrs) { + mWrapped.onWindowAttributesChanged(attrs); + } + + @Override + public void onContentChanged() { + mWrapped.onContentChanged(); + } + + @Override + public void onWindowFocusChanged(boolean hasFocus) { + mWrapped.onWindowFocusChanged(hasFocus); + } + + @Override + public void onAttachedToWindow() { + mWrapped.onAttachedToWindow(); + } + + @Override + public void onDetachedFromWindow() { + mWrapped.onDetachedFromWindow(); + } + + @Override + public void onPanelClosed(int featureId, Menu menu) { + mWrapped.onPanelClosed(featureId, menu); + } + + @Override + public boolean onSearchRequested() { + return mWrapped.onSearchRequested(); + } + + @Override + public ActionMode onWindowStartingActionMode(ActionMode.Callback callback) { + return mWrapped.onWindowStartingActionMode(callback); + } + + @Override + public void onActionModeStarted(ActionMode mode) { + mWrapped.onActionModeStarted(mode); + } + + @Override + public void onActionModeFinished(ActionMode mode) { + mWrapped.onActionModeFinished(mode); + } +} + diff --git a/core/java/android/widget/FastScroller.java b/core/java/android/widget/FastScroller.java index c0961fd..06b7a93 100644 --- a/core/java/android/widget/FastScroller.java +++ b/core/java/android/widget/FastScroller.java @@ -79,6 +79,7 @@ class FastScroller { // Positions for preview image and text. private static final int OVERLAY_FLOATING = 0; private static final int OVERLAY_AT_THUMB = 1; + private static final int OVERLAY_ABOVE_THUMB = 2; // Indices for mPreviewResId. private static final int PREVIEW_LEFT = 0; @@ -189,8 +190,9 @@ class FastScroller { /** * Position for the preview image and text. One of: * <ul> - * <li>{@link #OVERLAY_AT_THUMB} * <li>{@link #OVERLAY_FLOATING} + * <li>{@link #OVERLAY_AT_THUMB} + * <li>{@link #OVERLAY_ABOVE_THUMB} * </ul> */ private int mOverlayPosition; @@ -310,8 +312,10 @@ class FastScroller { final int textMinSize = Math.max(0, mPreviewMinHeight); mPrimaryText.setMinimumWidth(textMinSize); mPrimaryText.setMinimumHeight(textMinSize); + mPrimaryText.setIncludeFontPadding(false); mSecondaryText.setMinimumWidth(textMinSize); mSecondaryText.setMinimumHeight(textMinSize); + mSecondaryText.setIncludeFontPadding(false); refreshDrawablePressedState(); } @@ -595,10 +599,10 @@ class FastScroller { margins.right = mPreviewImage.getPaddingRight(); margins.bottom = mPreviewImage.getPaddingBottom(); - if (mOverlayPosition == OVERLAY_AT_THUMB) { - measureViewToSide(v, mThumbImage, margins, out); - } else { + if (mOverlayPosition == OVERLAY_FLOATING) { measureFloating(v, margins, out); + } else { + measureViewToSide(v, mThumbImage, margins, out); } } @@ -1147,11 +1151,23 @@ class FastScroller { final float thumbMiddle = position * range + offset; thumbImage.setTranslationY(thumbMiddle - thumbImage.getHeight() / 2); - final float previewPos = mOverlayPosition == OVERLAY_AT_THUMB ? thumbMiddle : 0; - - // Center the preview on the thumb, constrained to the list bounds. final View previewImage = mPreviewImage; final float previewHalfHeight = previewImage.getHeight() / 2f; + final float previewPos; + switch (mOverlayPosition) { + case OVERLAY_AT_THUMB: + previewPos = thumbMiddle; + break; + case OVERLAY_ABOVE_THUMB: + previewPos = thumbMiddle - previewHalfHeight; + break; + case OVERLAY_FLOATING: + default: + previewPos = 0; + break; + } + + // Center the preview on the thumb, constrained to the list bounds. final float minP = top + previewHalfHeight; final float maxP = bottom - previewHalfHeight; final float previewMiddle = MathUtils.constrain(previewPos, minP, maxP); diff --git a/core/java/com/android/internal/app/IMediaContainerService.aidl b/core/java/com/android/internal/app/IMediaContainerService.aidl index 7a3ffdf..81ea191 100644 --- a/core/java/com/android/internal/app/IMediaContainerService.aidl +++ b/core/java/com/android/internal/app/IMediaContainerService.aidl @@ -25,10 +25,7 @@ interface IMediaContainerService { boolean isExternal, boolean isForwardLocked, String abiOverride); int copyPackage(String packagePath, in IParcelFileDescriptorFactory target); - PackageInfoLite getMinimalPackageInfo(String packagePath, int flags, long threshold, - String abiOverride); - boolean checkInternalFreeStorage(String packagePath, boolean isForwardLocked, long threshold); - boolean checkExternalFreeStorage(String packagePath, boolean isForwardLocked, String abiOverride); + PackageInfoLite getMinimalPackageInfo(String packagePath, int flags, String abiOverride); ObbInfo getObbInfo(String filename); long calculateDirectorySize(String directory); /** Return file system stats: [0] is total bytes, [1] is available bytes */ diff --git a/core/java/com/android/internal/app/ToolbarActionBar.java b/core/java/com/android/internal/app/ToolbarActionBar.java index 96d7192..13eb182 100644 --- a/core/java/com/android/internal/app/ToolbarActionBar.java +++ b/core/java/com/android/internal/app/ToolbarActionBar.java @@ -29,6 +29,7 @@ import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.Window; +import android.view.WindowCallbackWrapper; import android.widget.SpinnerAdapter; import android.widget.Toolbar; import com.android.internal.view.menu.MenuBuilder; @@ -40,6 +41,7 @@ import java.util.ArrayList; public class ToolbarActionBar extends ActionBar { private Toolbar mToolbar; private DecorToolbar mDecorToolbar; + private boolean mToolbarMenuPrepared; private Window.Callback mWindowCallback; private boolean mLastMenuVisibility; @@ -64,12 +66,16 @@ public class ToolbarActionBar extends ActionBar { public ToolbarActionBar(Toolbar toolbar, CharSequence title, Window.Callback windowCallback) { mToolbar = toolbar; mDecorToolbar = new ToolbarWidgetWrapper(toolbar, false); - mWindowCallback = windowCallback; + mWindowCallback = new ToolbarCallbackWrapper(windowCallback); mDecorToolbar.setWindowCallback(mWindowCallback); toolbar.setOnMenuItemClickListener(mMenuClicker); mDecorToolbar.setWindowTitle(title); } + public Window.Callback getWrappedWindowCallback() { + return mWindowCallback; + } + @Override public void setCustomView(View view) { setCustomView(view, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); @@ -465,4 +471,20 @@ public class ToolbarActionBar extends ActionBar { mMenuVisibilityListeners.get(i).onMenuVisibilityChanged(isVisible); } } + + private class ToolbarCallbackWrapper extends WindowCallbackWrapper { + public ToolbarCallbackWrapper(Window.Callback wrapped) { + super(wrapped); + } + + @Override + public boolean onPreparePanel(int featureId, View view, Menu menu) { + final boolean result = super.onPreparePanel(featureId, view, menu); + if (result && !mToolbarMenuPrepared) { + mDecorToolbar.setMenuPrepared(); + mToolbarMenuPrepared = true; + } + return result; + } + } } diff --git a/core/java/com/android/internal/content/PackageHelper.java b/core/java/com/android/internal/content/PackageHelper.java index eff6684..fd96f64 100644 --- a/core/java/com/android/internal/content/PackageHelper.java +++ b/core/java/com/android/internal/content/PackageHelper.java @@ -16,14 +16,21 @@ package com.android.internal.content; +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.os.Environment; import android.os.FileUtils; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; import android.os.storage.IMountService; +import android.os.storage.StorageManager; import android.os.storage.StorageResultCode; import android.util.Log; +import libcore.io.IoUtils; + import java.io.File; import java.io.FileOutputStream; import java.io.IOException; @@ -33,8 +40,6 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import java.util.zip.ZipOutputStream; -import libcore.io.IoUtils; - /** * Constants used internally between the PackageManager * and media container service transports. @@ -298,4 +303,78 @@ public class PackageHelper { } return false; } + + /** + * Given a requested {@link PackageInfo#installLocation} and calculated + * install size, pick the actual location to install the app. + */ + public static int resolveInstallLocation(Context context, int installLocation, long sizeBytes, + int installFlags) { + final int prefer; + final boolean checkBoth; + if ((installFlags & PackageManager.INSTALL_INTERNAL) != 0) { + prefer = RECOMMEND_INSTALL_INTERNAL; + checkBoth = false; + } else if ((installFlags & PackageManager.INSTALL_EXTERNAL) != 0) { + prefer = RECOMMEND_INSTALL_EXTERNAL; + checkBoth = false; + } else if (installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) { + prefer = RECOMMEND_INSTALL_INTERNAL; + checkBoth = false; + } else if (installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) { + prefer = RECOMMEND_INSTALL_EXTERNAL; + checkBoth = true; + } else if (installLocation == PackageInfo.INSTALL_LOCATION_AUTO) { + prefer = RECOMMEND_INSTALL_INTERNAL; + checkBoth = true; + } else { + prefer = RECOMMEND_INSTALL_INTERNAL; + checkBoth = false; + } + + final boolean emulated = Environment.isExternalStorageEmulated(); + final StorageManager storage = StorageManager.from(context); + + boolean fitsOnInternal = false; + if (checkBoth || prefer == RECOMMEND_INSTALL_INTERNAL) { + fitsOnInternal = (sizeBytes + <= storage.getStorageBytesUntilLow(Environment.getDataDirectory())); + } + + boolean fitsOnExternal = false; + if (!emulated && (checkBoth || prefer == RECOMMEND_INSTALL_EXTERNAL)) { + fitsOnExternal = (sizeBytes + <= storage.getStorageBytesUntilLow(Environment.getExternalStorageDirectory())); + } + + if (prefer == RECOMMEND_INSTALL_INTERNAL) { + if (fitsOnInternal) { + return PackageHelper.RECOMMEND_INSTALL_INTERNAL; + } + } else if (!emulated && prefer == RECOMMEND_INSTALL_EXTERNAL) { + if (fitsOnExternal) { + return PackageHelper.RECOMMEND_INSTALL_EXTERNAL; + } + } + + if (checkBoth) { + if (fitsOnInternal) { + return PackageHelper.RECOMMEND_INSTALL_INTERNAL; + } else if (!emulated && fitsOnExternal) { + return PackageHelper.RECOMMEND_INSTALL_EXTERNAL; + } + } + + /* + * If they requested to be on the external media by default, return that + * the media was unavailable. Otherwise, indicate there was insufficient + * storage space available. + */ + if (!emulated && (checkBoth || prefer == RECOMMEND_INSTALL_EXTERNAL) + && !Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) { + return PackageHelper.RECOMMEND_MEDIA_UNAVAILABLE; + } else { + return PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE; + } + } } diff --git a/core/res/res/drawable/fastscroll_label_left_material.xml b/core/res/res/drawable/fastscroll_label_left_material.xml new file mode 100644 index 0000000..430d1b0 --- /dev/null +++ b/core/res/res/drawable/fastscroll_label_left_material.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2014 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. +--> + +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle"> + <corners + android:topLeftRadius="44dp" + android:topRightRadius="44dp" + android:bottomRightRadius="44dp" /> + <padding + android:paddingLeft="22dp" + android:paddingRight="22dp" /> + <solid android:color="?attr/colorControlActivated" /> +</shape> diff --git a/core/res/res/drawable/fastscroll_label_right_material.xml b/core/res/res/drawable/fastscroll_label_right_material.xml new file mode 100644 index 0000000..6e61397 --- /dev/null +++ b/core/res/res/drawable/fastscroll_label_right_material.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2014 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. +--> + +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle"> + <corners + android:topLeftRadius="44dp" + android:topRightRadius="44dp" + android:bottomLeftRadius="44dp" /> + <padding + android:paddingLeft="22dp" + android:paddingRight="22dp" /> + <solid android:color="?attr/colorControlActivated" /> +</shape> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 89bda82..f9ea5d8 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -733,6 +733,7 @@ <attr name="fastScrollOverlayPosition"> <enum name="floating" value="0" /> <enum name="atThumb" value="1" /> + <enum name="aboveThumb" value="2" /> </attr> <!-- Text color for the fast scroll index overlay. Make sure it plays nicely with fastScrollPreviewBackground[Left|Right]. --> @@ -3196,6 +3197,8 @@ <enum name="floating" value="0" /> <!-- Pinned alongside the thumb. --> <enum name="atThumb" value="1" /> + <!-- Pinned above the thumb. --> + <enum name="aboveThumb" value="2" /> </attr> <attr name="textAppearance" /> <attr name="textColor" /> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index bf97ca5..f702b95 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1656,4 +1656,11 @@ <!-- Package name providing WebView implementation. --> <string name="config_webViewPackageName" translatable="false">com.android.webview</string> + <!-- If EMS is not supported, framework breaks down EMS into single segment SMS + and adds page info " x/y". This config is used to set which carrier doesn't + support EMS and whether page info should be added at the beginning or the end. + We use tag 'prefix' for position beginning and 'suffix' for position end. + Examples: <item>311480;prefix</item> <item>310260;suffix</item> + --> + <string-array translatable="false" name="no_ems_support_sim_operators" /> </resources> diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml index 0f27824..298fea3 100644 --- a/core/res/res/values/styles_material.xml +++ b/core/res/res/values/styles_material.xml @@ -877,6 +877,10 @@ please see styles_device_defaults.xml. <style name="Widget.Material.FastScroll" parent="Widget.FastScroll"> <item name="thumbMinWidth">0dp</item> <item name="thumbMinHeight">0dp</item> + <item name="minWidth">88dp</item> + <item name="minHeight">88dp</item> + <item name="padding">0dp</item> + <item name="textSize">45sp</item> </style> <style name="Widget.Material.PreferenceFrameLayout"> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 426a82d..1cb7b17 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1982,4 +1982,5 @@ <java-symbol type="attr" name="checkMarkGravity" /> <java-symbol type="layout" name="select_dialog_singlechoice_material" /> <java-symbol type="layout" name="select_dialog_multichoice_material" /> + <java-symbol type="array" name="no_ems_support_sim_operators" /> </resources> diff --git a/core/res/res/values/themes_material.xml b/core/res/res/values/themes_material.xml index b285797..18170ac 100644 --- a/core/res/res/values/themes_material.xml +++ b/core/res/res/values/themes_material.xml @@ -364,10 +364,10 @@ please see themes_device_defaults.xml. <!-- TODO: This belongs in a FastScroll style --> <item name="fastScrollThumbDrawable">@drawable/fastscroll_thumb_material</item> - <item name="fastScrollPreviewBackgroundLeft">@drawable/fastscroll_label_left_holo_dark</item> - <item name="fastScrollPreviewBackgroundRight">@drawable/fastscroll_label_right_holo_dark</item> + <item name="fastScrollPreviewBackgroundLeft">@drawable/fastscroll_label_left_material</item> + <item name="fastScrollPreviewBackgroundRight">@drawable/fastscroll_label_right_material</item> <item name="fastScrollTrackDrawable">@drawable/fastscroll_track_material</item> - <item name="fastScrollOverlayPosition">atThumb</item> + <item name="fastScrollOverlayPosition">aboveThumb</item> <!-- Color palette --> <item name="colorPrimaryDark">@color/material_blue_grey_900</item> @@ -706,10 +706,10 @@ please see themes_device_defaults.xml. <item name="datePickerDialogTheme">?attr/dialogTheme</item> <item name="fastScrollThumbDrawable">@drawable/fastscroll_thumb_material</item> - <item name="fastScrollPreviewBackgroundLeft">@drawable/fastscroll_label_left_holo_light</item> - <item name="fastScrollPreviewBackgroundRight">@drawable/fastscroll_label_right_holo_light</item> + <item name="fastScrollPreviewBackgroundLeft">@drawable/fastscroll_label_left_material</item> + <item name="fastScrollPreviewBackgroundRight">@drawable/fastscroll_label_right_material</item> <item name="fastScrollTrackDrawable">@drawable/fastscroll_track_material</item> - <item name="fastScrollOverlayPosition">atThumb</item> + <item name="fastScrollOverlayPosition">aboveThumb</item> <!-- Color palette --> <item name="colorPrimaryDark">@color/material_blue_grey_100</item> diff --git a/graphics/java/android/graphics/drawable/AnimationDrawable.java b/graphics/java/android/graphics/drawable/AnimationDrawable.java index 5318fa7..d87e8e4 100644 --- a/graphics/java/android/graphics/drawable/AnimationDrawable.java +++ b/graphics/java/android/graphics/drawable/AnimationDrawable.java @@ -114,7 +114,9 @@ public class AnimationDrawable extends DrawableContainer implements Runnable, An final boolean changed = super.setVisible(visible, restart); if (visible) { if (restart || changed) { - setFrame(restart ? 0 : mCurFrame, true, mAnimating); + boolean startFromZero = restart || mCurFrame < 0 || + mCurFrame >= mAnimationState.getChildCount(); + setFrame(startFromZero ? 0 : mCurFrame, true, mAnimating); } } else { unscheduleSelf(this); diff --git a/graphics/java/android/graphics/drawable/InsetDrawable.java b/graphics/java/android/graphics/drawable/InsetDrawable.java index 588e776..dd0f06f 100644 --- a/graphics/java/android/graphics/drawable/InsetDrawable.java +++ b/graphics/java/android/graphics/drawable/InsetDrawable.java @@ -26,10 +26,13 @@ import android.content.res.ColorStateList; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.Resources.Theme; -import android.graphics.*; +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.Insets; +import android.graphics.Outline; import android.graphics.PorterDuff.Mode; +import android.graphics.Rect; import android.util.AttributeSet; -import android.util.Log; import java.io.IOException; @@ -50,8 +53,6 @@ import java.io.IOException; * @attr ref android.R.styleable#InsetDrawable_insetBottom */ public class InsetDrawable extends Drawable implements Drawable.Callback { - private static final String LOG_TAG = "InsetDrawable"; - private final Rect mTmpRect = new Rect(); private InsetState mInsetState; @@ -86,7 +87,6 @@ public class InsetDrawable extends Drawable implements Drawable.Callback { final TypedArray a = r.obtainAttributes(attrs, R.styleable.InsetDrawable); super.inflateWithAttributes(r, parser, a, R.styleable.InsetDrawable_visible); updateStateFromTypedArray(a); - a.recycle(); // Load inner XML elements. if (mInsetState.mDrawable == null) { @@ -104,9 +104,17 @@ public class InsetDrawable extends Drawable implements Drawable.Callback { dr.setCallback(this); } - // Verify state. - if (mInsetState.mDrawable == null) { - Log.w(LOG_TAG, "No drawable specified for <inset>"); + verifyRequiredAttributes(a); + a.recycle(); + } + + private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException { + // If we're not waiting on a theme, verify required attributes. + if (mInsetState.mDrawable == null && (mInsetState.mThemeAttrs == null + || mInsetState.mThemeAttrs[R.styleable.InsetDrawable_drawable] == 0)) { + throw new XmlPullParserException(a.getPositionDescription() + + ": <inset> tag requires a 'drawable' attribute or " + + "child tag defining a drawable"); } } @@ -167,6 +175,7 @@ public class InsetDrawable extends Drawable implements Drawable.Callback { final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.InsetDrawable); try { updateStateFromTypedArray(a); + verifyRequiredAttributes(a); } catch (XmlPullParserException e) { throw new RuntimeException(e); } finally { @@ -224,12 +233,8 @@ public class InsetDrawable extends Drawable implements Drawable.Callback { padding.top += mInsetState.mInsetTop; padding.bottom += mInsetState.mInsetBottom; - if (pad || (mInsetState.mInsetLeft | mInsetState.mInsetRight | - mInsetState.mInsetTop | mInsetState.mInsetBottom) != 0) { - return true; - } else { - return false; - } + return pad || (mInsetState.mInsetLeft | mInsetState.mInsetRight | + mInsetState.mInsetTop | mInsetState.mInsetBottom) != 0; } /** @hide */ diff --git a/media/java/android/media/tv/TvInputInfo.java b/media/java/android/media/tv/TvInputInfo.java index 6a7433d..3f5697f 100644 --- a/media/java/android/media/tv/TvInputInfo.java +++ b/media/java/android/media/tv/TvInputInfo.java @@ -158,17 +158,17 @@ public final class TvInputInfo implements Parcelable { } /** - * Create a new instance of the TvInputInfo class, - * instantiating it from the given Context, ResolveInfo, and HdmiDeviceInfo. + * Create a new instance of the TvInputInfo class, instantiating it from the given Context, + * ResolveInfo, and HdmiDeviceInfo. * * @param service The ResolveInfo returned from the package manager about this TV input service. * @param deviceInfo The HdmiDeviceInfo for a HDMI CEC logical device. * @param parentId The ID of this TV input's parent input. {@code null} if none exists. - * @param iconUri The {@link android.net.Uri} to load the icon image. - * {@see android.content.ContentResolver#openInputStream}. If it is null, the application - * icon of {@code service} will be loaded. - * @param label The label of this TvInputInfo. If it is null or empty, {@code service} label - * will be loaded. + * @param iconUri The {@link android.net.Uri} to load the icon image. See + * {@link android.content.ContentResolver#openInputStream}. If it is {@code null}, + * the application icon of {@code service} will be loaded. + * @param label The label of this TvInputInfo. If it is {@code null} or empty, {@code service} + * label will be loaded. * @hide */ @SystemApi @@ -182,16 +182,16 @@ public final class TvInputInfo implements Parcelable { } /** - * Create a new instance of the TvInputInfo class, - * instantiating it from the given Context, ResolveInfo, and TvInputHardwareInfo. + * Create a new instance of the TvInputInfo class, instantiating it from the given Context, + * ResolveInfo, and TvInputHardwareInfo. * * @param service The ResolveInfo returned from the package manager about this TV input service. * @param hardwareInfo The TvInputHardwareInfo for a TV input hardware device. - * @param iconUri The {@link android.net.Uri} to load the icon image. - * {@see android.content.ContentResolver#openInputStream}. If it is null, the application - * icon of {@code service} will be loaded. - * @param label The label of this TvInputInfo. If it is null or empty, {@code service} label - * will be loaded. + * @param iconUri The {@link android.net.Uri} to load the icon image. See + * {@link android.content.ContentResolver#openInputStream}. If it is {@code null}, + * the application icon of {@code service} will be loaded. + * @param label The label of this TvInputInfo. If it is {@code null} or empty, {@code service} + * label will be loaded. * @hide */ @SystemApi @@ -620,7 +620,7 @@ public final class TvInputInfo implements Parcelable { return new HashSet<String>(); } String[] ids = hiddenIdsString.split(TV_INPUT_SEPARATOR); - return new HashSet(Arrays.asList(ids)); + return new HashSet<>(Arrays.asList(ids)); } /** diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java index 6e075b2..f76c78b 100644 --- a/media/java/android/media/tv/TvInputManager.java +++ b/media/java/android/media/tv/TvInputManager.java @@ -235,14 +235,14 @@ public final class TvInputManager { } /** - * This is called when {@link TvInputService.Session#layoutSurface} is called to - * change the layout of surface. + * This is called when {@link TvInputService.Session#layoutSurface} is called to change the + * layout of surface. * * @param session A {@link TvInputManager.Session} associated with this callback - * @param l Left position. - * @param t Top position. - * @param r Right position. - * @param b Bottom position. + * @param left Left position. + * @param top Top position. + * @param right Right position. + * @param bottom Bottom position. * @hide */ @SystemApi @@ -1164,7 +1164,7 @@ public final class TvInputManager { * {@link TvTrackInfo#TYPE_SUBTITLE}. * @param trackId The ID of the track to select. When {@code null}, the currently selected * track of the given type will be unselected. - * @see #getTracks() + * @see #getTracks */ public void selectTrack(int type, String trackId) { if (type == TvTrackInfo.TYPE_AUDIO) { @@ -1462,14 +1462,14 @@ public final class TvInputManager { // Assumes the event has already been removed from the queue. void invokeFinishedInputEventCallback(PendingEvent p, boolean handled) { p.mHandled = handled; - if (p.mHandler.getLooper().isCurrentThread()) { + if (p.mEventHandler.getLooper().isCurrentThread()) { // Already running on the callback handler thread so we can send the callback // immediately. p.run(); } else { // Post the event to the callback handler thread. // In this case, the callback will be responsible for recycling the event. - Message msg = Message.obtain(p.mHandler, p); + Message msg = Message.obtain(p.mEventHandler, p); msg.setAsynchronous(true); msg.sendToTarget(); } @@ -1494,9 +1494,9 @@ public final class TvInputManager { p = new PendingEvent(); } p.mEvent = event; - p.mToken = token; + p.mEventToken = token; p.mCallback = callback; - p.mHandler = handler; + p.mEventHandler = handler; return p; } @@ -1568,24 +1568,24 @@ public final class TvInputManager { private final class PendingEvent implements Runnable { public InputEvent mEvent; - public Object mToken; + public Object mEventToken; public FinishedInputEventCallback mCallback; - public Handler mHandler; + public Handler mEventHandler; public boolean mHandled; public void recycle() { mEvent = null; - mToken = null; + mEventToken = null; mCallback = null; - mHandler = null; + mEventHandler = null; mHandled = false; } @Override public void run() { - mCallback.onFinishedInputEvent(mToken, mHandled); + mCallback.onFinishedInputEvent(mEventToken, mHandled); - synchronized (mHandler) { + synchronized (mEventHandler) { recyclePendingEventLocked(this); } } diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java index 8ffe6cc..c93b261 100644 --- a/media/java/android/media/tv/TvInputService.java +++ b/media/java/android/media/tv/TvInputService.java @@ -504,12 +504,13 @@ public abstract class TvInputService extends Service { /** * Assigns a position of the {@link Surface} passed by {@link #onSetSurface}. The position - * is relative to an overlay view. {@see #onOverlayViewSizeChanged}. + * is relative to an overlay view. * * @param left Left position in pixels, relative to the overlay view. * @param top Top position in pixels, relative to the overlay view. * @param right Right position in pixels, relative to the overlay view. * @param bottm Bottom position in pixels, relative to the overlay view. + * @see #onOverlayViewSizeChanged * @hide */ @SystemApi diff --git a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java index 20a621b..4c225c1 100644 --- a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java +++ b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java @@ -16,11 +16,13 @@ package com.android.defcontainer; +import static android.net.TrafficStats.MB_IN_BYTES; + import android.app.IntentService; +import android.content.Context; import android.content.Intent; import android.content.pm.IPackageManager; import android.content.pm.PackageCleanItem; -import android.content.pm.PackageInfo; import android.content.pm.PackageInfoLite; import android.content.pm.PackageManager; import android.content.pm.PackageParser; @@ -37,8 +39,6 @@ import android.os.ParcelFileDescriptor; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; -import android.os.StatFs; -import android.provider.Settings; import android.system.ErrnoException; import android.system.Os; import android.system.StructStatVfs; @@ -50,13 +50,11 @@ import com.android.internal.content.PackageHelper; import com.android.internal.os.IParcelFileDescriptorFactory; import com.android.internal.util.ArrayUtils; -import dalvik.system.VMRuntime; import libcore.io.IoUtils; import libcore.io.Streams; import java.io.File; import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -155,10 +153,12 @@ public class DefaultContainerService extends IntentService { * containing one or more APKs. */ @Override - public PackageInfoLite getMinimalPackageInfo(final String packagePath, int flags, - long threshold, String abiOverride) { - PackageInfoLite ret = new PackageInfoLite(); + public PackageInfoLite getMinimalPackageInfo(String packagePath, int flags, + String abiOverride) { + final Context context = DefaultContainerService.this; + final boolean isForwardLocked = (flags & PackageManager.INSTALL_FORWARD_LOCK) != 0; + PackageInfoLite ret = new PackageInfoLite(); if (packagePath == null) { Slog.i(TAG, "Invalid package file " + packagePath); ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK; @@ -167,10 +167,12 @@ public class DefaultContainerService extends IntentService { final File packageFile = new File(packagePath); final PackageParser.PackageLite pkg; + final long sizeBytes; try { pkg = PackageParser.parsePackageLite(packageFile, 0); - } catch (PackageParserException e) { - Slog.w(TAG, "Failed to parse package at " + packagePath); + sizeBytes = calculateInstalledSizeInner(pkg, isForwardLocked, abiOverride); + } catch (PackageParserException | IOException e) { + Slog.w(TAG, "Failed to parse package at " + packagePath + ": " + e); if (!packageFile.exists()) { ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_URI; @@ -185,55 +187,13 @@ public class DefaultContainerService extends IntentService { ret.versionCode = pkg.versionCode; ret.installLocation = pkg.installLocation; ret.verifiers = pkg.verifiers; - ret.recommendedInstallLocation = recommendAppInstallLocation(pkg, flags, threshold, - abiOverride); + ret.recommendedInstallLocation = PackageHelper.resolveInstallLocation(context, + pkg.installLocation, sizeBytes, flags); ret.multiArch = pkg.multiArch; return ret; } - /** - * Determine if package will fit on internal storage. - * - * @param packagePath absolute path to the package to be copied. Can be - * a single monolithic APK file or a cluster directory - * containing one or more APKs. - */ - @Override - public boolean checkInternalFreeStorage(String packagePath, boolean isForwardLocked, - long threshold) throws RemoteException { - final File packageFile = new File(packagePath); - final PackageParser.PackageLite pkg; - try { - pkg = PackageParser.parsePackageLite(packageFile, 0); - return isUnderInternalThreshold(pkg, isForwardLocked, threshold); - } catch (PackageParserException | IOException e) { - Slog.w(TAG, "Failed to parse package at " + packagePath); - return false; - } - } - - /** - * Determine if package will fit on external storage. - * - * @param packagePath absolute path to the package to be copied. Can be - * a single monolithic APK file or a cluster directory - * containing one or more APKs. - */ - @Override - public boolean checkExternalFreeStorage(String packagePath, boolean isForwardLocked, - String abiOverride) throws RemoteException { - final File packageFile = new File(packagePath); - final PackageParser.PackageLite pkg; - try { - pkg = PackageParser.parsePackageLite(packageFile, 0); - return isUnderExternalThreshold(pkg, isForwardLocked, abiOverride); - } catch (PackageParserException | IOException e) { - Slog.w(TAG, "Failed to parse package at " + packagePath); - return false; - } - } - @Override public ObbInfo getObbInfo(String filename) { try { @@ -295,13 +255,10 @@ public class DefaultContainerService extends IntentService { final PackageParser.PackageLite pkg; try { pkg = PackageParser.parsePackageLite(packageFile, 0); - return calculateContainerSize(pkg, isForwardLocked, abiOverride) * 1024 * 1024; + return calculateInstalledSizeInner(pkg, isForwardLocked, abiOverride); } catch (PackageParserException | IOException e) { - /* - * Okay, something failed, so let's just estimate it to be 2x - * the file size. Note this will be 0 if the file doesn't exist. - */ - return packageFile.length() * 2; + Slog.w(TAG, "Failed to calculate installed size: " + e); + return Long.MAX_VALUE; } } }; @@ -381,10 +338,12 @@ public class DefaultContainerService extends IntentService { return null; } - // Calculate size of container needed to hold base APK. + // Calculate size of container needed to hold base APK. Round up to + // nearest MB, and tack on an extra MB for filesystem overhead. final int sizeMb; try { - sizeMb = calculateContainerSize(pkg, handle, isForwardLocked, abis); + final long sizeBytes = calculateInstalledSizeInner(pkg, handle, isForwardLocked, abis); + sizeMb = ((int) ((sizeBytes + MB_IN_BYTES) / MB_IN_BYTES)) + 1; } catch (IOException e) { Slog.w(TAG, "Problem when trying to copy " + codeFile.getPath()); return null; @@ -523,124 +482,23 @@ public class DefaultContainerService extends IntentService { } } - private static final int PREFER_INTERNAL = 1; - private static final int PREFER_EXTERNAL = 2; - - private int recommendAppInstallLocation(PackageLite pkg, int flags, long threshold, - String abiOverride) { - int prefer; - boolean checkBoth = false; - - final boolean isForwardLocked = (flags & PackageManager.INSTALL_FORWARD_LOCK) != 0; - - check_inner : { - /* - * Explicit install flags should override the manifest settings. - */ - if ((flags & PackageManager.INSTALL_INTERNAL) != 0) { - prefer = PREFER_INTERNAL; - break check_inner; - } else if ((flags & PackageManager.INSTALL_EXTERNAL) != 0) { - prefer = PREFER_EXTERNAL; - break check_inner; - } - - /* No install flags. Check for manifest option. */ - if (pkg.installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) { - prefer = PREFER_INTERNAL; - break check_inner; - } else if (pkg.installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) { - prefer = PREFER_EXTERNAL; - checkBoth = true; - break check_inner; - } else if (pkg.installLocation == PackageInfo.INSTALL_LOCATION_AUTO) { - // We default to preferring internal storage. - prefer = PREFER_INTERNAL; - checkBoth = true; - break check_inner; - } - - // Pick user preference - int installPreference = Settings.Global.getInt(getApplicationContext() - .getContentResolver(), - Settings.Global.DEFAULT_INSTALL_LOCATION, - PackageHelper.APP_INSTALL_AUTO); - if (installPreference == PackageHelper.APP_INSTALL_INTERNAL) { - prefer = PREFER_INTERNAL; - break check_inner; - } else if (installPreference == PackageHelper.APP_INSTALL_EXTERNAL) { - prefer = PREFER_EXTERNAL; - break check_inner; - } - - /* - * Fall back to default policy of internal-only if nothing else is - * specified. - */ - prefer = PREFER_INTERNAL; - } - - final boolean emulated = Environment.isExternalStorageEmulated(); - - boolean fitsOnInternal = false; - if (checkBoth || prefer == PREFER_INTERNAL) { - try { - fitsOnInternal = isUnderInternalThreshold(pkg, isForwardLocked, threshold); - } catch (IOException e) { - return PackageHelper.RECOMMEND_FAILED_INVALID_URI; - } - } - - boolean fitsOnSd = false; - if (!emulated && (checkBoth || prefer == PREFER_EXTERNAL)) { - try { - fitsOnSd = isUnderExternalThreshold(pkg, isForwardLocked, abiOverride); - } catch (IOException e) { - return PackageHelper.RECOMMEND_FAILED_INVALID_URI; - } - } - - if (prefer == PREFER_INTERNAL) { - if (fitsOnInternal) { - return PackageHelper.RECOMMEND_INSTALL_INTERNAL; - } - } else if (!emulated && prefer == PREFER_EXTERNAL) { - if (fitsOnSd) { - return PackageHelper.RECOMMEND_INSTALL_EXTERNAL; - } - } - - if (checkBoth) { - if (fitsOnInternal) { - return PackageHelper.RECOMMEND_INSTALL_INTERNAL; - } else if (!emulated && fitsOnSd) { - return PackageHelper.RECOMMEND_INSTALL_EXTERNAL; - } - } - - /* - * If they requested to be on the external media by default, return that - * the media was unavailable. Otherwise, indicate there was insufficient - * storage space available. - */ - if (!emulated && (checkBoth || prefer == PREFER_EXTERNAL) - && !Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) { - return PackageHelper.RECOMMEND_MEDIA_UNAVAILABLE; - } else { - return PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE; + private long calculateInstalledSizeInner(PackageLite pkg, boolean isForwardLocked, + String abiOverride) throws IOException { + NativeLibraryHelper.Handle handle = null; + try { + handle = NativeLibraryHelper.Handle.create(pkg); + return calculateInstalledSizeInner(pkg, handle, isForwardLocked, + calculateAbiList(handle, abiOverride, pkg.multiArch)); + } finally { + IoUtils.closeQuietly(handle); } } - /** - * Measure a file to see if it fits within the free space threshold. - * - * @param threshold byte threshold to compare against - * @return true if file fits under threshold - * @throws FileNotFoundException when APK does not exist - */ - private boolean isUnderInternalThreshold(PackageLite pkg, boolean isForwardLocked, - long threshold) throws IOException { + private long calculateInstalledSizeInner(PackageLite pkg, NativeLibraryHelper.Handle handle, + boolean isForwardLocked, String[] abis) throws IOException { long sizeBytes = 0; + + // Include raw APKs, and possibly unpacked resources for (String codePath : pkg.getAllCodePaths()) { sizeBytes += new File(codePath).length(); @@ -649,47 +507,12 @@ public class DefaultContainerService extends IntentService { } } - final StatFs stat = new StatFs(Environment.getDataDirectory().getPath()); - final long availBytes = stat.getAvailableBytes(); - return (availBytes - sizeBytes) > threshold; - } - - /** - * Measure a file to see if it fits in the external free space. - * - * @return true if file fits - * @throws IOException when file does not exist - */ - private boolean isUnderExternalThreshold(PackageLite pkg, boolean isForwardLocked, - String abiOverride) throws IOException { - if (Environment.isExternalStorageEmulated()) { - return false; - } - - final int sizeMb = calculateContainerSize(pkg, isForwardLocked, abiOverride); - - final int availSdMb; - if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) { - final StatFs sdStats = new StatFs(Environment.getExternalStorageDirectory().getPath()); - final int blocksToMb = (1 << 20) / sdStats.getBlockSize(); - availSdMb = sdStats.getAvailableBlocks() * blocksToMb; - } else { - availSdMb = -1; + // Include all relevant native code + if (!ArrayUtils.isEmpty(abis)) { + sizeBytes += NativeLibraryHelper.sumNativeBinariesLI(handle, abis); } - return availSdMb > sizeMb; - } - - private int calculateContainerSize(PackageLite pkg, boolean isForwardLocked, String abiOverride) - throws IOException { - NativeLibraryHelper.Handle handle = null; - try { - handle = NativeLibraryHelper.Handle.create(pkg); - return calculateContainerSize(pkg, handle, isForwardLocked, - calculateAbiList(handle, abiOverride, pkg.multiArch)); - } finally { - IoUtils.closeQuietly(handle); - } + return sizeBytes; } private String[] calculateAbiList(NativeLibraryHelper.Handle handle, String abiOverride, @@ -731,43 +554,4 @@ public class DefaultContainerService extends IntentService { return null; } - - /** - * Calculate the container size for a package. - * - * @return size in megabytes (2^20 bytes) - * @throws IOException when there is a problem reading the file - */ - private int calculateContainerSize(PackageLite pkg, NativeLibraryHelper.Handle handle, - boolean isForwardLocked, String[] abis) throws IOException { - // Calculate size of container needed to hold APKs. - long sizeBytes = 0; - for (String codePath : pkg.getAllCodePaths()) { - sizeBytes += new File(codePath).length(); - - if (isForwardLocked) { - sizeBytes += PackageHelper.extractPublicFiles(codePath, null); - } - } - - // Check all the native files that need to be copied and add that to the - // container size. - if (abis != null) { - sizeBytes += NativeLibraryHelper.sumNativeBinariesLI(handle, abis); - } - - int sizeMb = (int) (sizeBytes >> 20); - if ((sizeBytes - (sizeMb * 1024 * 1024)) > 0) { - sizeMb++; - } - - /* - * Add buffer size because we don't have a good way to determine the - * real FAT size. Your FAT size varies with how many directory entries - * you need, how big the whole filesystem is, and other such headaches. - */ - sizeMb++; - - return sizeMb; - } } diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index dca8ad4..0393518 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -19,6 +19,7 @@ package com.android.server.pm; import static android.content.pm.PackageManager.INSTALL_ALL_USERS; import static android.content.pm.PackageManager.INSTALL_FROM_ADB; import static android.content.pm.PackageManager.INSTALL_REPLACE_EXISTING; +import static android.net.TrafficStats.MB_IN_BYTES; import static com.android.internal.util.XmlUtils.readBitmapAttribute; import static com.android.internal.util.XmlUtils.readBooleanAttribute; import static com.android.internal.util.XmlUtils.readIntAttribute; @@ -42,6 +43,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentSender; import android.content.IntentSender.SendIntentException; +import android.content.pm.ApplicationInfo; import android.content.pm.IPackageInstaller; import android.content.pm.IPackageInstallerCallback; import android.content.pm.IPackageInstallerSession; @@ -65,6 +67,7 @@ import android.os.RemoteException; import android.os.SELinux; import android.os.UserHandle; import android.os.UserManager; +import android.os.storage.StorageManager; import android.system.ErrnoException; import android.system.Os; import android.text.TextUtils; @@ -75,13 +78,14 @@ import android.util.ExceptionUtils; import android.util.Log; import android.util.Slog; import android.util.SparseArray; +import android.util.SparseBooleanArray; import android.util.Xml; import com.android.internal.annotations.GuardedBy; +import com.android.internal.content.PackageHelper; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.IndentingPrintWriter; import com.android.server.IoThread; -import com.android.server.pm.PackageInstallerSession.Snapshot; import com.google.android.collect.Sets; import libcore.io.IoUtils; @@ -108,6 +112,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub { // TODO: remove outstanding sessions when installer package goes away // TODO: notify listeners in other users when package has been installed there + // TODO: purge expired sessions periodically in addition to at reboot /** XML constants used in {@link #mSessionsFile} */ private static final String TAG_SESSIONS = "sessions"; @@ -117,6 +122,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub { private static final String ATTR_INSTALLER_PACKAGE_NAME = "installerPackageName"; private static final String ATTR_CREATED_MILLIS = "createdMillis"; private static final String ATTR_SESSION_STAGE_DIR = "sessionStageDir"; + private static final String ATTR_SESSION_STAGE_CID = "sessionStageCid"; private static final String ATTR_SEALED = "sealed"; private static final String ATTR_MODE = "mode"; private static final String ATTR_INSTALL_FLAGS = "installFlags"; @@ -139,6 +145,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub { private final Context mContext; private final PackageManagerService mPm; private final AppOpsManager mAppOps; + private final StorageManager mStorage; private final File mStagingDir; private final HandlerThread mInstallThread; @@ -165,10 +172,14 @@ public class PackageInstallerService extends IPackageInstaller.Stub { @GuardedBy("mSessions") private final SparseArray<PackageInstallerSession> mHistoricalSessions = new SparseArray<>(); + /** Sessions allocated to legacy users */ + @GuardedBy("mSessions") + private final SparseBooleanArray mLegacySessions = new SparseBooleanArray(); + private static final FilenameFilter sStageFilter = new FilenameFilter() { @Override public boolean accept(File dir, String name) { - return name.startsWith("vmdl") && name.endsWith(".tmp"); + return isStageName(name); } }; @@ -176,6 +187,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub { mContext = context; mPm = pm; mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE); + mStorage = StorageManager.from(mContext); mStagingDir = stagingDir; @@ -190,13 +202,16 @@ public class PackageInstallerService extends IPackageInstaller.Stub { synchronized (mSessions) { readSessionsLocked(); - // Clean up orphaned staging directories - final ArraySet<File> stages = Sets.newArraySet(mStagingDir.listFiles(sStageFilter)); + final ArraySet<File> unclaimed = Sets.newArraySet(mStagingDir.listFiles(sStageFilter)); + + // Ignore stages claimed by active sessions for (int i = 0; i < mSessions.size(); i++) { final PackageInstallerSession session = mSessions.valueAt(i); - stages.remove(session.sessionStageDir); + unclaimed.remove(session.internalStageDir); } - for (File stage : stages) { + + // Clean up orphaned staging directories + for (File stage : unclaimed) { Slog.w(TAG, "Deleting orphan stage " + stage); if (stage.isDirectory()) { FileUtils.deleteContents(stage); @@ -206,22 +221,64 @@ public class PackageInstallerService extends IPackageInstaller.Stub { } } - public static boolean isStageFile(File file) { - return sStageFilter.accept(null, file.getName()); + public void onSecureContainersAvailable() { + synchronized (mSessions) { + final ArraySet<String> unclaimed = new ArraySet<>(); + for (String cid : PackageHelper.getSecureContainerList()) { + if (isStageName(cid)) { + unclaimed.add(cid); + } + } + + // Ignore stages claimed by active sessions + for (int i = 0; i < mSessions.size(); i++) { + final PackageInstallerSession session = mSessions.valueAt(i); + final String cid = session.externalStageCid; + + if (unclaimed.remove(cid)) { + // Claimed by active session, mount it + PackageHelper.mountSdDir(cid, PackageManagerService.getEncryptKey(), + Process.SYSTEM_UID); + } + } + + // Clean up orphaned staging containers + for (String cid : unclaimed) { + Slog.w(TAG, "Deleting orphan container " + cid); + PackageHelper.destroySdDir(cid); + } + } + } + + public static boolean isStageName(String name) { + final boolean isFile = name.startsWith("vmdl") && name.endsWith(".tmp"); + final boolean isContainer = name.startsWith("smdl") && name.endsWith(".tmp"); + final boolean isLegacyContainer = name.startsWith("smdl2tmp"); + return isFile || isContainer || isLegacyContainer; } @Deprecated - public File allocateSessionDir() throws IOException { + public File allocateInternalStageDirLegacy() throws IOException { synchronized (mSessions) { try { final int sessionId = allocateSessionIdLocked(); - return prepareSessionStageDir(sessionId); + mLegacySessions.put(sessionId, true); + return prepareInternalStageDir(sessionId); } catch (IllegalStateException e) { throw new IOException(e); } } } + @Deprecated + public String allocateExternalStageCidLegacy() { + synchronized (mSessions) { + final int sessionId = allocateSessionIdLocked(); + mLegacySessions.put(sessionId, true); + return "smdl" + sessionId + ".tmp"; + } + } + private void readSessionsLocked() { if (LOGD) Slog.v(TAG, "readSessionsLocked()"); @@ -246,9 +303,10 @@ public class PackageInstallerService extends IPackageInstaller.Stub { Slog.w(TAG, "Abandoning old session first created at " + session.createdMillis); valid = false; - } else if (!session.sessionStageDir.exists()) { - Slog.w(TAG, "Abandoning session with missing stage " - + session.sessionStageDir); + } else if (session.internalStageDir != null + && !session.internalStageDir.exists()) { + Slog.w(TAG, "Abandoning internal session with missing stage " + + session.internalStageDir); valid = false; } else { valid = true; @@ -281,7 +339,9 @@ public class PackageInstallerService extends IPackageInstaller.Stub { final int userId = readIntAttribute(in, ATTR_USER_ID); final String installerPackageName = readStringAttribute(in, ATTR_INSTALLER_PACKAGE_NAME); final long createdMillis = readLongAttribute(in, ATTR_CREATED_MILLIS); - final File sessionStageDir = new File(readStringAttribute(in, ATTR_SESSION_STAGE_DIR)); + final String stageDirRaw = readStringAttribute(in, ATTR_SESSION_STAGE_DIR); + final File stageDir = (stageDirRaw != null) ? new File(stageDirRaw) : null; + final String stageCid = readStringAttribute(in, ATTR_SESSION_STAGE_CID); final boolean sealed = readBooleanAttribute(in, ATTR_SEALED); final SessionParams params = new SessionParams( @@ -299,7 +359,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub { return new PackageInstallerSession(mInternalCallback, mContext, mPm, mInstallThread.getLooper(), sessionId, userId, installerPackageName, params, - createdMillis, sessionStageDir, sealed); + createdMillis, stageDir, stageCid, sealed); } private void writeSessionsLocked() { @@ -332,7 +392,6 @@ public class PackageInstallerService extends IPackageInstaller.Stub { private void writeSessionLocked(XmlSerializer out, PackageInstallerSession session) throws IOException { final SessionParams params = session.params; - final Snapshot snapshot = session.snapshot(); out.startTag(null, TAG_SESSION); @@ -341,9 +400,14 @@ public class PackageInstallerService extends IPackageInstaller.Stub { writeStringAttribute(out, ATTR_INSTALLER_PACKAGE_NAME, session.installerPackageName); writeLongAttribute(out, ATTR_CREATED_MILLIS, session.createdMillis); - writeStringAttribute(out, ATTR_SESSION_STAGE_DIR, - session.sessionStageDir.getAbsolutePath()); - writeBooleanAttribute(out, ATTR_SEALED, snapshot.sealed); + if (session.internalStageDir != null) { + writeStringAttribute(out, ATTR_SESSION_STAGE_DIR, + session.internalStageDir.getAbsolutePath()); + } + if (session.externalStageCid != null) { + writeStringAttribute(out, ATTR_SESSION_STAGE_CID, session.externalStageCid); + } + writeBooleanAttribute(out, ATTR_SEALED, session.isSealed()); writeIntAttribute(out, ATTR_MODE, params.mode); writeIntAttribute(out, ATTR_INSTALL_FLAGS, params.installFlags); @@ -372,6 +436,15 @@ public class PackageInstallerService extends IPackageInstaller.Stub { @Override public int createSession(SessionParams params, String installerPackageName, int userId) { + try { + return createSessionInternal(params, installerPackageName, userId); + } catch (IOException e) { + throw ExceptionUtils.wrap(e); + } + } + + private int createSessionInternal(SessionParams params, String installerPackageName, int userId) + throws IOException { final int callingUid = Binder.getCallingUid(); mPm.enforceCrossUserPermission(callingUid, userId, true, "createSession"); @@ -393,14 +466,6 @@ public class PackageInstallerService extends IPackageInstaller.Stub { params.installFlags |= INSTALL_REPLACE_EXISTING; } - switch (params.mode) { - case SessionParams.MODE_FULL_INSTALL: - case SessionParams.MODE_INHERIT_EXISTING: - break; - default: - throw new IllegalArgumentException("Params must have valid mode set"); - } - // Defensively resize giant app icons if (params.appIcon != null) { final ActivityManager am = (ActivityManager) mContext.getSystemService( @@ -413,13 +478,41 @@ public class PackageInstallerService extends IPackageInstaller.Stub { } } - // Sanity check that install could fit - if (params.sizeBytes > 0) { - try { - mPm.freeStorage(params.sizeBytes); - } catch (IOException e) { - throw ExceptionUtils.wrap(e); + // Figure out where we're going to be staging session data + final boolean stageInternal; + + if (params.mode == SessionParams.MODE_FULL_INSTALL) { + // Brand new install, use best resolved location. This also verifies + // that target has enough free space for the install. + final int resolved = PackageHelper.resolveInstallLocation(mContext, + params.installLocation, params.sizeBytes, params.installFlags); + if (resolved == PackageHelper.RECOMMEND_INSTALL_INTERNAL) { + stageInternal = true; + } else if (resolved == PackageHelper.RECOMMEND_INSTALL_EXTERNAL) { + stageInternal = false; + } else { + throw new IOException("No storage with enough free space; res=" + resolved); } + + } else if (params.mode == SessionParams.MODE_INHERIT_EXISTING) { + // We always stage inheriting sessions on internal storage first, + // since we don't want to grow containers until we're sure that + // everything looks legit. + stageInternal = true; + checkInternalStorage(params.sizeBytes); + + // If we have a good hunch we'll end up on external storage, verify + // free space there too. + final ApplicationInfo info = mPm.getApplicationInfo(params.appPackageName, 0, + userId); + if (info != null && (info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) { + checkExternalStorage(params.sizeBytes); + + throw new UnsupportedOperationException("TODO: finish fleshing out ASEC support"); + } + + } else { + throw new IllegalArgumentException("Invalid install mode: " + params.mode); } final int sessionId; @@ -437,14 +530,21 @@ public class PackageInstallerService extends IPackageInstaller.Stub { "Too many historical sessions for UID " + callingUid); } + final long createdMillis = System.currentTimeMillis(); sessionId = allocateSessionIdLocked(); - final long createdMillis = System.currentTimeMillis(); - final File sessionStageDir = prepareSessionStageDir(sessionId); + // We're staging to exactly one location + File stageDir = null; + String stageCid = null; + if (stageInternal) { + stageDir = prepareInternalStageDir(sessionId); + } else { + stageCid = prepareExternalStageCid(sessionId, params.sizeBytes); + } session = new PackageInstallerSession(mInternalCallback, mContext, mPm, mInstallThread.getLooper(), sessionId, userId, installerPackageName, params, - createdMillis, sessionStageDir, false); + createdMillis, stageDir, stageCid, false); mSessions.put(sessionId, session); } @@ -453,6 +553,29 @@ public class PackageInstallerService extends IPackageInstaller.Stub { return sessionId; } + private void checkInternalStorage(long sizeBytes) throws IOException { + if (sizeBytes <= 0) return; + + final File target = Environment.getDataDirectory(); + final long targetBytes = sizeBytes + mStorage.getStorageLowBytes(target); + + mPm.freeStorage(targetBytes); + if (target.getUsableSpace() < targetBytes) { + throw new IOException("Not enough internal space to write " + sizeBytes + " bytes"); + } + } + + private void checkExternalStorage(long sizeBytes) throws IOException { + if (sizeBytes <= 0) return; + + final File target = Environment.getExternalStorageDirectory(); + final long targetBytes = sizeBytes + mStorage.getStorageLowBytes(target); + + if (target.getUsableSpace() < targetBytes) { + throw new IOException("Not enough external space to write " + sizeBytes + " bytes"); + } + } + @Override public IPackageInstallerSession openSession(int sessionId) { synchronized (mSessions) { @@ -463,9 +586,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub { if (!isCallingUidOwner(session)) { throw new SecurityException("Caller has no access to session " + sessionId); } - if (session.openCount.getAndIncrement() == 0) { - mCallbacks.notifySessionOpened(sessionId, session.userId); - } + session.open(); return session; } } @@ -475,7 +596,8 @@ public class PackageInstallerService extends IPackageInstaller.Stub { int sessionId; do { sessionId = mRandom.nextInt(Integer.MAX_VALUE - 1) + 1; - if (mSessions.get(sessionId) == null && mHistoricalSessions.get(sessionId) == null) { + if (mSessions.get(sessionId) == null && mHistoricalSessions.get(sessionId) == null + && !mLegacySessions.get(sessionId, false)) { return sessionId; } } while (n++ < 32); @@ -483,11 +605,11 @@ public class PackageInstallerService extends IPackageInstaller.Stub { throw new IllegalStateException("Failed to allocate session ID"); } - private File prepareSessionStageDir(int sessionId) { + private File prepareInternalStageDir(int sessionId) throws IOException { final File file = new File(mStagingDir, "vmdl" + sessionId + ".tmp"); if (file.exists()) { - throw new IllegalStateException("Session dir already exists: " + file); + throw new IOException("Session dir already exists: " + file); } try { @@ -495,16 +617,34 @@ public class PackageInstallerService extends IPackageInstaller.Stub { Os.chmod(file.getAbsolutePath(), 0755); } catch (ErrnoException e) { // This purposefully throws if directory already exists - throw new IllegalStateException("Failed to prepare session dir", e); + throw new IOException("Failed to prepare session dir", e); } if (!SELinux.restorecon(file)) { - throw new IllegalStateException("Failed to restorecon session dir"); + throw new IOException("Failed to restorecon session dir"); } return file; } + private String prepareExternalStageCid(int sessionId, long sizeBytes) throws IOException { + if (sizeBytes <= 0) { + throw new IOException("Session must provide valid size for ASEC"); + } + + final String cid = "smdl" + sessionId + ".tmp"; + + // Round up to nearest MB, plus another MB for filesystem overhead + final int sizeMb = (int) ((sizeBytes + MB_IN_BYTES) / MB_IN_BYTES) + 1; + + if (PackageHelper.createSdDir(sizeMb, cid, PackageManagerService.getEncryptKey(), + Process.SYSTEM_UID, true) == null) { + throw new IOException("Failed to create ASEC"); + } + + return cid; + } + @Override public SessionInfo getSessionInfo(int sessionId) { synchronized (mSessions) { @@ -643,7 +783,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub { public void onUserActionRequired(Intent intent) { final Intent fillIn = new Intent(); fillIn.putExtra(PackageInstaller.EXTRA_STATUS, - PackageInstaller.STATUS_USER_ACTION_REQUIRED); + PackageInstaller.STATUS_PENDING_USER_ACTION); fillIn.putExtra(Intent.EXTRA_INTENT, intent); try { mTarget.sendIntent(mContext, 0, fillIn, null, null); @@ -679,7 +819,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub { public void onUserActionRequired(Intent intent) { final Intent fillIn = new Intent(); fillIn.putExtra(PackageInstaller.EXTRA_STATUS, - PackageInstaller.STATUS_USER_ACTION_REQUIRED); + PackageInstaller.STATUS_PENDING_USER_ACTION); fillIn.putExtra(Intent.EXTRA_INTENT, intent); try { mTarget.sendIntent(mContext, 0, fillIn, null, null); @@ -817,6 +957,11 @@ public class PackageInstallerService extends IPackageInstaller.Stub { } pw.println(); pw.decreaseIndent(); + + pw.println("Legacy install sessions:"); + pw.increaseIndent(); + pw.println(mLegacySessions.toString()); + pw.decreaseIndent(); } } @@ -825,6 +970,10 @@ public class PackageInstallerService extends IPackageInstaller.Stub { mCallbacks.notifySessionProgressChanged(session.sessionId, session.userId, progress); } + public void onSessionOpened(PackageInstallerSession session) { + mCallbacks.notifySessionOpened(session.sessionId, session.userId); + } + public void onSessionClosed(PackageInstallerSession session) { mCallbacks.notifySessionClosed(session.sessionId, session.userId); } diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 5ef24f2..0616460 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -18,6 +18,7 @@ package com.android.server.pm; import static android.content.pm.PackageManager.INSTALL_FAILED_ABORTED; import static android.content.pm.PackageManager.INSTALL_FAILED_ALREADY_EXISTS; +import static android.content.pm.PackageManager.INSTALL_FAILED_CONTAINER_ERROR; import static android.content.pm.PackageManager.INSTALL_FAILED_INTERNAL_ERROR; import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK; import static android.content.pm.PackageManager.INSTALL_FAILED_PACKAGE_CHANGED; @@ -58,6 +59,7 @@ import android.util.MathUtils; import android.util.Slog; import com.android.internal.annotations.GuardedBy; +import com.android.internal.content.PackageHelper; import com.android.internal.util.ArrayUtils; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; @@ -79,7 +81,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { // TODO: enforce INSTALL_ALLOW_TEST // TODO: enforce INSTALL_ALLOW_DOWNGRADE - // TODO: handle INSTALL_EXTERNAL, INSTALL_INTERNAL // TODO: treat INHERIT_EXISTING as installExistingPackage() @@ -93,12 +94,24 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { final String installerPackageName; final SessionParams params; final long createdMillis; - final File sessionStageDir; + + /** Internal location where staged data is written. */ + final File internalStageDir; + /** External container where staged data is written. */ + final String externalStageCid; + + /** + * When a {@link SessionParams#MODE_INHERIT_EXISTING} session is installed + * into an ASEC, this is the container where the stage is combined with the + * existing install. + */ + // TODO: persist this cid once we start splicing + String combinedCid; /** Note that UID is not persisted; it's always derived at runtime. */ final int installerUid; - AtomicInteger openCount = new AtomicInteger(); + private final AtomicInteger mOpenCount = new AtomicInteger(); private final Object mLock = new Object(); @@ -119,6 +132,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { private int mFinalStatus; private String mFinalMessage; + @GuardedBy("mLock") + private File mResolvedStageDir; + /** * Path to the resolved base APK for this session, which may point at an APK * inside the session (when the session defines the base), or it may point @@ -165,7 +181,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { public PackageInstallerSession(PackageInstallerService.InternalCallback callback, Context context, PackageManagerService pm, Looper looper, int sessionId, int userId, String installerPackageName, SessionParams params, long createdMillis, - File sessionStageDir, boolean sealed) { + File internalStageDir, String externalStageCid, boolean sealed) { mCallback = callback; mContext = context; mPm = pm; @@ -176,7 +192,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { this.installerPackageName = installerPackageName; this.params = params; this.createdMillis = createdMillis; - this.sessionStageDir = sessionStageDir; + this.internalStageDir = internalStageDir; + this.externalStageCid = externalStageCid; mSealed = sealed; @@ -195,23 +212,29 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { public SessionInfo generateInfo() { final SessionInfo info = new SessionInfo(); - - info.sessionId = sessionId; - info.installerPackageName = installerPackageName; - info.resolvedBaseCodePath = mResolvedBaseCodePath; - info.progress = mProgress; - info.sealed = mSealed; - info.open = openCount.get() > 0; - - info.mode = params.mode; - info.sizeBytes = params.sizeBytes; - info.appPackageName = params.appPackageName; - info.appIcon = params.appIcon; - info.appLabel = params.appLabel; - + synchronized (mLock) { + info.sessionId = sessionId; + info.installerPackageName = installerPackageName; + info.resolvedBaseCodePath = mResolvedBaseCodePath; + info.progress = mProgress; + info.sealed = mSealed; + info.open = mOpenCount.get() > 0; + + info.mode = params.mode; + info.sizeBytes = params.sizeBytes; + info.appPackageName = params.appPackageName; + info.appIcon = params.appIcon; + info.appLabel = params.appLabel; + } return info; } + public boolean isSealed() { + synchronized (mLock) { + return mSealed; + } + } + private void assertNotSealed(String cookie) { synchronized (mLock) { if (mSealed) { @@ -220,6 +243,30 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } + /** + * Resolve the actual location where staged data should be written. This + * might point at an ASEC mount point, which is why we delay path resolution + * until someone actively works with the session. + */ + private File getStageDir() throws IOException { + synchronized (mLock) { + if (mResolvedStageDir == null) { + if (internalStageDir != null) { + mResolvedStageDir = internalStageDir; + } else { + final String path = PackageHelper.getSdDir(externalStageCid); + if (path != null) { + mResolvedStageDir = new File(path); + } else { + throw new IOException( + "Failed to resolve container path for " + externalStageCid); + } + } + } + return mResolvedStageDir; + } + } + @Override public void setClientProgress(float progress) { synchronized (mLock) { @@ -253,7 +300,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { @Override public String[] getNames() { assertNotSealed("getNames"); - return sessionStageDir.list(); + try { + return getStageDir().list(); + } catch (IOException e) { + throw ExceptionUtils.wrap(e); + } } @Override @@ -267,8 +318,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { private ParcelFileDescriptor openWriteInternal(String name, long offsetBytes, long lengthBytes) throws IOException { - // TODO: relay over to DCS when installing to ASEC - // Quick sanity check of state, and allocate a pipe for ourselves. We // then do heavy disk allocation outside the lock, but this open pipe // will block any attempted install transitions. @@ -285,7 +334,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { if (!FileUtils.isValidExtFilename(name)) { throw new IllegalArgumentException("Invalid name: " + name); } - final File target = new File(sessionStageDir, name); + final File target = new File(getStageDir(), name); final FileDescriptor targetFd = Libcore.os.open(target.getAbsolutePath(), O_CREAT | O_WRONLY, 0644); @@ -331,7 +380,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { if (!FileUtils.isValidExtFilename(name)) { throw new IllegalArgumentException("Invalid name: " + name); } - final File target = new File(sessionStageDir, name); + final File target = new File(getStageDir(), name); final FileDescriptor targetFd = Libcore.os.open(target.getAbsolutePath(), O_RDONLY, 0); return new ParcelFileDescriptor(targetFd); @@ -369,10 +418,18 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { // beyond this point we may have hardlinks to the valid install } + final File stageDir; + try { + stageDir = getStageDir(); + } catch (IOException e) { + throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR, + "Failed to resolve stage dir", e); + } + // Verify that stage looks sane with respect to existing application. // This currently only ensures packageName, versionCode, and certificate // consistency. - validateInstallLocked(); + validateInstallLocked(stageDir); Preconditions.checkNotNull(mPackageName); Preconditions.checkNotNull(mSignatures); @@ -394,7 +451,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { // Inherit any packages and native libraries from existing install that // haven't been overridden. if (params.mode == SessionParams.MODE_INHERIT_EXISTING) { - spliceExistingFilesIntoStage(); + // TODO: implement splicing into existing ASEC + spliceExistingFilesIntoStage(stageDir); } // TODO: surface more granular state from dexopt @@ -418,7 +476,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } }; - mPm.installStage(mPackageName, this.sessionStageDir, localObserver, params, + // TODO: send ASEC cid if that's where we staged things + mPm.installStage(mPackageName, this.internalStageDir, null, localObserver, params, installerPackageName, installerUid, new UserHandle(userId)); } @@ -428,13 +487,13 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { * <p> * Renames package files in stage to match split names defined inside. */ - private void validateInstallLocked() throws PackageManagerException { + private void validateInstallLocked(File stageDir) throws PackageManagerException { mPackageName = null; mVersionCode = -1; mSignatures = null; mResolvedBaseCodePath = null; - final File[] files = sessionStageDir.listFiles(); + final File[] files = stageDir.listFiles(); if (ArrayUtils.isEmpty(files)) { throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, "No packages staged"); } @@ -480,7 +539,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { "Invalid filename: " + targetName); } - final File targetFile = new File(sessionStageDir, targetName); + final File targetFile = new File(stageDir, targetName); if (!file.equals(targetFile)) { file.renameTo(targetFile); } @@ -550,7 +609,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { * Application is already installed; splice existing files that haven't been * overridden into our stage. */ - private void spliceExistingFilesIntoStage() throws PackageManagerException { + private void spliceExistingFilesIntoStage(File stageDir) throws PackageManagerException { final ApplicationInfo app = mPm.getApplicationInfo(mPackageName, 0, userId); int n = 0; @@ -559,7 +618,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { for (File oldFile : oldFiles) { if (!PackageParser.isApkFile(oldFile)) continue; - final File newFile = new File(sessionStageDir, oldFile.getName()); + final File newFile = new File(stageDir, oldFile.getName()); try { Os.link(oldFile.getAbsolutePath(), newFile.getAbsolutePath()); n++; @@ -588,9 +647,15 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } + public void open() { + if (mOpenCount.getAndIncrement() == 0) { + mCallback.onSessionOpened(this); + } + } + @Override public void close() { - if (openCount.decrementAndGet() == 0) { + if (mOpenCount.decrementAndGet() == 0) { mCallback.onSessionClosed(this); } } @@ -621,11 +686,22 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { mSealed = true; mDestroyed = true; } - FileUtils.deleteContents(sessionStageDir); - sessionStageDir.delete(); + if (internalStageDir != null) { + FileUtils.deleteContents(internalStageDir); + internalStageDir.delete(); + } + if (externalStageCid != null) { + PackageHelper.destroySdDir(externalStageCid); + } } void dump(IndentingPrintWriter pw) { + synchronized (mLock) { + dumpLocked(pw); + } + } + + private void dumpLocked(IndentingPrintWriter pw) { pw.println("Session " + sessionId + ":"); pw.increaseIndent(); @@ -633,7 +709,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { pw.printPair("installerPackageName", installerPackageName); pw.printPair("installerUid", installerUid); pw.printPair("createdMillis", createdMillis); - pw.printPair("sessionStageDir", sessionStageDir); + pw.printPair("internalStageDir", internalStageDir); + pw.printPair("externalStageCid", externalStageCid); pw.println(); params.dump(pw); @@ -650,18 +727,4 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { pw.decreaseIndent(); } - - Snapshot snapshot() { - return new Snapshot(this); - } - - static class Snapshot { - final float clientProgress; - final boolean sealed; - - public Snapshot(PackageInstallerSession session) { - clientProgress = session.mClientProgress; - sealed = session.mSealed; - } - } } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index b62c304..51559aa 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -140,6 +140,7 @@ import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.os.Environment.UserEnvironment; +import android.os.storage.StorageManager; import android.os.FileUtils; import android.os.Handler; import android.os.IBinder; @@ -211,6 +212,7 @@ import dalvik.system.StaleDexCacheError; import dalvik.system.VMRuntime; import libcore.io.IoUtils; +import libcore.util.EmptyArray; /** * Keep track of all those .apks everywhere. @@ -311,8 +313,6 @@ public class PackageManagerService extends IPackageManager.Stub { private static final String VENDOR_OVERLAY_DIR = "/vendor/overlay"; - static final String mTempContainerPrefix = "smdl2tmp"; - private static String sPreferredInstructionSet; final ServiceThread mHandlerThread; @@ -4101,21 +4101,24 @@ public class PackageManagerService extends IPackageManager.Stub { for (File file : files) { final boolean isPackage = (isApkFile(file) || file.isDirectory()) - && !PackageInstallerService.isStageFile(file); + && !PackageInstallerService.isStageName(file.getName()); if (!isPackage) { - // Ignore entries which are not apk's + // Ignore entries which are not packages continue; } try { - scanPackageLI(file, flags | PackageParser.PARSE_MUST_BE_APK, scanMode, currentTime, null); + scanPackageLI(file, flags | PackageParser.PARSE_MUST_BE_APK, + scanMode, currentTime, null); } catch (PackageManagerException e) { Slog.w(TAG, "Failed to parse " + file + ": " + e.getMessage()); - // Don't mess around with apps in system partition. + // Delete invalid userdata apps if ((flags & PackageParser.PARSE_IS_SYSTEM) == 0 && e.error == PackageManager.INSTALL_FAILED_INVALID_APK) { - // Delete the apk - Slog.w(TAG, "Cleaning up failed install of " + file); + Slog.w(TAG, "Deleting invalid package at " + file); + if (file.isDirectory()) { + FileUtils.deleteContents(file); + } file.delete(); } } @@ -7839,19 +7842,19 @@ public class PackageManagerService extends IPackageManager.Stub { verificationParams.setInstallerUid(uid); final Message msg = mHandler.obtainMessage(INIT_COPY); - msg.obj = new InstallParams(originFile, false, observer, filteredFlags, + msg.obj = new InstallParams(originFile, null, false, observer, filteredFlags, installerPackageName, verificationParams, user, packageAbiOverride); mHandler.sendMessage(msg); } - void installStage(String packageName, File stageDir, IPackageInstallObserver2 observer, - PackageInstaller.SessionParams params, String installerPackageName, int installerUid, - UserHandle user) { + void installStage(String packageName, File stagedDir, String stagedCid, + IPackageInstallObserver2 observer, PackageInstaller.SessionParams params, + String installerPackageName, int installerUid, UserHandle user) { final VerificationParams verifParams = new VerificationParams(null, params.originatingUri, params.referrerUri, installerUid, null); final Message msg = mHandler.obtainMessage(INIT_COPY); - msg.obj = new InstallParams(stageDir, true, observer, params.installFlags, + msg.obj = new InstallParams(stagedDir, stagedCid, true, observer, params.installFlags, installerPackageName, verifParams, user, params.abiOverride); mHandler.sendMessage(msg); } @@ -8551,10 +8554,12 @@ public class PackageManagerService extends IPackageManager.Stub { * file, or a cluster directory. This location may be untrusted. */ final File originFile; + final String originCid; /** - * Flag indicating that {@link #originFile} has already been staged, - * meaning downstream users don't need to defensively copy the contents. + * Flag indicating that {@link #originFile} or {@link #originCid} has + * already been staged, meaning downstream users don't need to + * defensively copy the contents. */ boolean originStaged; @@ -8567,11 +8572,12 @@ public class PackageManagerService extends IPackageManager.Stub { final String packageAbiOverride; boolean multiArch; - InstallParams(File originFile, boolean originStaged, IPackageInstallObserver2 observer, - int flags, String installerPackageName, VerificationParams verificationParams, - UserHandle user, String packageAbiOverride) { + InstallParams(File originFile, String originCid, boolean originStaged, + IPackageInstallObserver2 observer, int flags, String installerPackageName, + VerificationParams verificationParams, UserHandle user, String packageAbiOverride) { super(user); - this.originFile = Preconditions.checkNotNull(originFile); + this.originFile = originFile; + this.originCid = originCid; this.originStaged = originStaged; this.observer = observer; this.flags = flags; @@ -8582,9 +8588,8 @@ public class PackageManagerService extends IPackageManager.Stub { @Override public String toString() { - return "InstallParams{" - + Integer.toHexString(System.identityHashCode(this)) - + " " + originFile + "}"; + return "InstallParams{" + Integer.toHexString(System.identityHashCode(this)) + + " file=" + originFile + " cid=" + originCid + "}"; } public ManifestDigest getManifestDigest() { @@ -8653,15 +8658,6 @@ public class PackageManagerService extends IPackageManager.Stub { return pkgLite.recommendedInstallLocation; } - private long getMemoryLowThreshold() { - final DeviceStorageMonitorInternal - dsm = LocalServices.getService(DeviceStorageMonitorInternal.class); - if (dsm == null) { - return 0L; - } - return dsm.getMemoryLowThreshold(); - } - /* * Invoke remote method to get package information and install * location values. Override install location based on default @@ -8679,14 +8675,9 @@ public class PackageManagerService extends IPackageManager.Stub { Slog.w(TAG, "Conflicting flags specified for installing on both internal and external"); ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION; } else { - final long lowThreshold = getMemoryLowThreshold(); - if (lowThreshold == 0L) { - Log.w(TAG, "Couldn't get low memory threshold; no free limit imposed"); - } - // Remote call to find out default install location final String originPath = originFile.getAbsolutePath(); - pkgLite = mContainerService.getMinimalPackageInfo(originPath, flags, lowThreshold, + pkgLite = mContainerService.getMinimalPackageInfo(originPath, flags, packageAbiOverride); // Keep track of whether this package is a multiArch package until // we perform a full scan of it. We need to do this because we might @@ -8700,12 +8691,19 @@ public class PackageManagerService extends IPackageManager.Stub { */ if (pkgLite.recommendedInstallLocation == PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) { - final long size = mContainerService.calculateInstalledSize( + // TODO: focus freeing disk space on the target device + final StorageManager storage = StorageManager.from(mContext); + final long lowThreshold = storage.getStorageLowBytes( + Environment.getDataDirectory()); + + final long sizeBytes = mContainerService.calculateInstalledSize( originPath, isForwardLocked(), packageAbiOverride); - if (mInstaller.freeCache(size + lowThreshold) >= 0) { + + if (mInstaller.freeCache(sizeBytes + lowThreshold) >= 0) { pkgLite = mContainerService.getMinimalPackageInfo(originPath, flags, - lowThreshold, packageAbiOverride); + packageAbiOverride); } + /* * The cache free must have deleted the file we * downloaded to install. @@ -9235,24 +9233,11 @@ public class PackageManagerService extends IPackageManager.Stub { } boolean checkFreeStorage(IMediaContainerService imcs) throws RemoteException { - final long lowThreshold; - - final DeviceStorageMonitorInternal - dsm = LocalServices.getService(DeviceStorageMonitorInternal.class); - if (dsm == null) { - Log.w(TAG, "Couldn't get low memory threshold; no free limit imposed"); - lowThreshold = 0L; - } else { - if (dsm.isMemoryLow()) { - Log.w(TAG, "Memory is reported as being too low; aborting package install"); - return false; - } + final long sizeBytes = imcs.calculateInstalledSize(originFile.getAbsolutePath(), + isFwdLocked(), abiOverride); - lowThreshold = dsm.getMemoryLowThreshold(); - } - - return imcs.checkInternalFreeStorage(originFile.getAbsolutePath(), isFwdLocked(), - lowThreshold); + final StorageManager storage = StorageManager.from(mContext); + return (sizeBytes <= storage.getStorageBytesUntilLow(Environment.getDataDirectory())); } int copyApk(IMediaContainerService imcs, boolean temp) throws RemoteException { @@ -9264,7 +9249,7 @@ public class PackageManagerService extends IPackageManager.Stub { resourceFile = originFile; } else { try { - final File tempDir = mInstallerService.allocateSessionDir(); + final File tempDir = mInstallerService.allocateInternalStageDirLegacy(); codeFile = tempDir; resourceFile = tempDir; } catch (IOException e) { @@ -9569,12 +9554,22 @@ public class PackageManagerService extends IPackageManager.Stub { } void createCopyFile() { - cid = getTempContainerId(); + cid = mInstallerService.allocateExternalStageCidLegacy(); } boolean checkFreeStorage(IMediaContainerService imcs) throws RemoteException { - return imcs.checkExternalFreeStorage(originFile.getAbsolutePath(), isFwdLocked(), + final long sizeBytes = imcs.calculateInstalledSize(packagePath, isFwdLocked(), abiOverride); + + final File target; + if (isExternal()) { + target = Environment.getExternalStorageDirectory(); + } else { + target = Environment.getDataDirectory(); + } + + final StorageManager storage = StorageManager.from(mContext); + return (sizeBytes <= storage.getStorageBytesUntilLow(target)); } private final boolean isExternal() { @@ -12653,7 +12648,7 @@ public class PackageManagerService extends IPackageManager.Stub { private boolean mMediaMounted = false; - private String getEncryptKey() { + static String getEncryptKey() { try { String sdEncKey = SystemKeyStore.getInstance().retrieveKeyHexString( SD_ENCRYPTION_KEYSTORE_NAME); @@ -12673,30 +12668,6 @@ public class PackageManagerService extends IPackageManager.Stub { Slog.e(TAG, "Failed to retrieve encryption keys with exception: " + ioe); return null; } - - } - - /* package */static String getTempContainerId() { - int tmpIdx = 1; - String list[] = PackageHelper.getSecureContainerList(); - if (list != null) { - for (final String name : list) { - // Ignore null and non-temporary container entries - if (name == null || !name.startsWith(mTempContainerPrefix)) { - continue; - } - - String subStr = name.substring(mTempContainerPrefix.length()); - try { - int cid = Integer.parseInt(subStr); - if (cid >= tmpIdx) { - tmpIdx = cid + 1; - } - } catch (NumberFormatException e) { - } - } - } - return mTempContainerPrefix + tmpIdx; } /* @@ -12754,31 +12725,27 @@ public class PackageManagerService extends IPackageManager.Stub { */ private void updateExternalMediaStatusInner(boolean isMounted, boolean reportStatus, boolean externalStorage) { - // Collection of uids - int uidArr[] = null; - // Collection of stale containers - HashSet<String> removeCids = new HashSet<String>(); - // Collection of packages on external media with valid containers. - HashMap<AsecInstallArgs, String> processCids = new HashMap<AsecInstallArgs, String>(); - // Get list of secure containers. - final String list[] = PackageHelper.getSecureContainerList(); - if (list == null || list.length == 0) { - Log.i(TAG, "No secure containers on sdcard"); + ArrayMap<AsecInstallArgs, String> processCids = new ArrayMap<>(); + int[] uidArr = EmptyArray.INT; + + final String[] list = PackageHelper.getSecureContainerList(); + if (ArrayUtils.isEmpty(list)) { + Log.i(TAG, "No secure containers found"); } else { // Process list of secure containers and categorize them // as active or stale based on their package internal state. - int uidList[] = new int[list.length]; - int num = 0; + // reader synchronized (mPackages) { for (String cid : list) { + // Leave stages untouched for now; installer service owns them + if (PackageInstallerService.isStageName(cid)) continue; + if (DEBUG_SD_INSTALL) Log.i(TAG, "Processing container " + cid); String pkgName = getAsecPackageName(cid); if (pkgName == null) { - if (DEBUG_SD_INSTALL) - Log.i(TAG, "Container : " + cid + " stale"); - removeCids.add(cid); + Slog.i(TAG, "Found stale container " + cid + " with no package name"); continue; } if (DEBUG_SD_INSTALL) @@ -12786,8 +12753,7 @@ public class PackageManagerService extends IPackageManager.Stub { final PackageSetting ps = mSettings.mPackages.get(pkgName); if (ps == null) { - Log.i(TAG, "Deleting container with no matching settings " + cid); - removeCids.add(cid); + Slog.i(TAG, "Found stale container " + cid + " with no matching settings"); continue; } @@ -12813,35 +12779,25 @@ public class PackageManagerService extends IPackageManager.Stub { processCids.put(args, ps.codePathString); final int uid = ps.appId; if (uid != -1) { - uidList[num++] = uid; + uidArr = ArrayUtils.appendInt(uidArr, uid); } } else { - Log.i(TAG, "Deleting stale container for " + cid); - removeCids.add(cid); + Slog.i(TAG, "Found stale container " + cid + ": expected codePath=" + + ps.codePathString); } } } - if (num > 0) { - // Sort uid list - Arrays.sort(uidList, 0, num); - // Throw away duplicates - uidArr = new int[num]; - uidArr[0] = uidList[0]; - int di = 0; - for (int i = 1; i < num; i++) { - if (uidList[i - 1] != uidList[i]) { - uidArr[di++] = uidList[i]; - } - } - } + Arrays.sort(uidArr); } + // Process packages with valid entries. if (isMounted) { if (DEBUG_SD_INSTALL) Log.i(TAG, "Loading packages"); - loadMediaPackages(processCids, uidArr, removeCids); + loadMediaPackages(processCids, uidArr); startCleaningPackages(); + mInstallerService.onSecureContainersAvailable(); } else { if (DEBUG_SD_INSTALL) Log.i(TAG, "Unloading packages"); @@ -12849,8 +12805,8 @@ public class PackageManagerService extends IPackageManager.Stub { } } - private void sendResourcesChangedBroadcast(boolean mediaStatus, boolean replacing, - ArrayList<String> pkgList, int uidArr[], IIntentReceiver finishedReceiver) { + private void sendResourcesChangedBroadcast(boolean mediaStatus, boolean replacing, + ArrayList<String> pkgList, int uidArr[], IIntentReceiver finishedReceiver) { int size = pkgList.size(); if (size > 0) { // Send broadcasts here @@ -12875,11 +12831,10 @@ public class PackageManagerService extends IPackageManager.Stub { * the cid is added to list of removeCids. We currently don't delete stale * containers. */ - private void loadMediaPackages(HashMap<AsecInstallArgs, String> processCids, int uidArr[], - HashSet<String> removeCids) { + private void loadMediaPackages(ArrayMap<AsecInstallArgs, String> processCids, int[] uidArr) { ArrayList<String> pkgList = new ArrayList<String>(); Set<AsecInstallArgs> keys = processCids.keySet(); - boolean doGc = false; + for (AsecInstallArgs args : keys) { String codePath = processCids.get(args); if (DEBUG_SD_INSTALL) @@ -12907,7 +12862,6 @@ public class PackageManagerService extends IPackageManager.Stub { parseFlags |= PackageParser.PARSE_FORWARD_LOCK; } - doGc = true; synchronized (mInstallLock) { PackageParser.Package pkg = null; try { @@ -12937,9 +12891,7 @@ public class PackageManagerService extends IPackageManager.Stub { } finally { if (retCode != PackageManager.INSTALL_SUCCEEDED) { - // Don't destroy container here. Wait till gc clears things - // up. - removeCids.add(args.cid); + Log.w(TAG, "Container " + args.cid + " is stale, retCode=" + retCode); } } } @@ -12974,21 +12926,6 @@ public class PackageManagerService extends IPackageManager.Stub { if (pkgList.size() > 0) { sendResourcesChangedBroadcast(true, false, pkgList, uidArr, null); } - // Force gc to avoid any stale parser references that we might have. - if (doGc) { - Runtime.getRuntime().gc(); - } - // List stale containers and destroy stale temporary containers. - if (removeCids != null) { - for (String cid : removeCids) { - if (cid.startsWith(mTempContainerPrefix)) { - Log.i(TAG, "Destroying stale temporary container " + cid); - PackageHelper.destroySdDir(cid); - } else { - Log.w(TAG, "Container " + cid + " is stale"); - } - } - } } /* @@ -13012,7 +12949,7 @@ public class PackageManagerService extends IPackageManager.Stub { * that we always have to post this message if status has been requested no * matter what. */ - private void unloadMediaPackages(HashMap<AsecInstallArgs, String> processCids, int uidArr[], + private void unloadMediaPackages(ArrayMap<AsecInstallArgs, String> processCids, int uidArr[], final boolean reportStatus) { if (DEBUG_SD_INSTALL) Log.i(TAG, "unloading media packages"); diff --git a/services/core/java/com/android/server/tv/TvInputHal.java b/services/core/java/com/android/server/tv/TvInputHal.java index e7dd82d..c7d95aa 100644 --- a/services/core/java/com/android/server/tv/TvInputHal.java +++ b/services/core/java/com/android/server/tv/TvInputHal.java @@ -19,12 +19,11 @@ package com.android.server.tv; import android.media.tv.TvInputHardwareInfo; import android.media.tv.TvStreamConfig; import android.os.Handler; -import android.os.HandlerThread; import android.os.Message; -import android.view.Surface; import android.util.Slog; import android.util.SparseArray; import android.util.SparseIntArray; +import android.view.Surface; import java.util.LinkedList; import java.util.Queue; @@ -64,12 +63,12 @@ final class TvInputHal implements Handler.Callback { int generation); private static native void nativeClose(long ptr); - private Object mLock = new Object(); + private final Object mLock = new Object(); private long mPtr = 0; private final Callback mCallback; private final Handler mHandler; - private SparseIntArray mStreamConfigGenerations = new SparseIntArray(); - private SparseArray<TvStreamConfig[]> mStreamConfigs = new SparseArray<>();; + private final SparseIntArray mStreamConfigGenerations = new SparseIntArray(); + private final SparseArray<TvStreamConfig[]> mStreamConfigs = new SparseArray<>(); public TvInputHal(Callback callback) { mCallback = callback; @@ -153,7 +152,7 @@ final class TvInputHal implements Handler.Callback { // Handler.Callback implementation - private Queue<Message> mPendingMessageQueue = new LinkedList<Message>(); + private final Queue<Message> mPendingMessageQueue = new LinkedList<Message>(); @Override public boolean handleMessage(Message msg) { diff --git a/services/core/java/com/android/server/tv/TvInputHardwareManager.java b/services/core/java/com/android/server/tv/TvInputHardwareManager.java index ae9ae13..425eff3 100644 --- a/services/core/java/com/android/server/tv/TvInputHardwareManager.java +++ b/services/core/java/com/android/server/tv/TvInputHardwareManager.java @@ -20,14 +20,12 @@ import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED; import static android.media.tv.TvInputManager.INPUT_STATE_DISCONNECTED; import android.content.Context; -import android.content.Intent; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.HdmiHotplugEvent; import android.hardware.hdmi.IHdmiControlService; import android.hardware.hdmi.IHdmiDeviceEventListener; import android.hardware.hdmi.IHdmiHotplugEventListener; -import android.hardware.hdmi.IHdmiInputChangeListener; import android.hardware.hdmi.IHdmiSystemAudioModeChangeListener; import android.media.AudioDevicePort; import android.media.AudioFormat; @@ -39,7 +37,6 @@ import android.media.AudioPort; import android.media.AudioPortConfig; import android.media.tv.ITvInputHardware; import android.media.tv.ITvInputHardwareCallback; -import android.media.tv.TvContract; import android.media.tv.TvInputHardwareInfo; import android.media.tv.TvInputInfo; import android.media.tv.TvStreamConfig; @@ -77,7 +74,6 @@ import java.util.Map; class TvInputHardwareManager implements TvInputHal.Callback { private static final String TAG = TvInputHardwareManager.class.getSimpleName(); - private final Context mContext; private final Listener mListener; private final TvInputHal mHal = new TvInputHal(this); private final SparseArray<Connection> mConnections = new SparseArray<>(); @@ -107,7 +103,6 @@ class TvInputHardwareManager implements TvInputHal.Callback { private final Object mLock = new Object(); public TvInputHardwareManager(Context context, Listener listener) { - mContext = context; mListener = listener; mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); mHal.init(); diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 112972f..aa20892 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -428,7 +428,7 @@ public class WindowManagerService extends IWindowManager.Stub /** * Stores for each user whether screencapture is disabled * This array is essentially a cache for all userId for - * {@link android.app.admin.DevicePolicyManager#getScreenCaptureDisabled(null, userId)} + * {@link android.app.admin.DevicePolicyManager#getScreenCaptureDisabled} */ SparseArray<Boolean> mScreenCaptureDisabled = new SparseArray<Boolean>(); @@ -2315,9 +2315,12 @@ public class WindowManagerService extends IWindowManager.Stub return WindowManagerGlobal.ADD_BAD_APP_TOKEN; } } else if (token.appWindowToken != null) { - Slog.i(TAG, "Non-null appWindowToken for system window of type=" + type); - // app token should be null for any other window types. - token.appWindowToken = null; + Slog.w(TAG, "Non-null appWindowToken for system window of type=" + type); + // It is not valid to use an app token with other system types; we will + // instead make a new token for it (as if null had been passed in for the token). + attrs.token = null; + token = new WindowToken(this, null, -1, false); + addToken = true; } win = new WindowState(this, session, client, token, diff --git a/tests/ActivityTests/AndroidManifest.xml b/tests/ActivityTests/AndroidManifest.xml index 15d075c..c0898d3 100644 --- a/tests/ActivityTests/AndroidManifest.xml +++ b/tests/ActivityTests/AndroidManifest.xml @@ -23,6 +23,7 @@ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" /> <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" /> <uses-permission android:name="android.permission.MANAGE_USERS" /> + <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> <application android:label="ActivityTest"> <activity android:name="ActivityTestMain"> <intent-filter> diff --git a/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java b/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java index 0e063d6..9002125 100644 --- a/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java +++ b/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java @@ -34,6 +34,7 @@ import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; import android.graphics.Bitmap; +import android.view.WindowManager; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; @@ -130,6 +131,12 @@ public class ActivityTestMain extends Activity { mSecondUser = ui.id; } } + + /* + AlertDialog ad = new AlertDialog.Builder(this).setTitle("title").setMessage("message").create(); + ad.getWindow().getAttributes().type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR; + ad.show(); + */ } @Override diff --git a/tools/aapt/Android.mk b/tools/aapt/Android.mk index 6a5d06e..700afa1 100644 --- a/tools/aapt/Android.mk +++ b/tools/aapt/Android.mk @@ -91,7 +91,6 @@ LOCAL_C_INCLUDES += $(aaptCIncludes) LOCAL_CFLAGS += -Wno-format-y2k LOCAL_CFLAGS += -DSTATIC_ANDROIDFW_FOR_TOOLS -LOCAL_CFLAGS += -DAAPT_VERSION=\"$(BUILD_NUMBER)\" ifeq (darwin,$(HOST_OS)) LOCAL_CFLAGS += -D_DARWIN_UNLIMITED_STREAMS endif diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp index fa2bacd..5d146d6 100644 --- a/tools/aapt/Command.cpp +++ b/tools/aapt/Command.cpp @@ -32,7 +32,7 @@ int doVersion(Bundle* bundle) if (bundle->getFileSpecCount() != 0) { printf("(ignoring extra arguments)\n"); } - printf("Android Asset Packaging Tool, v0.2-" AAPT_VERSION "\n"); + printf("Android Asset Packaging Tool, v0.2\n"); return 0; } |