diff options
86 files changed, 3029 insertions, 1173 deletions
diff --git a/Android.mk b/Android.mk index e3d343380961..30cb9c650f90 100644 --- a/Android.mk +++ b/Android.mk @@ -131,7 +131,7 @@ LOCAL_SRC_FILES += \ core/java/android/content/pm/IPackageInstallObserver.aidl \ core/java/android/content/pm/IPackageInstallObserver2.aidl \ core/java/android/content/pm/IPackageInstaller.aidl \ - core/java/android/content/pm/IPackageInstallerObserver.aidl \ + core/java/android/content/pm/IPackageInstallerCallback.aidl \ core/java/android/content/pm/IPackageInstallerSession.aidl \ core/java/android/content/pm/IPackageManager.aidl \ core/java/android/content/pm/IPackageMoveObserver.aidl \ diff --git a/CleanSpec.mk b/CleanSpec.mk index 3014e0f4285d..abec4c8429a8 100644 --- a/CleanSpec.mk +++ b/CleanSpec.mk @@ -212,6 +212,7 @@ $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framew $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework-base_intermediates/src/telecomm/java/com/android/internal/telecomm) $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/framework.* $(PRODUCT_OUT)/system/framework2.*) +$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework-base_intermediates) # ****************************************************************** # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST ABOVE THIS BANNER diff --git a/api/current.txt b/api/current.txt index d482260e3807..a94d10bf32ca 100644 --- a/api/current.txt +++ b/api/current.txt @@ -8454,32 +8454,31 @@ package android.content.pm { public class InstallSessionInfo implements android.os.Parcelable { method public int describeContents(); - method public android.graphics.Bitmap getIcon(); + method public android.graphics.Bitmap getAppIcon(); + method public java.lang.CharSequence getAppLabel(); + method public java.lang.String getAppPackageName(); method public java.lang.String getInstallerPackageName(); - method public java.lang.String getPackageName(); - method public int getProgress(); + method public float getProgress(); method public int getSessionId(); - method public java.lang.CharSequence getTitle(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; } public class InstallSessionParams implements android.os.Parcelable { - ctor public InstallSessionParams(); + ctor public InstallSessionParams(int); method public int describeContents(); - method public void setDeltaSize(long); - method public void setIcon(android.graphics.Bitmap); + method public void setAppIcon(android.graphics.Bitmap); + method public void setAppLabel(java.lang.CharSequence); + method public void setAppPackageName(java.lang.String); method public void setInstallLocation(int); - method public void setModeFullInstall(); - method public void setModeInheritExisting(); method public void setOriginatingUri(android.net.Uri); - method public void setPackageName(java.lang.String); - method public void setProgressMax(int); method public void setReferrerUri(android.net.Uri); method public void setSignatures(android.content.pm.Signature[]); - method public void setTitle(java.lang.CharSequence); + method public void setSize(long); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; + field public static final int MODE_FULL_INSTALL = 1; // 0x1 + field public static final int MODE_INHERIT_EXISTING = 2; // 0x2 } public class InstrumentationInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable { @@ -8580,42 +8579,47 @@ package android.content.pm { } public class PackageInstaller { + method public void addSessionCallback(android.content.pm.PackageInstaller.SessionCallback); + method public void addSessionCallback(android.content.pm.PackageInstaller.SessionCallback, android.os.Handler); method public int createSession(android.content.pm.InstallSessionParams) throws java.io.IOException; - method public java.util.List<android.content.pm.InstallSessionInfo> getActiveSessions(); + method public java.util.List<android.content.pm.InstallSessionInfo> getAllSessions(); + method public java.util.List<android.content.pm.InstallSessionInfo> getMySessions(); + method public android.content.pm.InstallSessionInfo getSessionInfo(int); method public android.content.pm.PackageInstaller.Session openSession(int); - method public void registerSessionObserver(android.content.pm.PackageInstaller.SessionObserver); - method public void uninstall(java.lang.String, android.content.pm.PackageInstaller.UninstallResultCallback); - method public void unregisterSessionObserver(android.content.pm.PackageInstaller.SessionObserver); + method public void removeSessionCallback(android.content.pm.PackageInstaller.SessionCallback); + method public void uninstall(java.lang.String, android.content.pm.PackageInstaller.UninstallCallback); } - public static abstract class PackageInstaller.CommitResultCallback { - ctor public PackageInstaller.CommitResultCallback(); - method public abstract void onFailure(java.lang.String); - method public void onFailureConflict(java.lang.String, java.lang.String); - method public void onFailureIncompatible(java.lang.String); - method public void onFailureInvalid(java.lang.String); - method public void onFailureStorage(java.lang.String); + public static abstract class PackageInstaller.CommitCallback { + ctor public PackageInstaller.CommitCallback(); + method public abstract void onFailure(int, java.lang.String, android.os.Bundle); method public abstract void onSuccess(); + field public static final java.lang.String EXTRA_PACKAGE_NAME = "android.content.pm.extra.PACKAGE_NAME"; + field public static final int FAILURE_CONFLICT = 2; // 0x2 + field public static final int FAILURE_INCOMPATIBLE = 4; // 0x4 + field public static final int FAILURE_INVALID = 1; // 0x1 + field public static final int FAILURE_STORAGE = 3; // 0x3 + field public static final int FAILURE_UNKNOWN = 0; // 0x0 } public static class PackageInstaller.Session implements java.io.Closeable { + method public void abandon(); method public void close(); - method public void commit(android.content.pm.PackageInstaller.CommitResultCallback); - method public void destroy(); + method public void commit(android.content.pm.PackageInstaller.CommitCallback); method public void fsync(java.io.OutputStream) throws java.io.IOException; method public java.io.OutputStream openWrite(java.lang.String, long, long) throws java.io.IOException; - method public void setProgress(int); + method public void setProgress(float); } - public static abstract class PackageInstaller.SessionObserver { - ctor public PackageInstaller.SessionObserver(); - method public abstract void onCreated(android.content.pm.InstallSessionInfo); - method public abstract void onFinalized(int, boolean); - method public abstract void onProgress(int, int); + public static abstract class PackageInstaller.SessionCallback { + ctor public PackageInstaller.SessionCallback(); + method public abstract void onCreated(int); + method public abstract void onFinished(int, boolean); + method public abstract void onProgressChanged(int, float); } - public static abstract class PackageInstaller.UninstallResultCallback { - ctor public PackageInstaller.UninstallResultCallback(); + public static abstract class PackageInstaller.UninstallCallback { + ctor public PackageInstaller.UninstallCallback(); method public abstract void onFailure(java.lang.String); method public abstract void onSuccess(); } @@ -8682,7 +8686,6 @@ package android.content.pm { method public abstract android.graphics.drawable.Drawable getDrawable(java.lang.String, int, android.content.pm.ApplicationInfo); method public abstract java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int); method public abstract java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int); - method public abstract android.content.pm.PackageInstaller getInstaller(); method public abstract java.lang.String getInstallerPackageName(java.lang.String); method public abstract android.content.pm.InstrumentationInfo getInstrumentationInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException; method public abstract android.content.pm.KeySet getKeySetByAlias(java.lang.String, java.lang.String); @@ -8692,6 +8695,7 @@ package android.content.pm { method public android.content.pm.PackageInfo getPackageArchiveInfo(java.lang.String, int); method public abstract int[] getPackageGids(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException; method public abstract android.content.pm.PackageInfo getPackageInfo(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException; + method public abstract android.content.pm.PackageInstaller getPackageInstaller(); method public abstract java.lang.String[] getPackagesForUid(int); method public abstract java.util.List<android.content.pm.PackageInfo> getPackagesHoldingPermissions(java.lang.String[], int); method public abstract android.content.pm.PermissionGroupInfo getPermissionGroupInfo(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException; @@ -16230,8 +16234,8 @@ package android.media.browse { method public void notifyChildrenChanged(android.net.Uri); method public android.os.IBinder onBind(android.content.Intent); method public abstract android.media.browse.MediaBrowserService.BrowserRoot onGetRoot(java.lang.String, int, android.os.Bundle); - method protected abstract void onLoadChildren(android.net.Uri, android.media.browse.MediaBrowserService.Result<java.util.List<android.media.browse.MediaBrowserItem>>); - method protected abstract void onLoadThumbnail(android.net.Uri, int, int, android.media.browse.MediaBrowserService.Result<android.graphics.Bitmap>); + method public abstract void onLoadChildren(android.net.Uri, android.media.browse.MediaBrowserService.Result<java.util.List<android.media.browse.MediaBrowserItem>>); + method public abstract void onLoadThumbnail(android.net.Uri, int, int, android.media.browse.MediaBrowserService.Result<android.graphics.Bitmap>); method public void setSessionToken(android.media.session.MediaSession.Token); field public static final java.lang.String SERVICE_ACTION = "android.media.browse.MediaBrowserService"; } @@ -24343,6 +24347,7 @@ package android.provider { } public static final class ContactsContract.CommonDataKinds.Event implements android.provider.ContactsContract.CommonDataKinds.CommonColumns android.provider.ContactsContract.DataColumnsWithJoins { + method public static final java.lang.CharSequence getTypeLabel(android.content.res.Resources, int, java.lang.CharSequence); method public static int getTypeResource(java.lang.Integer); field public static final java.lang.String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/contact_event"; field public static final java.lang.String START_DATE = "data1"; @@ -30129,7 +30134,6 @@ package android.test.mock { method public android.graphics.drawable.Drawable getDrawable(java.lang.String, int, android.content.pm.ApplicationInfo); method public java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int); method public java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int); - method public android.content.pm.PackageInstaller getInstaller(); method public java.lang.String getInstallerPackageName(java.lang.String); method public android.content.pm.InstrumentationInfo getInstrumentationInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException; method public android.content.pm.KeySet getKeySetByAlias(java.lang.String, java.lang.String); @@ -30138,6 +30142,7 @@ package android.test.mock { method public java.lang.String getNameForUid(int); method public int[] getPackageGids(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException; method public android.content.pm.PackageInfo getPackageInfo(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException; + method public android.content.pm.PackageInstaller getPackageInstaller(); method public java.lang.String[] getPackagesForUid(int); method public java.util.List<android.content.pm.PackageInfo> getPackagesHoldingPermissions(java.lang.String[], int); method public android.content.pm.PermissionGroupInfo getPermissionGroupInfo(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException; @@ -39749,8 +39754,6 @@ package android.widget { method public void setLogoDescription(java.lang.CharSequence); method public void setNavigationContentDescription(java.lang.CharSequence); method public void setNavigationContentDescription(int); - method public void setNavigationDescription(int); - method public void setNavigationDescription(java.lang.CharSequence); method public void setNavigationIcon(int); method public void setNavigationIcon(android.graphics.drawable.Drawable); method public void setNavigationOnClickListener(android.view.View.OnClickListener); diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java index faf5622f38b6..96019b375efa 100644 --- a/cmds/pm/src/com/android/commands/pm/Pm.java +++ b/cmds/pm/src/com/android/commands/pm/Pm.java @@ -27,11 +27,12 @@ import android.content.pm.IPackageDataObserver; import android.content.pm.IPackageDeleteObserver; import android.content.pm.IPackageInstaller; import android.content.pm.IPackageManager; +import android.content.pm.InstallSessionInfo; import android.content.pm.InstallSessionParams; import android.content.pm.InstrumentationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageInstaller; -import android.content.pm.PackageInstaller.CommitResultCallback; +import android.content.pm.PackageInstaller.CommitCallback; import android.content.pm.PackageItemInfo; import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; @@ -157,8 +158,8 @@ public final class Pm { return; } - if ("install-destroy".equals(op)) { - runInstallDestroy(); + if ("install-abandon".equals(op) || "install-destroy".equals(op)) { + runInstallAbandon(); return; } @@ -770,7 +771,7 @@ public final class Pm { } } - class LocalCommitResultCallback extends CommitResultCallback { + class LocalCommitCallback extends CommitCallback { boolean finished; boolean success; String msg; @@ -790,7 +791,7 @@ public final class Pm { } @Override - public void onFailure(String msg) { + public void onFailure(int failureReason, String msg, Bundle extras) { setResult(false, msg); } } @@ -996,10 +997,9 @@ public final class Pm { private void runInstallCreate() throws RemoteException { String installerPackageName = null; - final InstallSessionParams params = new InstallSessionParams(); + final InstallSessionParams params = new InstallSessionParams( + InstallSessionParams.MODE_FULL_INSTALL); params.installFlags = PackageManager.INSTALL_ALL_USERS; - params.setModeFullInstall(); - params.setProgressMax(-1); String opt; while ((opt = nextOption()) != null) { @@ -1021,11 +1021,9 @@ public final class Pm { } else if (opt.equals("-d")) { params.installFlags |= PackageManager.INSTALL_ALLOW_DOWNGRADE; } else if (opt.equals("-p")) { - params.setModeInheritExisting(); + params.mode = InstallSessionParams.MODE_INHERIT_EXISTING; } else if (opt.equals("-S")) { - final long deltaSize = Long.parseLong(nextOptionData()); - params.setDeltaSize(deltaSize); - params.setProgressMax((int) params.deltaSize); + params.setSize(Long.parseLong(nextOptionData())); } else if (opt.equals("--abi")) { params.abiOverride = checkAbiArgument(nextOptionData()); } else { @@ -1033,7 +1031,7 @@ public final class Pm { } } - final int sessionId = mInstaller.createSession(installerPackageName, params, + final int sessionId = mInstaller.createSession(params, installerPackageName, UserHandle.USER_OWNER); // NOTE: adb depends on parsing this string @@ -1080,7 +1078,12 @@ public final class Pm { final int n = Streams.copy(in, out); session.fsync(out); - session.addProgress(n); + + final InstallSessionInfo info = mInstaller.getSessionInfo(sessionId); + if (info.sizeBytes > 0) { + final float fraction = ((float) n / (float) info.sizeBytes); + session.addProgress(fraction); + } System.out.println("Success: streamed " + n + " bytes"); } finally { @@ -1097,7 +1100,7 @@ public final class Pm { try { session = new PackageInstaller.Session(mInstaller.openSession(sessionId)); - final LocalCommitResultCallback callback = new LocalCommitResultCallback(); + final LocalCommitCallback callback = new LocalCommitCallback(); session.commit(callback); synchronized (callback) { @@ -1118,13 +1121,13 @@ public final class Pm { } } - private void runInstallDestroy() throws RemoteException { + private void runInstallAbandon() throws RemoteException { final int sessionId = Integer.parseInt(nextArg()); PackageInstaller.Session session = null; try { session = new PackageInstaller.Session(mInstaller.openSession(sessionId)); - session.destroy(); + session.abandon(); System.out.println("Success"); } finally { IoUtils.closeQuietly(session); @@ -1743,7 +1746,7 @@ public final class Pm { System.err.println(" pm install-create [-lrtsfdp] [-i PACKAGE] [-S BYTES]"); System.err.println(" pm install-write [-S BYTES] SESSION_ID SPLIT_NAME [PATH]"); System.err.println(" pm install-commit SESSION_ID"); - System.err.println(" pm install-destroy SESSION_ID"); + System.err.println(" pm install-abandon SESSION_ID"); System.err.println(" pm uninstall [-k] [--user USER_ID] PACKAGE"); System.err.println(" pm set-installer PACKAGE INSTALLER"); System.err.println(" pm clear [--user USER_ID] PACKAGE"); @@ -1813,7 +1816,7 @@ public final class Pm { System.err.println(" -S: size in bytes of package, required for stdin"); System.err.println(""); System.err.println("pm install-commit: perform install of fully staged session"); - System.err.println("pm install-destroy: destroy session"); + System.err.println("pm install-abandon: abandon session"); System.err.println(""); System.err.println("pm set-installer: set installer package name"); System.err.println(""); diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 1cb0fd42608a..68d4cf108891 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -61,7 +61,10 @@ import android.os.UserManager; import android.util.ArrayMap; import android.util.Log; import android.view.Display; + +import com.android.internal.annotations.GuardedBy; import com.android.internal.util.Preconditions; + import dalvik.system.VMRuntime; import java.lang.ref.WeakReference; @@ -74,13 +77,20 @@ final class ApplicationPackageManager extends PackageManager { private final static boolean DEBUG = false; private final static boolean DEBUG_ICONS = false; - UserManager mUserManager; + private final Object mLock = new Object(); + + @GuardedBy("mLock") + private UserManager mUserManager; + @GuardedBy("mLock") + private PackageInstaller mInstaller; UserManager getUserManager() { - if (mUserManager == null) { - mUserManager = UserManager.get(mContext); + synchronized (mLock) { + if (mUserManager == null) { + mUserManager = UserManager.get(mContext); + } + return mUserManager; } - return mUserManager; } @Override @@ -1543,12 +1553,17 @@ final class ApplicationPackageManager extends PackageManager { } @Override - public PackageInstaller getInstaller() { - try { - return new PackageInstaller(this, mPM.getPackageInstaller(), mContext.getPackageName(), - mContext.getUserId()); - } catch (RemoteException e) { - throw e.rethrowAsRuntimeException(); + public PackageInstaller getPackageInstaller() { + synchronized (mLock) { + if (mInstaller == null) { + try { + mInstaller = new PackageInstaller(this, mPM.getPackageInstaller(), + mContext.getPackageName(), mContext.getUserId()); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + return mInstaller; } } diff --git a/core/java/android/app/usage/UsageStatsManagerInternal.java b/core/java/android/app/usage/UsageStatsManagerInternal.java index d6345f3c3b4d..0d41be27bffa 100644 --- a/core/java/android/app/usage/UsageStatsManagerInternal.java +++ b/core/java/android/app/usage/UsageStatsManagerInternal.java @@ -29,11 +29,13 @@ public abstract class UsageStatsManagerInternal { * Reports an event to the UsageStatsManager. * * @param component The component for which this event ocurred. + * @param userId The user id to which the component belongs to. * @param timeStamp The time at which this event ocurred. * @param eventType The event that occured. Valid values can be found at * {@link android.app.usage.UsageStats.Event} */ - public abstract void reportEvent(ComponentName component, long timeStamp, int eventType); + public abstract void reportEvent(ComponentName component, int userId, + long timeStamp, int eventType); /** * Prepares the UsageStatsService for shutdown. diff --git a/core/java/android/bluetooth/le/ScanSettings.java b/core/java/android/bluetooth/le/ScanSettings.java index 2f86d09ec08c..b2ee6a82c94f 100644 --- a/core/java/android/bluetooth/le/ScanSettings.java +++ b/core/java/android/bluetooth/le/ScanSettings.java @@ -21,38 +21,37 @@ import android.os.Parcel; import android.os.Parcelable; /** - * Bluetooth LE scan settings are passed to {@link BluetoothLeScanner#startScan} - * to define the parameters for the scan. + * Bluetooth LE scan settings are passed to {@link BluetoothLeScanner#startScan} to define the + * parameters for the scan. */ public final class ScanSettings implements Parcelable { /** - * Perform Bluetooth LE scan in low power mode. - * This is the default scan mode as it consumes the least power. + * Perform Bluetooth LE scan in low power mode. This is the default scan mode as it consumes the + * least power. */ public static final int SCAN_MODE_LOW_POWER = 0; /** - * Perform Bluetooth LE scan in balanced power mode. - * Scan results are returned at a rate that provides a good trade-off between scan - * frequency and power consumption. + * Perform Bluetooth LE scan in balanced power mode. Scan results are returned at a rate that + * provides a good trade-off between scan frequency and power consumption. */ public static final int SCAN_MODE_BALANCED = 1; /** - * Scan using highest duty cycle. - * It's recommended to only use this mode when the application is running in the foreground. + * Scan using highest duty cycle. It's recommended to only use this mode when the application is + * running in the foreground. */ public static final int SCAN_MODE_LOW_LATENCY = 2; /** - * Trigger a callback for every Bluetooth advertisement found that matches the - * filter criteria. If no filter is active, all advertisement packets are reported. + * Trigger a callback for every Bluetooth advertisement found that matches the filter criteria. + * If no filter is active, all advertisement packets are reported. */ public static final int CALLBACK_TYPE_ALL_MATCHES = 1; /** - * A result callback is only triggered for the first advertisement packet received that - * matches the filter criteria. + * A result callback is only triggered for the first advertisement packet received that matches + * the filter criteria. */ public static final int CALLBACK_TYPE_FIRST_MATCH = 2; @@ -63,15 +62,17 @@ public final class ScanSettings implements Parcelable { public static final int CALLBACK_TYPE_MATCH_LOST = 4; /** - * Request full scan results which contain the device, rssi, advertising data, scan response - * as well as the scan timestamp. + * Request full scan results which contain the device, rssi, advertising data, scan response as + * well as the scan timestamp. */ public static final int SCAN_RESULT_TYPE_FULL = 0; /** * Request abbreviated scan results which contain the device, rssi and scan timestamp. - * <p><b>Note:</b> It is possible for an application to get more scan results than - * it asked for, if there are multiple apps using this type. + * <p> + * <b>Note:</b> It is possible for an application to get more scan results than it asked for, if + * there are multiple apps using this type. + * * @hide */ @SystemApi @@ -109,11 +110,11 @@ public final class ScanSettings implements Parcelable { } private ScanSettings(int scanMode, int callbackType, int scanResultType, - long reportDelaySeconds) { + long reportDelayMillis) { mScanMode = scanMode; mCallbackType = callbackType; mScanResultType = scanResultType; - mReportDelayMillis = reportDelaySeconds; + mReportDelayMillis = reportDelayMillis; } private ScanSettings(Parcel in) { @@ -184,15 +185,24 @@ public final class ScanSettings implements Parcelable { * @throws IllegalArgumentException If the {@code callbackType} is invalid. */ public Builder setCallbackType(int callbackType) { - if (callbackType < CALLBACK_TYPE_ALL_MATCHES - || callbackType > (CALLBACK_TYPE_FIRST_MATCH | CALLBACK_TYPE_MATCH_LOST) - || (callbackType & CALLBACK_TYPE_ALL_MATCHES) != CALLBACK_TYPE_ALL_MATCHES) { + + if (!isValidCallbackType(callbackType)) { throw new IllegalArgumentException("invalid callback type - " + callbackType); } mCallbackType = callbackType; return this; } + // Returns true if the callbackType is valid. + private boolean isValidCallbackType(int callbackType) { + if (callbackType == CALLBACK_TYPE_ALL_MATCHES || + callbackType == CALLBACK_TYPE_FIRST_MATCH || + callbackType == CALLBACK_TYPE_MATCH_LOST) { + return true; + } + return callbackType == (CALLBACK_TYPE_FIRST_MATCH | CALLBACK_TYPE_MATCH_LOST); + } + /** * Set scan result type for Bluetooth LE scan. * @@ -215,16 +225,15 @@ public final class ScanSettings implements Parcelable { /** * Set report delay timestamp for Bluetooth LE scan. - * @param reportDelayMillis Set to 0 to be notified of results immediately. - * Values > 0 causes the scan results to be queued - * up and delivered after the requested delay or when - * the internal buffers fill up. - * @throws IllegalArgumentException If {@code reportDelaySeconds} < 0. * + * @param reportDelayMillis Set to 0 to be notified of results immediately. Values > 0 + * causes the scan results to be queued up and delivered after the requested + * delay or when the internal buffers fill up. + * @throws IllegalArgumentException If {@code reportDelayMillis} < 0. */ public Builder setReportDelayMillis(long reportDelayMillis) { if (reportDelayMillis < 0) { - throw new IllegalArgumentException("reportDelaySeconds must be > 0"); + throw new IllegalArgumentException("reportDelayMillis must be > 0"); } mReportDelayMillis = reportDelayMillis; return this; diff --git a/core/java/android/content/pm/IPackageInstaller.aidl b/core/java/android/content/pm/IPackageInstaller.aidl index 32460c93c4e0..0c650349bc91 100644 --- a/core/java/android/content/pm/IPackageInstaller.aidl +++ b/core/java/android/content/pm/IPackageInstaller.aidl @@ -17,7 +17,7 @@ package android.content.pm; import android.content.pm.IPackageDeleteObserver; -import android.content.pm.IPackageInstallerObserver; +import android.content.pm.IPackageInstallerCallback; import android.content.pm.IPackageInstallerSession; import android.content.pm.InstallSessionInfo; import android.content.pm.InstallSessionParams; @@ -25,13 +25,15 @@ import android.os.ParcelFileDescriptor; /** {@hide} */ interface IPackageInstaller { - int createSession(String installerPackageName, in InstallSessionParams params, int userId); + int createSession(in InstallSessionParams params, String installerPackageName, int userId); IPackageInstallerSession openSession(int sessionId); - List<InstallSessionInfo> getSessions(int userId); + InstallSessionInfo getSessionInfo(int sessionId); + List<InstallSessionInfo> getAllSessions(int userId); + List<InstallSessionInfo> getMySessions(String installerPackageName, int userId); - void registerObserver(IPackageInstallerObserver observer, int userId); - void unregisterObserver(IPackageInstallerObserver observer, int userId); + void registerCallback(IPackageInstallerCallback callback, int userId); + void unregisterCallback(IPackageInstallerCallback callback); void uninstall(String packageName, int flags, in IPackageDeleteObserver observer, int userId); void uninstallSplit(String packageName, String splitName, int flags, in IPackageDeleteObserver observer, int userId); diff --git a/core/java/android/content/pm/IPackageInstallerObserver.aidl b/core/java/android/content/pm/IPackageInstallerCallback.aidl index 85660e4f818b..a31ae54061c5 100644 --- a/core/java/android/content/pm/IPackageInstallerObserver.aidl +++ b/core/java/android/content/pm/IPackageInstallerCallback.aidl @@ -16,11 +16,9 @@ package android.content.pm; -import android.content.pm.InstallSessionInfo; - /** {@hide} */ -oneway interface IPackageInstallerObserver { - void onSessionCreated(in InstallSessionInfo info); - void onSessionProgress(int sessionId, int progress); +oneway interface IPackageInstallerCallback { + void onSessionCreated(int sessionId); + void onSessionProgressChanged(int sessionId, float progress); void onSessionFinished(int sessionId, boolean success); } diff --git a/core/java/android/content/pm/IPackageInstallerSession.aidl b/core/java/android/content/pm/IPackageInstallerSession.aidl index d6775d4d5d70..2fd7ddbf0661 100644 --- a/core/java/android/content/pm/IPackageInstallerSession.aidl +++ b/core/java/android/content/pm/IPackageInstallerSession.aidl @@ -21,11 +21,12 @@ import android.os.ParcelFileDescriptor; /** {@hide} */ interface IPackageInstallerSession { - void setClientProgress(int progress); - void addClientProgress(int progress); + void setClientProgress(float progress); + void addClientProgress(float progress); ParcelFileDescriptor openWrite(String name, long offsetBytes, long lengthBytes); - void install(in IPackageInstallObserver2 observer); - void destroy(); + void close(); + void commit(in IPackageInstallObserver2 observer); + void abandon(); } diff --git a/core/java/android/content/pm/InstallSessionInfo.java b/core/java/android/content/pm/InstallSessionInfo.java index 33367274ce8e..a9c574a439f7 100644 --- a/core/java/android/content/pm/InstallSessionInfo.java +++ b/core/java/android/content/pm/InstallSessionInfo.java @@ -16,6 +16,7 @@ package android.content.pm; +import android.annotation.Nullable; import android.graphics.Bitmap; import android.os.Parcel; import android.os.Parcelable; @@ -30,16 +31,18 @@ public class InstallSessionInfo implements Parcelable { /** {@hide} */ public String installerPackageName; /** {@hide} */ - public int progress; + public float progress; /** {@hide} */ public int mode; /** {@hide} */ - public String packageName; + public long sizeBytes; /** {@hide} */ - public Bitmap icon; + public String appPackageName; /** {@hide} */ - public CharSequence title; + public Bitmap appIcon; + /** {@hide} */ + public CharSequence appLabel; /** {@hide} */ public InstallSessionInfo() { @@ -49,12 +52,13 @@ public class InstallSessionInfo implements Parcelable { public InstallSessionInfo(Parcel source) { sessionId = source.readInt(); installerPackageName = source.readString(); - progress = source.readInt(); + progress = source.readFloat(); mode = source.readInt(); - packageName = source.readString(); - icon = source.readParcelable(null); - title = source.readString(); + sizeBytes = source.readLong(); + appPackageName = source.readString(); + appIcon = source.readParcelable(null); + appLabel = source.readString(); } /** @@ -67,19 +71,19 @@ public class InstallSessionInfo implements Parcelable { /** * Return the package name of the app that owns this session. */ - public String getInstallerPackageName() { + public @Nullable String getInstallerPackageName() { return installerPackageName; } /** - * Return current overall progress of this session, between 0 and 100. + * Return current overall progress of this session, between 0 and 1. * <p> * Note that this progress may not directly correspond to the value reported - * by {@link PackageInstaller.Session#setProgress(int)}, as the system may + * by {@link PackageInstaller.Session#setProgress(float)}, as the system may * carve out a portion of the overall progress to represent its own internal * installation work. */ - public int getProgress() { + public float getProgress() { return progress; } @@ -87,24 +91,24 @@ public class InstallSessionInfo implements Parcelable { * Return the package name this session is working with. May be {@code null} * if unknown. */ - public String getPackageName() { - return packageName; + public @Nullable String getAppPackageName() { + return appPackageName; } /** * Return an icon representing the app being installed. May be {@code null} * if unavailable. */ - public Bitmap getIcon() { - return icon; + public @Nullable Bitmap getAppIcon() { + return appIcon; } /** - * Return a title representing the app being installed. May be {@code null} + * Return a label representing the app being installed. May be {@code null} * if unavailable. */ - public CharSequence getTitle() { - return title; + public @Nullable CharSequence getAppLabel() { + return appLabel; } @Override @@ -116,12 +120,13 @@ public class InstallSessionInfo implements Parcelable { public void writeToParcel(Parcel dest, int flags) { dest.writeInt(sessionId); dest.writeString(installerPackageName); - dest.writeInt(progress); + dest.writeFloat(progress); dest.writeInt(mode); - dest.writeString(packageName); - dest.writeParcelable(icon, flags); - dest.writeString(title != null ? title.toString() : null); + dest.writeLong(sizeBytes); + dest.writeString(appPackageName); + dest.writeParcelable(appIcon, flags); + dest.writeString(appLabel != null ? appLabel.toString() : null); } public static final Parcelable.Creator<InstallSessionInfo> diff --git a/core/java/android/content/pm/InstallSessionParams.java b/core/java/android/content/pm/InstallSessionParams.java index e0396992216e..3de986343242 100644 --- a/core/java/android/content/pm/InstallSessionParams.java +++ b/core/java/android/content/pm/InstallSessionParams.java @@ -16,6 +16,7 @@ package android.content.pm; +import android.annotation.Nullable; import android.content.Intent; import android.graphics.Bitmap; import android.net.Uri; @@ -29,17 +30,25 @@ import com.android.internal.util.IndentingPrintWriter; */ public class InstallSessionParams implements Parcelable { - // TODO: extend to support remaining VerificationParams - - /** {@hide} */ - public static final int MODE_INVALID = 0; - /** {@hide} */ + /** + * Mode for an install session whose staged APKs should fully replace any + * existing APKs for the target app. + */ public static final int MODE_FULL_INSTALL = 1; - /** {@hide} */ + + /** + * Mode for an install session that should inherit any existing APKs for the + * target app, unless they have been explicitly overridden (based on split + * name) by the session. For example, this can be used to add one or more + * split APKs to an existing installation. + * <p> + * If there are no existing APKs for the target app, this behaves like + * {@link #MODE_FULL_INSTALL}. + */ public static final int MODE_INHERIT_EXISTING = 2; /** {@hide} */ - public int mode = MODE_INVALID; + public int mode; /** {@hide} */ public int installFlags; /** {@hide} */ @@ -47,15 +56,13 @@ public class InstallSessionParams implements Parcelable { /** {@hide} */ public Signature[] signatures; /** {@hide} */ - public long deltaSize = -1; + public long sizeBytes = -1; /** {@hide} */ - public int progressMax = 100; + public String appPackageName; /** {@hide} */ - public String packageName; + public Bitmap appIcon; /** {@hide} */ - public Bitmap icon; - /** {@hide} */ - public CharSequence title; + public CharSequence appLabel; /** {@hide} */ public Uri originatingUri; /** {@hide} */ @@ -63,7 +70,15 @@ public class InstallSessionParams implements Parcelable { /** {@hide} */ public String abiOverride; - public InstallSessionParams() { + /** + * Construct parameters for a new package install session. + * + * @param mode one of {@link #MODE_FULL_INSTALL} or + * {@link #MODE_INHERIT_EXISTING} describing how the session + * should interact with an existing app. + */ + public InstallSessionParams(int mode) { + this.mode = mode; } /** {@hide} */ @@ -72,34 +87,16 @@ public class InstallSessionParams implements Parcelable { installFlags = source.readInt(); installLocation = source.readInt(); signatures = (Signature[]) source.readParcelableArray(null); - deltaSize = source.readLong(); - progressMax = source.readInt(); - packageName = source.readString(); - icon = source.readParcelable(null); - title = source.readString(); + sizeBytes = source.readLong(); + appPackageName = source.readString(); + appIcon = source.readParcelable(null); + appLabel = source.readString(); originatingUri = source.readParcelable(null); referrerUri = source.readParcelable(null); abiOverride = source.readString(); } /** - * Set session mode indicating that it should fully replace any existing - * APKs for this application. - */ - public void setModeFullInstall() { - this.mode = MODE_FULL_INSTALL; - } - - /** - * Set session mode indicating that it should inherit any existing APKs for - * this application, unless they are explicitly overridden (by split name) - * in the session. - */ - public void setModeInheritExisting() { - this.mode = MODE_INHERIT_EXISTING; - } - - /** * Provide value of {@link PackageInfo#installLocation}, which may be used * to determine where the app will be staged. Defaults to * {@link PackageInfo#INSTALL_LOCATION_INTERNAL_ONLY}. @@ -109,56 +106,57 @@ public class InstallSessionParams implements Parcelable { } /** - * Optionally provide the required value of {@link PackageInfo#signatures}. - * This can be used to assert that all staged APKs have been signed with - * this set of specific certificates. Regardless of this value, all APKs in - * the application must have the same signing certificates. - */ - public void setSignatures(Signature[] signatures) { - this.signatures = signatures; - } - - /** - * Indicate the expected growth in disk usage resulting from this session. - * This may be used to ensure enough disk space exists before proceeding, or - * to estimate container size for installations living on external storage. + * Optionally provide a set of certificates for the app being installed. * <p> - * This value should only reflect APK sizes. + * If the APKs staged in the session aren't consistent with these + * signatures, the install will fail. Regardless of this value, all APKs in + * the app must have the same signing certificates. + * + * @see PackageInfo#signatures */ - public void setDeltaSize(long deltaSize) { - this.deltaSize = deltaSize; + public void setSignatures(@Nullable Signature[] signatures) { + this.signatures = signatures; } /** - * Set the maximum progress for this session, used for normalization - * purposes. + * Optionally indicate the total size (in bytes) of all APKs that will be + * delivered in this session. The system may use this to ensure enough disk + * space exists before proceeding, or to estimate container size for + * installations living on external storage. * - * @see PackageInstaller.Session#setProgress(int) + * @see PackageInfo#INSTALL_LOCATION_AUTO + * @see PackageInfo#INSTALL_LOCATION_PREFER_EXTERNAL */ - public void setProgressMax(int progressMax) { - this.progressMax = progressMax; + public void setSize(long sizeBytes) { + this.sizeBytes = sizeBytes; } /** - * Optionally set the package name this session will be working with. It's - * strongly recommended that you provide this value when known. + * Optionally set the package name of the app being installed. It's strongly + * recommended that you provide this value when known, so that observers can + * communicate installing apps to users. + * <p> + * If the APKs staged in the session aren't consistent with this package + * name, the install will fail. Regardless of this value, all APKs in the + * app must have the same package name. */ - public void setPackageName(String packageName) { - this.packageName = packageName; + public void setAppPackageName(@Nullable String appPackageName) { + this.appPackageName = appPackageName; } /** - * Optionally set an icon representing the app being installed. + * Optionally set an icon representing the app being installed. This should + * be at least {@link android.R.dimen#app_icon_size} in both dimensions. */ - public void setIcon(Bitmap icon) { - this.icon = icon; + public void setAppIcon(@Nullable Bitmap appIcon) { + this.appIcon = appIcon; } /** - * Optionally set a title representing the app being installed. + * Optionally set a label representing the app being installed. */ - public void setTitle(CharSequence title) { - this.title = title; + public void setAppLabel(@Nullable CharSequence appLabel) { + this.appLabel = appLabel; } /** @@ -167,7 +165,7 @@ public class InstallSessionParams implements Parcelable { * * @see Intent#EXTRA_ORIGINATING_URI */ - public void setOriginatingUri(Uri originatingUri) { + public void setOriginatingUri(@Nullable Uri originatingUri) { this.originatingUri = originatingUri; } @@ -177,7 +175,7 @@ public class InstallSessionParams implements Parcelable { * * @see Intent#EXTRA_REFERRER */ - public void setReferrerUri(Uri referrerUri) { + public void setReferrerUri(@Nullable Uri referrerUri) { this.referrerUri = referrerUri; } @@ -187,11 +185,10 @@ public class InstallSessionParams implements Parcelable { pw.printHexPair("installFlags", installFlags); pw.printPair("installLocation", installLocation); pw.printPair("signatures", (signatures != null)); - pw.printPair("deltaSize", deltaSize); - pw.printPair("progressMax", progressMax); - pw.printPair("packageName", packageName); - pw.printPair("icon", (icon != null)); - pw.printPair("title", title); + pw.printPair("sizeBytes", sizeBytes); + pw.printPair("appPackageName", appPackageName); + pw.printPair("appIcon", (appIcon != null)); + pw.printPair("appLabel", appLabel); pw.printPair("originatingUri", originatingUri); pw.printPair("referrerUri", referrerUri); pw.printPair("abiOverride", abiOverride); @@ -209,11 +206,10 @@ public class InstallSessionParams implements Parcelable { dest.writeInt(installFlags); dest.writeInt(installLocation); dest.writeParcelableArray(signatures, flags); - dest.writeLong(deltaSize); - dest.writeInt(progressMax); - dest.writeString(packageName); - dest.writeParcelable(icon, flags); - dest.writeString(title != null ? title.toString() : null); + dest.writeLong(sizeBytes); + dest.writeString(appPackageName); + dest.writeParcelable(appIcon, flags); + dest.writeString(appLabel != null ? appLabel.toString() : null); dest.writeParcelable(originatingUri, flags); dest.writeParcelable(referrerUri, flags); dest.writeString(abiOverride); diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index df82d264d1bb..a114bb894084 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -16,11 +16,16 @@ package android.content.pm; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.PackageInstallObserver; import android.app.PackageUninstallObserver; import android.content.pm.PackageManager.NameNotFoundException; import android.os.Bundle; import android.os.FileBridge; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.util.ExceptionUtils; @@ -28,6 +33,8 @@ import android.util.ExceptionUtils; import java.io.Closeable; import java.io.IOException; import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Iterator; import java.util.List; /** @@ -61,6 +68,8 @@ public class PackageInstaller { private final int mUserId; private final String mInstallerPackageName; + private final ArrayList<SessionCallbackDelegate> mDelegates = new ArrayList<>(); + /** {@hide} */ public PackageInstaller(PackageManager pm, IPackageInstaller installer, String installerPackageName, int userId) { @@ -71,28 +80,6 @@ public class PackageInstaller { } /** - * Quickly test if the given package is already available on the device. - * This is typically used in multi-user scenarios where another user on the - * device has already installed the package. - * - * @hide - */ - public boolean isPackageAvailable(String packageName) { - return mPm.isPackageAvailable(packageName); - } - - /** {@hide} */ - public void installAvailablePackage(String packageName, PackageInstallObserver observer) { - int returnCode; - try { - returnCode = mPm.installExistingPackage(packageName); - } catch (NameNotFoundException e) { - returnCode = PackageManager.INSTALL_FAILED_PACKAGE_CHANGED; - } - observer.packageInstalled(packageName, null, returnCode); - } - - /** * Create a new session using the given parameters, returning a unique ID * that represents the session. Once created, the session can be opened * multiple times across multiple device boots. @@ -104,9 +91,9 @@ public class PackageInstaller { * @throws IOException if parameters were unsatisfiable, such as lack of * disk space or unavailable media. */ - public int createSession(InstallSessionParams params) throws IOException { + public int createSession(@NonNull InstallSessionParams params) throws IOException { try { - return mInstaller.createSession(mInstallerPackageName, params, mUserId); + return mInstaller.createSession(params, mInstallerPackageName, mUserId); } catch (RuntimeException e) { ExceptionUtils.maybeUnwrapIOException(e); throw e; @@ -116,9 +103,10 @@ public class PackageInstaller { } /** - * Open an existing session to actively perform work. + * Open an existing session to actively perform work. To succeed, the caller + * must be the owner of the install session. */ - public Session openSession(int sessionId) { + public @NonNull Session openSession(int sessionId) { try { return new Session(mInstaller.openSession(sessionId)); } catch (RemoteException e) { @@ -127,13 +115,35 @@ public class PackageInstaller { } /** - * Return list of all active install sessions on the device. + * Return details for a specific session. To succeed, the caller must either + * own this session, or be the current home app. */ - public List<InstallSessionInfo> getActiveSessions() { - // TODO: filter based on caller - // TODO: let launcher app see all active sessions + public @Nullable InstallSessionInfo getSessionInfo(int sessionId) { try { - return mInstaller.getSessions(mUserId); + return mInstaller.getSessionInfo(sessionId); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + /** + * Return list of all active install sessions, regardless of the installer. + * To succeed, the caller must be the current home app. + */ + public @NonNull List<InstallSessionInfo> getAllSessions() { + try { + return mInstaller.getAllSessions(mUserId); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + /** + * Return list of all install sessions owned by the calling app. + */ + public @NonNull List<InstallSessionInfo> getMySessions() { + try { + return mInstaller.getMySessions(mInstallerPackageName, mUserId); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } @@ -144,10 +154,10 @@ public class PackageInstaller { * method is only available to the current "installer of record" for the * package. */ - public void uninstall(String packageName, UninstallResultCallback callback) { + public void uninstall(@NonNull String packageName, @NonNull UninstallCallback callback) { try { mInstaller.uninstall(packageName, 0, - new UninstallResultCallbackDelegate(callback).getBinder(), mUserId); + new UninstallCallbackDelegate(callback).getBinder(), mUserId); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } @@ -158,10 +168,11 @@ public class PackageInstaller { * * @hide */ - public void uninstall(String packageName, String splitName, UninstallResultCallback callback) { + public void uninstall(@NonNull String packageName, @NonNull String splitName, + @NonNull UninstallCallback callback) { try { mInstaller.uninstallSplit(packageName, splitName, 0, - new UninstallResultCallbackDelegate(callback).getBinder(), mUserId); + new UninstallCallbackDelegate(callback).getBinder(), mUserId); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } @@ -170,69 +181,121 @@ public class PackageInstaller { /** * Events for observing session lifecycle. */ - public static abstract class SessionObserver { - private final IPackageInstallerObserver.Stub mBinder = new IPackageInstallerObserver.Stub() { - @Override - public void onSessionCreated(InstallSessionInfo info) { - SessionObserver.this.onCreated(info); - } - - @Override - public void onSessionProgress(int sessionId, int progress) { - SessionObserver.this.onProgress(sessionId, progress); - } - - @Override - public void onSessionFinished(int sessionId, boolean success) { - SessionObserver.this.onFinalized(sessionId, success); - } - }; - - /** {@hide} */ - public IPackageInstallerObserver getBinder() { - return mBinder; - } - + public static abstract class SessionCallback { /** * New session has been created. */ - public abstract void onCreated(InstallSessionInfo info); + public abstract void onCreated(int sessionId); /** * Progress for given session has been updated. * <p> * Note that this progress may not directly correspond to the value - * reported by {@link PackageInstaller.Session#setProgress(int)}, as the - * system may carve out a portion of the overall progress to represent - * its own internal installation work. + * reported by {@link PackageInstaller.Session#setProgress(float)}, as + * the system may carve out a portion of the overall progress to + * represent its own internal installation work. */ - public abstract void onProgress(int sessionId, int progress); + public abstract void onProgressChanged(int sessionId, float progress); /** - * Session has been finalized, either with success or failure. + * Session has completely finished, either with success or failure. */ - public abstract void onFinalized(int sessionId, boolean success); + public abstract void onFinished(int sessionId, boolean success); + } + + /** {@hide} */ + private static class SessionCallbackDelegate extends IPackageInstallerCallback.Stub implements + Handler.Callback { + private static final int MSG_SESSION_CREATED = 1; + private static final int MSG_SESSION_PROGRESS_CHANGED = 2; + private static final int MSG_SESSION_FINISHED = 3; + + final SessionCallback mCallback; + final Handler mHandler; + + public SessionCallbackDelegate(SessionCallback callback, Looper looper) { + mCallback = callback; + mHandler = new Handler(looper, this); + } + + @Override + public boolean handleMessage(Message msg) { + switch (msg.what) { + case MSG_SESSION_CREATED: + mCallback.onCreated(msg.arg1); + return true; + case MSG_SESSION_PROGRESS_CHANGED: + mCallback.onProgressChanged(msg.arg1, (float) msg.obj); + return true; + case MSG_SESSION_FINISHED: + mCallback.onFinished(msg.arg1, msg.arg2 != 0); + return true; + } + return false; + } + + @Override + public void onSessionCreated(int sessionId) { + mHandler.obtainMessage(MSG_SESSION_CREATED, sessionId, 0).sendToTarget(); + } + + @Override + public void onSessionProgressChanged(int sessionId, float progress) { + mHandler.obtainMessage(MSG_SESSION_PROGRESS_CHANGED, sessionId, 0, progress) + .sendToTarget(); + } + + @Override + public void onSessionFinished(int sessionId, boolean success) { + mHandler.obtainMessage(MSG_SESSION_FINISHED, sessionId, success ? 1 : 0) + .sendToTarget(); + } } /** - * Register to watch for session lifecycle events. + * Register to watch for session lifecycle events. To succeed, the caller + * must be the current home app. */ - public void registerSessionObserver(SessionObserver observer) { - try { - mInstaller.registerObserver(observer.getBinder(), mUserId); - } catch (RemoteException e) { - throw e.rethrowAsRuntimeException(); + public void addSessionCallback(@NonNull SessionCallback callback) { + addSessionCallback(callback, new Handler()); + } + + /** + * Register to watch for session lifecycle events. To succeed, the caller + * must be the current home app. + * + * @param handler to dispatch callback events through, otherwise uses + * calling thread. + */ + public void addSessionCallback(@NonNull SessionCallback callback, @NonNull Handler handler) { + synchronized (mDelegates) { + final SessionCallbackDelegate delegate = new SessionCallbackDelegate(callback, + handler.getLooper()); + try { + mInstaller.registerCallback(delegate, mUserId); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + mDelegates.add(delegate); } } /** - * Unregister an existing observer. + * Unregister an existing callback. */ - public void unregisterSessionObserver(SessionObserver observer) { - try { - mInstaller.unregisterObserver(observer.getBinder(), mUserId); - } catch (RemoteException e) { - throw e.rethrowAsRuntimeException(); + public void removeSessionCallback(@NonNull SessionCallback callback) { + synchronized (mDelegates) { + for (Iterator<SessionCallbackDelegate> i = mDelegates.iterator(); i.hasNext();) { + final SessionCallbackDelegate delegate = i.next(); + if (delegate.mCallback == callback) { + try { + mInstaller.unregisterCallback(delegate); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + i.remove(); + } + } } } @@ -244,9 +307,9 @@ public class PackageInstaller { * A session may contain any number of split packages. If the application * does not yet exist, this session must include a base package. * <p> - * If a package included in this session is already defined by the existing - * installation (for example, the same split name), the package in this - * session will replace the existing package. + * If an APK included in this session is already defined by the existing + * installation (for example, the same split name), the APK in this session + * will replace the existing APK. */ public static class Session implements Closeable { private IPackageInstallerSession mSession; @@ -257,10 +320,9 @@ public class PackageInstaller { } /** - * Set current progress. Valid values are anywhere between 0 and - * {@link InstallSessionParams#setProgressMax(int)}. + * Set current progress. Valid values are anywhere between 0 and 1. */ - public void setProgress(int progress) { + public void setProgress(float progress) { try { mSession.setClientProgress(progress); } catch (RemoteException e) { @@ -269,7 +331,7 @@ public class PackageInstaller { } /** {@hide} */ - public void addProgress(int progress) { + public void addProgress(float progress) { try { mSession.addClientProgress(progress); } catch (RemoteException e) { @@ -278,15 +340,33 @@ public class PackageInstaller { } /** - * Open an APK file for writing, starting at the given offset. You can - * then stream data into the file, periodically calling - * {@link #fsync(OutputStream)} to ensure bytes have been written to - * disk. + * Open a stream to write an APK file into the session. + * <p> + * The returned stream will start writing data at the requested offset + * in the underlying file, which can be used to resume a partially + * written file. If a valid file length is specified, the system will + * preallocate the underlying disk space to optimize placement on disk. + * It's strongly recommended to provide a valid file length when known. + * <p> + * You can write data into the returned stream, optionally call + * {@link #fsync(OutputStream)} as needed to ensure bytes have been + * persisted to disk, and then close when finished. All streams must be + * closed before calling {@link #commit(CommitCallback)}. + * + * @param name arbitrary, unique name of your choosing to identify the + * APK being written. You can open a file again for + * additional writes (such as after a reboot) by using the + * same name. This name is only meaningful within the context + * of a single install session. + * @param offsetBytes offset into the file to begin writing at, or 0 to + * start at the beginning of the file. + * @param lengthBytes total size of the file being written, used to + * preallocate the underlying disk space, or -1 if unknown. */ - public OutputStream openWrite(String splitName, long offsetBytes, long lengthBytes) - throws IOException { + public @NonNull OutputStream openWrite(@NonNull String name, long offsetBytes, + long lengthBytes) throws IOException { try { - final ParcelFileDescriptor clientSocket = mSession.openWrite(splitName, + final ParcelFileDescriptor clientSocket = mSession.openWrite(name, offsetBytes, lengthBytes); return new FileBridge.FileBridgeOutputStream(clientSocket.getFileDescriptor()); } catch (RuntimeException e) { @@ -302,7 +382,7 @@ public class PackageInstaller { * to disk. This is only valid for streams returned from * {@link #openWrite(String, long, long)}. */ - public void fsync(OutputStream out) throws IOException { + public void fsync(@NonNull OutputStream out) throws IOException { if (out instanceof FileBridge.FileBridgeOutputStream) { ((FileBridge.FileBridgeOutputStream) out).fsync(); } else { @@ -319,9 +399,9 @@ public class PackageInstaller { * on the session. If the device reboots before the session has been * finalized, you may commit the session again. */ - public void commit(CommitResultCallback callback) { + public void commit(@NonNull CommitCallback callback) { try { - mSession.install(new CommitResultCallbackDelegate(callback).getBinder()); + mSession.commit(new CommitCallbackDelegate(callback).getBinder()); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } @@ -333,15 +413,20 @@ public class PackageInstaller { */ @Override public void close() { - // No resources to release at the moment + try { + mSession.close(); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } } /** - * Completely destroy this session, rendering it invalid. + * Completely abandon this session, destroying all staged data and + * rendering it invalid. */ - public void destroy() { + public void abandon() { try { - mSession.destroy(); + mSession.abandon(); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } @@ -351,30 +436,26 @@ public class PackageInstaller { /** * Final result of an uninstall request. */ - public static abstract class UninstallResultCallback { + public static abstract class UninstallCallback { public abstract void onSuccess(); public abstract void onFailure(String msg); } /** {@hide} */ - private static class UninstallResultCallbackDelegate extends PackageUninstallObserver { - private final UninstallResultCallback target; + private static class UninstallCallbackDelegate extends PackageUninstallObserver { + private final UninstallCallback target; - public UninstallResultCallbackDelegate(UninstallResultCallback target) { + public UninstallCallbackDelegate(UninstallCallback target) { this.target = target; } @Override public void onUninstallFinished(String basePackageName, int returnCode) { - final String msg = null; - - switch (returnCode) { - case PackageManager.DELETE_SUCCEEDED: target.onSuccess(); break; - case PackageManager.DELETE_FAILED_INTERNAL_ERROR: target.onFailure("DELETE_FAILED_INTERNAL_ERROR: " + msg); break; - case PackageManager.DELETE_FAILED_DEVICE_POLICY_MANAGER: target.onFailure("DELETE_FAILED_DEVICE_POLICY_MANAGER: " + msg); break; - case PackageManager.DELETE_FAILED_USER_RESTRICTED: target.onFailure("DELETE_FAILED_USER_RESTRICTED: " + msg); break; - case PackageManager.DELETE_FAILED_OWNER_BLOCKED: target.onFailure("DELETE_FAILED_OWNER_BLOCKED: " + msg); break; - default: target.onFailure(msg); break; + if (returnCode == PackageManager.DELETE_SUCCEEDED) { + target.onSuccess(); + } else { + final String msg = PackageManager.deleteStatusToString(returnCode); + target.onFailure(msg); } } } @@ -382,16 +463,13 @@ public class PackageInstaller { /** * Final result of a session commit request. */ - public static abstract class CommitResultCallback { - public abstract void onSuccess(); - + public static abstract class CommitCallback { /** - * Generic failure occurred. You can override methods (such as - * {@link #onFailureInvalid(String)}) to handle more specific categories - * of failure. By default, those specific categories all flow into this - * generic failure. + * Generic unknown failure. The system will always try to provide a more + * specific failure reason, but in some rare cases this may be + * delivered. */ - public abstract void onFailure(String msg); + public static final int FAILURE_UNKNOWN = 0; /** * One or more of the APKs included in the session was invalid. For @@ -399,23 +477,19 @@ public class PackageInstaller { * mismatched, etc. The installer may want to try downloading and * installing again. */ - public void onFailureInvalid(String msg) { - onFailure(msg); - } + public static final int FAILURE_INVALID = 1; /** * This install session conflicts (or is inconsistent with) with another * package already installed on the device. For example, an existing * permission, incompatible certificates, etc. The user may be able to * uninstall another app to fix the issue. - * - * @param otherPackageName if one specific package was identified as the - * cause of the conflict, it's named here. If unknown, or - * multiple packages, this may be {@code null}. + * <p> + * The extras bundle may contain {@link #EXTRA_PACKAGE_NAME} if one + * specific package was identified as the cause of the conflict. If + * unknown, or multiple packages, the extra may be {@code null}. */ - public void onFailureConflict(String msg, String otherPackageName) { - onFailure(msg); - } + public static final int FAILURE_CONFLICT = 2; /** * This install session failed due to storage issues. For example, @@ -423,9 +497,7 @@ public class PackageInstaller { * media may be unavailable. The user may be able to help free space * or insert the correct media. */ - public void onFailureStorage(String msg) { - onFailure(msg); - } + public static final int FAILURE_STORAGE = 3; /** * This install session is fundamentally incompatible with this @@ -434,66 +506,37 @@ public class PackageInstaller { * ABI, or it requires a newer SDK version, etc. This install would * never succeed. */ - public void onFailureIncompatible(String msg) { - onFailure(msg); - } + public static final int FAILURE_INCOMPATIBLE = 4; + + public static final String EXTRA_PACKAGE_NAME = "android.content.pm.extra.PACKAGE_NAME"; + + public abstract void onSuccess(); + public abstract void onFailure(int failureReason, String msg, Bundle extras); } /** {@hide} */ - private static class CommitResultCallbackDelegate extends PackageInstallObserver { - private final CommitResultCallback target; + private static class CommitCallbackDelegate extends PackageInstallObserver { + private final CommitCallback target; - public CommitResultCallbackDelegate(CommitResultCallback target) { + public CommitCallbackDelegate(CommitCallback target) { this.target = target; } @Override public void packageInstalled(String basePackageName, Bundle extras, int returnCode, String msg) { - final String otherPackage = null; - - switch (returnCode) { - case PackageManager.INSTALL_SUCCEEDED: target.onSuccess(); break; - case PackageManager.INSTALL_FAILED_ALREADY_EXISTS: target.onFailureConflict("INSTALL_FAILED_ALREADY_EXISTS: " + msg, otherPackage); break; - case PackageManager.INSTALL_FAILED_INVALID_APK: target.onFailureInvalid("INSTALL_FAILED_INVALID_APK: " + msg); break; - case PackageManager.INSTALL_FAILED_INVALID_URI: target.onFailureInvalid("INSTALL_FAILED_INVALID_URI: " + msg); break; - case PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE: target.onFailureStorage("INSTALL_FAILED_INSUFFICIENT_STORAGE: " + msg); break; - case PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE: target.onFailureConflict("INSTALL_FAILED_DUPLICATE_PACKAGE: " + msg, otherPackage); break; - case PackageManager.INSTALL_FAILED_NO_SHARED_USER: target.onFailureConflict("INSTALL_FAILED_NO_SHARED_USER: " + msg, otherPackage); break; - case PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE: target.onFailureConflict("INSTALL_FAILED_UPDATE_INCOMPATIBLE: " + msg, otherPackage); break; - case PackageManager.INSTALL_FAILED_SHARED_USER_INCOMPATIBLE: target.onFailureConflict("INSTALL_FAILED_SHARED_USER_INCOMPATIBLE: " + msg, otherPackage); break; - case PackageManager.INSTALL_FAILED_MISSING_SHARED_LIBRARY: target.onFailureIncompatible("INSTALL_FAILED_MISSING_SHARED_LIBRARY: " + msg); break; - case PackageManager.INSTALL_FAILED_REPLACE_COULDNT_DELETE: target.onFailureConflict("INSTALL_FAILED_REPLACE_COULDNT_DELETE: " + msg, otherPackage); break; - case PackageManager.INSTALL_FAILED_DEXOPT: target.onFailureInvalid("INSTALL_FAILED_DEXOPT: " + msg); break; - case PackageManager.INSTALL_FAILED_OLDER_SDK: target.onFailureIncompatible("INSTALL_FAILED_OLDER_SDK: " + msg); break; - case PackageManager.INSTALL_FAILED_CONFLICTING_PROVIDER: target.onFailureConflict("INSTALL_FAILED_CONFLICTING_PROVIDER: " + msg, otherPackage); break; - case PackageManager.INSTALL_FAILED_NEWER_SDK: target.onFailureIncompatible("INSTALL_FAILED_NEWER_SDK: " + msg); break; - case PackageManager.INSTALL_FAILED_TEST_ONLY: target.onFailureInvalid("INSTALL_FAILED_TEST_ONLY: " + msg); break; - case PackageManager.INSTALL_FAILED_CPU_ABI_INCOMPATIBLE: target.onFailureIncompatible("INSTALL_FAILED_CPU_ABI_INCOMPATIBLE: " + msg); break; - case PackageManager.INSTALL_FAILED_MISSING_FEATURE: target.onFailureIncompatible("INSTALL_FAILED_MISSING_FEATURE: " + msg); break; - case PackageManager.INSTALL_FAILED_CONTAINER_ERROR: target.onFailureStorage("INSTALL_FAILED_CONTAINER_ERROR: " + msg); break; - case PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION: target.onFailureStorage("INSTALL_FAILED_INVALID_INSTALL_LOCATION: " + msg); break; - case PackageManager.INSTALL_FAILED_MEDIA_UNAVAILABLE: target.onFailureStorage("INSTALL_FAILED_MEDIA_UNAVAILABLE: " + msg); break; - case PackageManager.INSTALL_FAILED_VERIFICATION_TIMEOUT: target.onFailure("INSTALL_FAILED_VERIFICATION_TIMEOUT: " + msg); break; - case PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE: target.onFailure("INSTALL_FAILED_VERIFICATION_FAILURE: " + msg); break; - case PackageManager.INSTALL_FAILED_PACKAGE_CHANGED: target.onFailureInvalid("INSTALL_FAILED_PACKAGE_CHANGED: " + msg); break; - case PackageManager.INSTALL_FAILED_UID_CHANGED: target.onFailureInvalid("INSTALL_FAILED_UID_CHANGED: " + msg); break; - case PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE: target.onFailureInvalid("INSTALL_FAILED_VERSION_DOWNGRADE: " + msg); break; - case PackageManager.INSTALL_PARSE_FAILED_NOT_APK: target.onFailureInvalid("INSTALL_PARSE_FAILED_NOT_APK: " + msg); break; - case PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST: target.onFailureInvalid("INSTALL_PARSE_FAILED_BAD_MANIFEST: " + msg); break; - case PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION: target.onFailureInvalid("INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION: " + msg); break; - case PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES: target.onFailureInvalid("INSTALL_PARSE_FAILED_NO_CERTIFICATES: " + msg); break; - case PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES: target.onFailureInvalid("INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES: " + msg); break; - case PackageManager.INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING: target.onFailureInvalid("INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING: " + msg); break; - case PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME: target.onFailureInvalid("INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME: " + msg); break; - case PackageManager.INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID: target.onFailureInvalid("INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID: " + msg); break; - case PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED: target.onFailureInvalid("INSTALL_PARSE_FAILED_MANIFEST_MALFORMED: " + msg); break; - case PackageManager.INSTALL_PARSE_FAILED_MANIFEST_EMPTY: target.onFailureInvalid("INSTALL_PARSE_FAILED_MANIFEST_EMPTY: " + msg); break; - case PackageManager.INSTALL_FAILED_INTERNAL_ERROR: target.onFailure("INSTALL_FAILED_INTERNAL_ERROR: " + msg); break; - case PackageManager.INSTALL_FAILED_USER_RESTRICTED: target.onFailureIncompatible("INSTALL_FAILED_USER_RESTRICTED: " + msg); break; - case PackageManager.INSTALL_FAILED_DUPLICATE_PERMISSION: target.onFailureConflict("INSTALL_FAILED_DUPLICATE_PERMISSION: " + msg, otherPackage); break; - case PackageManager.INSTALL_FAILED_NO_MATCHING_ABIS: target.onFailureInvalid("INSTALL_FAILED_NO_MATCHING_ABIS: " + msg); break; - default: target.onFailure(msg); break; + if (returnCode == PackageManager.INSTALL_SUCCEEDED) { + target.onSuccess(); + } else { + final int failureReason = PackageManager.installStatusToFailureReason(returnCode); + msg = PackageManager.installStatusToString(returnCode) + ": " + msg; + + if (extras != null) { + extras.putString(CommitCallback.EXTRA_PACKAGE_NAME, + extras.getString(PackageManager.EXTRA_FAILURE_EXISTING_PACKAGE)); + } + + target.onFailure(failureReason, msg, extras); } } } diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 8b6ae41d628c..62611efa5db0 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -17,6 +17,7 @@ package android.content.pm; import android.annotation.IntDef; +import android.annotation.NonNull; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SystemApi; @@ -26,6 +27,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.IntentSender; +import android.content.pm.PackageInstaller.CommitCallback; import android.content.pm.PackageParser.PackageParserException; import android.content.res.Resources; import android.content.res.XmlResourceParser; @@ -3723,7 +3725,7 @@ public abstract class PackageManager { * Return interface that offers the ability to install, upgrade, and remove * applications on the device. */ - public abstract PackageInstaller getInstaller(); + public abstract @NonNull PackageInstaller getPackageInstaller(); /** * Returns the data directory for a particular user and package, given the uid of the package. @@ -3772,4 +3774,109 @@ public abstract class PackageManager { /** {@hide} */ public abstract boolean isPackageAvailable(String packageName); + + /** {@hide} */ + public static String installStatusToString(int status) { + switch (status) { + case INSTALL_SUCCEEDED: return "INSTALL_SUCCEEDED"; + case INSTALL_FAILED_ALREADY_EXISTS: return "INSTALL_FAILED_ALREADY_EXISTS"; + case INSTALL_FAILED_INVALID_APK: return "INSTALL_FAILED_INVALID_APK"; + case INSTALL_FAILED_INVALID_URI: return "INSTALL_FAILED_INVALID_URI"; + case INSTALL_FAILED_INSUFFICIENT_STORAGE: return "INSTALL_FAILED_INSUFFICIENT_STORAGE"; + case INSTALL_FAILED_DUPLICATE_PACKAGE: return "INSTALL_FAILED_DUPLICATE_PACKAGE"; + case INSTALL_FAILED_NO_SHARED_USER: return "INSTALL_FAILED_NO_SHARED_USER"; + case INSTALL_FAILED_UPDATE_INCOMPATIBLE: return "INSTALL_FAILED_UPDATE_INCOMPATIBLE"; + case INSTALL_FAILED_SHARED_USER_INCOMPATIBLE: return "INSTALL_FAILED_SHARED_USER_INCOMPATIBLE"; + case INSTALL_FAILED_MISSING_SHARED_LIBRARY: return "INSTALL_FAILED_MISSING_SHARED_LIBRARY"; + case INSTALL_FAILED_REPLACE_COULDNT_DELETE: return "INSTALL_FAILED_REPLACE_COULDNT_DELETE"; + case INSTALL_FAILED_DEXOPT: return "INSTALL_FAILED_DEXOPT"; + case INSTALL_FAILED_OLDER_SDK: return "INSTALL_FAILED_OLDER_SDK"; + case INSTALL_FAILED_CONFLICTING_PROVIDER: return "INSTALL_FAILED_CONFLICTING_PROVIDER"; + case INSTALL_FAILED_NEWER_SDK: return "INSTALL_FAILED_NEWER_SDK"; + case INSTALL_FAILED_TEST_ONLY: return "INSTALL_FAILED_TEST_ONLY"; + case INSTALL_FAILED_CPU_ABI_INCOMPATIBLE: return "INSTALL_FAILED_CPU_ABI_INCOMPATIBLE"; + case INSTALL_FAILED_MISSING_FEATURE: return "INSTALL_FAILED_MISSING_FEATURE"; + case INSTALL_FAILED_CONTAINER_ERROR: return "INSTALL_FAILED_CONTAINER_ERROR"; + case INSTALL_FAILED_INVALID_INSTALL_LOCATION: return "INSTALL_FAILED_INVALID_INSTALL_LOCATION"; + case INSTALL_FAILED_MEDIA_UNAVAILABLE: return "INSTALL_FAILED_MEDIA_UNAVAILABLE"; + case INSTALL_FAILED_VERIFICATION_TIMEOUT: return "INSTALL_FAILED_VERIFICATION_TIMEOUT"; + case INSTALL_FAILED_VERIFICATION_FAILURE: return "INSTALL_FAILED_VERIFICATION_FAILURE"; + case INSTALL_FAILED_PACKAGE_CHANGED: return "INSTALL_FAILED_PACKAGE_CHANGED"; + case INSTALL_FAILED_UID_CHANGED: return "INSTALL_FAILED_UID_CHANGED"; + case INSTALL_FAILED_VERSION_DOWNGRADE: return "INSTALL_FAILED_VERSION_DOWNGRADE"; + case INSTALL_PARSE_FAILED_NOT_APK: return "INSTALL_PARSE_FAILED_NOT_APK"; + case INSTALL_PARSE_FAILED_BAD_MANIFEST: return "INSTALL_PARSE_FAILED_BAD_MANIFEST"; + case INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION: return "INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION"; + case INSTALL_PARSE_FAILED_NO_CERTIFICATES: return "INSTALL_PARSE_FAILED_NO_CERTIFICATES"; + case INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES: return "INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES"; + case INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING: return "INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING"; + case INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME: return "INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME"; + case INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID: return "INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID"; + case INSTALL_PARSE_FAILED_MANIFEST_MALFORMED: return "INSTALL_PARSE_FAILED_MANIFEST_MALFORMED"; + case INSTALL_PARSE_FAILED_MANIFEST_EMPTY: return "INSTALL_PARSE_FAILED_MANIFEST_EMPTY"; + case INSTALL_FAILED_INTERNAL_ERROR: return "INSTALL_FAILED_INTERNAL_ERROR"; + case INSTALL_FAILED_USER_RESTRICTED: return "INSTALL_FAILED_USER_RESTRICTED"; + case INSTALL_FAILED_DUPLICATE_PERMISSION: return "INSTALL_FAILED_DUPLICATE_PERMISSION"; + case INSTALL_FAILED_NO_MATCHING_ABIS: return "INSTALL_FAILED_NO_MATCHING_ABIS"; + default: return Integer.toString(status); + } + } + + /** {@hide} */ + public static int installStatusToFailureReason(int status) { + switch (status) { + case INSTALL_FAILED_ALREADY_EXISTS: return CommitCallback.FAILURE_CONFLICT; + case INSTALL_FAILED_INVALID_APK: return CommitCallback.FAILURE_INVALID; + case INSTALL_FAILED_INVALID_URI: return CommitCallback.FAILURE_INVALID; + case INSTALL_FAILED_INSUFFICIENT_STORAGE: return CommitCallback.FAILURE_STORAGE; + case INSTALL_FAILED_DUPLICATE_PACKAGE: return CommitCallback.FAILURE_CONFLICT; + case INSTALL_FAILED_NO_SHARED_USER: return CommitCallback.FAILURE_CONFLICT; + case INSTALL_FAILED_UPDATE_INCOMPATIBLE: return CommitCallback.FAILURE_CONFLICT; + case INSTALL_FAILED_SHARED_USER_INCOMPATIBLE: return CommitCallback.FAILURE_CONFLICT; + case INSTALL_FAILED_MISSING_SHARED_LIBRARY: return CommitCallback.FAILURE_INCOMPATIBLE; + case INSTALL_FAILED_REPLACE_COULDNT_DELETE: return CommitCallback.FAILURE_CONFLICT; + case INSTALL_FAILED_DEXOPT: return CommitCallback.FAILURE_INVALID; + case INSTALL_FAILED_OLDER_SDK: return CommitCallback.FAILURE_INCOMPATIBLE; + case INSTALL_FAILED_CONFLICTING_PROVIDER: return CommitCallback.FAILURE_CONFLICT; + case INSTALL_FAILED_NEWER_SDK: return CommitCallback.FAILURE_INCOMPATIBLE; + case INSTALL_FAILED_TEST_ONLY: return CommitCallback.FAILURE_INVALID; + case INSTALL_FAILED_CPU_ABI_INCOMPATIBLE: return CommitCallback.FAILURE_INCOMPATIBLE; + case INSTALL_FAILED_MISSING_FEATURE: return CommitCallback.FAILURE_INCOMPATIBLE; + case INSTALL_FAILED_CONTAINER_ERROR: return CommitCallback.FAILURE_STORAGE; + case INSTALL_FAILED_INVALID_INSTALL_LOCATION: return CommitCallback.FAILURE_STORAGE; + case INSTALL_FAILED_MEDIA_UNAVAILABLE: return CommitCallback.FAILURE_STORAGE; + case INSTALL_FAILED_VERIFICATION_TIMEOUT: return CommitCallback.FAILURE_UNKNOWN; + case INSTALL_FAILED_VERIFICATION_FAILURE: return CommitCallback.FAILURE_UNKNOWN; + case INSTALL_FAILED_PACKAGE_CHANGED: return CommitCallback.FAILURE_INVALID; + case INSTALL_FAILED_UID_CHANGED: return CommitCallback.FAILURE_INVALID; + case INSTALL_FAILED_VERSION_DOWNGRADE: return CommitCallback.FAILURE_INVALID; + case INSTALL_PARSE_FAILED_NOT_APK: return CommitCallback.FAILURE_INVALID; + case INSTALL_PARSE_FAILED_BAD_MANIFEST: return CommitCallback.FAILURE_INVALID; + case INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION: return CommitCallback.FAILURE_INVALID; + case INSTALL_PARSE_FAILED_NO_CERTIFICATES: return CommitCallback.FAILURE_INVALID; + case INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES: return CommitCallback.FAILURE_INVALID; + case INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING: return CommitCallback.FAILURE_INVALID; + case INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME: return CommitCallback.FAILURE_INVALID; + case INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID: return CommitCallback.FAILURE_INVALID; + case INSTALL_PARSE_FAILED_MANIFEST_MALFORMED: return CommitCallback.FAILURE_INVALID; + case INSTALL_PARSE_FAILED_MANIFEST_EMPTY: return CommitCallback.FAILURE_INVALID; + case INSTALL_FAILED_INTERNAL_ERROR: return CommitCallback.FAILURE_UNKNOWN; + case INSTALL_FAILED_USER_RESTRICTED: return CommitCallback.FAILURE_INCOMPATIBLE; + case INSTALL_FAILED_DUPLICATE_PERMISSION: return CommitCallback.FAILURE_CONFLICT; + case INSTALL_FAILED_NO_MATCHING_ABIS: return CommitCallback.FAILURE_INCOMPATIBLE; + default: return CommitCallback.FAILURE_UNKNOWN; + } + } + + /** {@hide} */ + public static String deleteStatusToString(int status) { + switch (status) { + case DELETE_SUCCEEDED: return "DELETE_SUCCEEDED"; + case DELETE_FAILED_INTERNAL_ERROR: return "DELETE_FAILED_INTERNAL_ERROR"; + case DELETE_FAILED_DEVICE_POLICY_MANAGER: return "DELETE_FAILED_DEVICE_POLICY_MANAGER"; + case DELETE_FAILED_USER_RESTRICTED: return "DELETE_FAILED_USER_RESTRICTED"; + case DELETE_FAILED_OWNER_BLOCKED: return "DELETE_FAILED_OWNER_BLOCKED"; + default: return Integer.toString(status); + } + } } diff --git a/core/java/android/hardware/camera2/legacy/CameraDeviceState.java b/core/java/android/hardware/camera2/legacy/CameraDeviceState.java index ab7e8449230c..2fa9d8520b8d 100644 --- a/core/java/android/hardware/camera2/legacy/CameraDeviceState.java +++ b/core/java/android/hardware/camera2/legacy/CameraDeviceState.java @@ -65,7 +65,7 @@ public class CameraDeviceState { void onError(int errorCode, RequestHolder holder); void onConfiguring(); void onIdle(); - void onCaptureStarted(RequestHolder holder); + void onCaptureStarted(RequestHolder holder, long timestamp); void onCaptureResult(CameraMetadataNative result, RequestHolder holder); } @@ -125,11 +125,12 @@ public class CameraDeviceState { * </p> * * @param request A {@link RequestHolder} containing the request for the current capture. + * @param timestamp The timestamp of the capture start in nanoseconds. * @return {@link CameraBinderDecorator#NO_ERROR}, or an error if one has occurred. */ - public synchronized int setCaptureStart(final RequestHolder request) { + public synchronized int setCaptureStart(final RequestHolder request, long timestamp) { mCurrentRequest = request; - doStateTransition(STATE_CAPTURING); + doStateTransition(STATE_CAPTURING, timestamp); return mCurrentError; } @@ -180,6 +181,10 @@ public class CameraDeviceState { } private void doStateTransition(int newState) { + doStateTransition(newState, /*timestamp*/0); + } + + private void doStateTransition(int newState, final long timestamp) { if (DEBUG) { if (newState != mCurrentState) { Log.d(TAG, "Transitioning to state " + newState); @@ -250,7 +255,7 @@ public class CameraDeviceState { mCurrentHandler.post(new Runnable() { @Override public void run() { - mCurrentListener.onCaptureStarted(mCurrentRequest); + mCurrentListener.onCaptureStarted(mCurrentRequest, timestamp); } }); } diff --git a/core/java/android/hardware/camera2/legacy/CaptureCollector.java b/core/java/android/hardware/camera2/legacy/CaptureCollector.java new file mode 100644 index 000000000000..a9834d06339b --- /dev/null +++ b/core/java/android/hardware/camera2/legacy/CaptureCollector.java @@ -0,0 +1,453 @@ +/* + * 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.hardware.camera2.legacy; + +import android.hardware.camera2.impl.CameraMetadataNative; +import android.util.Log; +import android.util.Pair; + +import java.util.ArrayDeque; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Collect timestamps and state for each {@link CaptureRequest} as it passes through + * the Legacy camera pipeline. + */ +public class CaptureCollector { + private static final String TAG = "CaptureCollector"; + + private static final boolean DEBUG = Log.isLoggable(LegacyCameraDevice.DEBUG_PROP, Log.DEBUG); + + private static final int FLAG_RECEIVED_JPEG = 1; + private static final int FLAG_RECEIVED_JPEG_TS = 2; + private static final int FLAG_RECEIVED_PREVIEW = 4; + private static final int FLAG_RECEIVED_PREVIEW_TS = 8; + private static final int FLAG_RECEIVED_ALL_JPEG = FLAG_RECEIVED_JPEG | FLAG_RECEIVED_JPEG_TS; + private static final int FLAG_RECEIVED_ALL_PREVIEW = FLAG_RECEIVED_PREVIEW | + FLAG_RECEIVED_PREVIEW_TS; + + private static final int MAX_JPEGS_IN_FLIGHT = 1; + + private class CaptureHolder { + private final RequestHolder mRequest; + private final LegacyRequest mLegacy; + public final boolean needsJpeg; + public final boolean needsPreview; + + private long mTimestamp = 0; + private int mReceivedFlags = 0; + private boolean mHasStarted = false; + + public CaptureHolder(RequestHolder request, LegacyRequest legacyHolder) { + mRequest = request; + mLegacy = legacyHolder; + needsJpeg = request.hasJpegTargets(); + needsPreview = request.hasPreviewTargets(); + } + + public boolean isPreviewCompleted() { + return (mReceivedFlags & FLAG_RECEIVED_ALL_PREVIEW) == FLAG_RECEIVED_ALL_PREVIEW; + } + + public boolean isJpegCompleted() { + return (mReceivedFlags & FLAG_RECEIVED_ALL_JPEG) == FLAG_RECEIVED_ALL_JPEG; + } + + public boolean isCompleted() { + return (needsJpeg == isJpegCompleted()) && (needsPreview == isPreviewCompleted()); + } + + public void tryComplete() { + if (needsPreview && isPreviewCompleted()) { + CaptureCollector.this.onPreviewCompleted(); + } + if (isCompleted()) { + CaptureCollector.this.onRequestCompleted(mRequest, mLegacy, mTimestamp); + } + } + + public void setJpegTimestamp(long timestamp) { + if (DEBUG) { + Log.d(TAG, "setJpegTimestamp - called for request " + mRequest.getRequestId()); + } + if (!needsJpeg) { + throw new IllegalStateException( + "setJpegTimestamp called for capture with no jpeg targets."); + } + if (isCompleted()) { + throw new IllegalStateException( + "setJpegTimestamp called on already completed request."); + } + + mReceivedFlags |= FLAG_RECEIVED_JPEG_TS; + + if (mTimestamp == 0) { + mTimestamp = timestamp; + } + + if (!mHasStarted) { + mHasStarted = true; + CaptureCollector.this.mDeviceState.setCaptureStart(mRequest, mTimestamp); + } + + tryComplete(); + } + + public void setJpegProduced() { + if (DEBUG) { + Log.d(TAG, "setJpegProduced - called for request " + mRequest.getRequestId()); + } + if (!needsJpeg) { + throw new IllegalStateException( + "setJpegProduced called for capture with no jpeg targets."); + } + if (isCompleted()) { + throw new IllegalStateException( + "setJpegProduced called on already completed request."); + } + + mReceivedFlags |= FLAG_RECEIVED_JPEG; + tryComplete(); + } + + public void setPreviewTimestamp(long timestamp) { + if (DEBUG) { + Log.d(TAG, "setPreviewTimestamp - called for request " + mRequest.getRequestId()); + } + if (!needsPreview) { + throw new IllegalStateException( + "setPreviewTimestamp called for capture with no preview targets."); + } + if (isCompleted()) { + throw new IllegalStateException( + "setPreviewTimestamp called on already completed request."); + } + + mReceivedFlags |= FLAG_RECEIVED_PREVIEW_TS; + + if (mTimestamp == 0) { + mTimestamp = timestamp; + } + + if (!needsJpeg) { + if (!mHasStarted) { + mHasStarted = true; + CaptureCollector.this.mDeviceState.setCaptureStart(mRequest, mTimestamp); + } + } + + tryComplete(); + } + + public void setPreviewProduced() { + if (DEBUG) { + Log.d(TAG, "setPreviewProduced - called for request " + mRequest.getRequestId()); + } + if (!needsPreview) { + throw new IllegalStateException( + "setPreviewProduced called for capture with no preview targets."); + } + if (isCompleted()) { + throw new IllegalStateException( + "setPreviewProduced called on already completed request."); + } + + mReceivedFlags |= FLAG_RECEIVED_PREVIEW; + tryComplete(); + } + } + + private final ArrayDeque<CaptureHolder> mJpegCaptureQueue; + private final ArrayDeque<CaptureHolder> mJpegProduceQueue; + private final ArrayDeque<CaptureHolder> mPreviewCaptureQueue; + private final ArrayDeque<CaptureHolder> mPreviewProduceQueue; + + private final ReentrantLock mLock = new ReentrantLock(); + private final Condition mIsEmpty; + private final Condition mPreviewsEmpty; + private final Condition mNotFull; + private final CameraDeviceState mDeviceState; + private final LegacyResultMapper mMapper = new LegacyResultMapper(); + private int mInFlight = 0; + private int mInFlightPreviews = 0; + private final int mMaxInFlight; + + /** + * Create a new {@link CaptureCollector} that can modify the given {@link CameraDeviceState}. + * + * @param maxInFlight max allowed in-flight requests. + * @param deviceState the {@link CameraDeviceState} to update as requests are processed. + */ + public CaptureCollector(int maxInFlight, CameraDeviceState deviceState) { + mMaxInFlight = maxInFlight; + mJpegCaptureQueue = new ArrayDeque<>(MAX_JPEGS_IN_FLIGHT); + mJpegProduceQueue = new ArrayDeque<>(MAX_JPEGS_IN_FLIGHT); + mPreviewCaptureQueue = new ArrayDeque<>(mMaxInFlight); + mPreviewProduceQueue = new ArrayDeque<>(mMaxInFlight); + mIsEmpty = mLock.newCondition(); + mNotFull = mLock.newCondition(); + mPreviewsEmpty = mLock.newCondition(); + mDeviceState = deviceState; + } + + /** + * Queue a new request. + * + * <p> + * For requests that use the Camera1 API preview output stream, this will block if there are + * already {@code maxInFlight} requests in progress (until at least one prior request has + * completed). For requests that use the Camera1 API jpeg callbacks, this will block until + * all prior requests have been completed to avoid stopping preview for + * {@link android.hardware.Camera#takePicture} before prior preview requests have been + * completed. + * </p> + * @param holder the {@link RequestHolder} for this request. + * @param legacy the {@link LegacyRequest} for this request; this will not be mutated. + * @param timeout a timeout to use for this call. + * @param unit the units to use for the timeout. + * @return {@code false} if this method timed out. + * @throws InterruptedException if this thread is interrupted. + */ + public boolean queueRequest(RequestHolder holder, LegacyRequest legacy, long timeout, + TimeUnit unit) + throws InterruptedException { + CaptureHolder h = new CaptureHolder(holder, legacy); + long nanos = unit.toNanos(timeout); + final ReentrantLock lock = this.mLock; + lock.lock(); + try { + if (DEBUG) { + Log.d(TAG, "queueRequest for request " + holder.getRequestId() + + " - " + mInFlight + " requests remain in flight."); + } + if (h.needsJpeg) { + // Wait for all current requests to finish before queueing jpeg. + while (mInFlight > 0) { + if (nanos <= 0) { + return false; + } + nanos = mIsEmpty.awaitNanos(nanos); + } + mJpegCaptureQueue.add(h); + mJpegProduceQueue.add(h); + } + if (h.needsPreview) { + while (mInFlight >= mMaxInFlight) { + if (nanos <= 0) { + return false; + } + nanos = mNotFull.awaitNanos(nanos); + } + mPreviewCaptureQueue.add(h); + mPreviewProduceQueue.add(h); + mInFlightPreviews++; + } + + if (!(h.needsJpeg || h.needsPreview)) { + throw new IllegalStateException("Request must target at least one output surface!"); + } + + mInFlight++; + return true; + } finally { + lock.unlock(); + } + } + + /** + * Wait all queued requests to complete. + * + * @param timeout a timeout to use for this call. + * @param unit the units to use for the timeout. + * @return {@code false} if this method timed out. + * @throws InterruptedException if this thread is interrupted. + */ + public boolean waitForEmpty(long timeout, TimeUnit unit) throws InterruptedException { + long nanos = unit.toNanos(timeout); + final ReentrantLock lock = this.mLock; + lock.lock(); + try { + while (mInFlight > 0) { + if (nanos <= 0) { + return false; + } + nanos = mIsEmpty.awaitNanos(nanos); + } + return true; + } finally { + lock.unlock(); + } + } + + /** + * Wait all queued requests that use the Camera1 API preview output to complete. + * + * @param timeout a timeout to use for this call. + * @param unit the units to use for the timeout. + * @return {@code false} if this method timed out. + * @throws InterruptedException if this thread is interrupted. + */ + public boolean waitForPreviewsEmpty(long timeout, TimeUnit unit) throws InterruptedException { + long nanos = unit.toNanos(timeout); + final ReentrantLock lock = this.mLock; + lock.lock(); + try { + while (mInFlightPreviews > 0) { + if (nanos <= 0) { + return false; + } + nanos = mPreviewsEmpty.awaitNanos(nanos); + } + return true; + } finally { + lock.unlock(); + } + } + + /** + * Called to alert the {@link CaptureCollector} that the jpeg capture has begun. + * + * @param timestamp the time of the jpeg capture. + * @return the {@link RequestHolder} for the request associated with this capture. + */ + public RequestHolder jpegCaptured(long timestamp) { + final ReentrantLock lock = this.mLock; + lock.lock(); + try { + CaptureHolder h = mJpegCaptureQueue.poll(); + if (h == null) { + Log.w(TAG, "jpegCaptured called with no jpeg request on queue!"); + return null; + } + h.setJpegTimestamp(timestamp); + return h.mRequest; + } finally { + lock.unlock(); + } + } + + /** + * Called to alert the {@link CaptureCollector} that the jpeg capture has completed. + * + * @return a pair containing the {@link RequestHolder} and the timestamp of the capture. + */ + public Pair<RequestHolder, Long> jpegProduced() { + final ReentrantLock lock = this.mLock; + lock.lock(); + try { + CaptureHolder h = mJpegProduceQueue.poll(); + if (h == null) { + Log.w(TAG, "jpegProduced called with no jpeg request on queue!"); + return null; + } + h.setJpegProduced(); + return new Pair<>(h.mRequest, h.mTimestamp); + } finally { + lock.unlock(); + } + } + + /** + * Check if there are any pending capture requests that use the Camera1 API preview output. + * + * @return {@code true} if there are pending preview requests. + */ + public boolean hasPendingPreviewCaptures() { + final ReentrantLock lock = this.mLock; + lock.lock(); + try { + return !mPreviewCaptureQueue.isEmpty(); + } finally { + lock.unlock(); + } + } + + /** + * Called to alert the {@link CaptureCollector} that the preview capture has begun. + * + * @param timestamp the time of the preview capture. + * @return a pair containing the {@link RequestHolder} and the timestamp of the capture. + */ + public Pair<RequestHolder, Long> previewCaptured(long timestamp) { + final ReentrantLock lock = this.mLock; + lock.lock(); + try { + CaptureHolder h = mPreviewCaptureQueue.poll(); + if (h == null) { + Log.w(TAG, "previewCaptured called with no preview request on queue!"); + return null; + } + h.setPreviewTimestamp(timestamp); + return new Pair<>(h.mRequest, h.mTimestamp); + } finally { + lock.unlock(); + } + } + + /** + * Called to alert the {@link CaptureCollector} that the preview capture has completed. + * + * @return the {@link RequestHolder} for the request associated with this capture. + */ + public RequestHolder previewProduced() { + final ReentrantLock lock = this.mLock; + lock.lock(); + try { + CaptureHolder h = mPreviewProduceQueue.poll(); + if (h == null) { + Log.w(TAG, "previewProduced called with no preview request on queue!"); + return null; + } + h.setPreviewProduced(); + return h.mRequest; + } finally { + lock.unlock(); + } + } + + private void onPreviewCompleted() { + mInFlightPreviews--; + if (mInFlightPreviews < 0) { + throw new IllegalStateException( + "More preview captures completed than requests queued."); + } + if (mInFlightPreviews == 0) { + mPreviewsEmpty.signalAll(); + } + } + + private void onRequestCompleted(RequestHolder request, LegacyRequest legacyHolder, + long timestamp) { + mInFlight--; + if (DEBUG) { + Log.d(TAG, "Completed request " + request.getRequestId() + + ", " + mInFlight + " requests remain in flight."); + } + if (mInFlight < 0) { + throw new IllegalStateException( + "More captures completed than requests queued."); + } + mNotFull.signalAll(); + if (mInFlight == 0) { + mIsEmpty.signalAll(); + } + CameraMetadataNative result = mMapper.cachedConvertResultMetadata( + legacyHolder, timestamp); + mDeviceState.setCaptureResult(request, result); + } +} diff --git a/core/java/android/hardware/camera2/legacy/GLThreadManager.java b/core/java/android/hardware/camera2/legacy/GLThreadManager.java index 5d44fd28609d..06521cf16f59 100644 --- a/core/java/android/hardware/camera2/legacy/GLThreadManager.java +++ b/core/java/android/hardware/camera2/legacy/GLThreadManager.java @@ -25,6 +25,8 @@ import android.view.Surface; import java.util.Collection; +import static com.android.internal.util.Preconditions.*; + /** * GLThreadManager handles the thread used for rendering into the configured output surfaces. */ @@ -38,6 +40,8 @@ public class GLThreadManager { private static final int MSG_DROP_FRAMES = 4; private static final int MSG_ALLOW_FRAMES = 5; + private CaptureCollector mCaptureCollector; + private final SurfaceTextureRenderer mTextureRenderer; private final RequestHandlerThread mGLHandlerThread; @@ -51,10 +55,13 @@ public class GLThreadManager { private static class ConfigureHolder { public final ConditionVariable condition; public final Collection<Surface> surfaces; + public final CaptureCollector collector; - public ConfigureHolder(ConditionVariable condition, Collection<Surface> surfaces) { + public ConfigureHolder(ConditionVariable condition, Collection<Surface> surfaces, + CaptureCollector collector) { this.condition = condition; this.surfaces = surfaces; + this.collector = collector; } } @@ -74,6 +81,7 @@ public class GLThreadManager { ConfigureHolder configure = (ConfigureHolder) msg.obj; mTextureRenderer.cleanupEGLContext(); mTextureRenderer.configureSurfaces(configure.surfaces); + mCaptureCollector = checkNotNull(configure.collector); configure.condition.open(); mConfigured = true; break; @@ -88,7 +96,7 @@ public class GLThreadManager { if (!mConfigured) { Log.e(TAG, "Dropping frame, EGL context not configured!"); } - mTextureRenderer.drawIntoSurfaces((Collection<Surface>) msg.obj); + mTextureRenderer.drawIntoSurfaces(mCaptureCollector); break; case MSG_CLEANUP: mTextureRenderer.cleanupEGLContext(); @@ -158,16 +166,11 @@ public class GLThreadManager { } /** - * Queue a new call to draw into a given set of surfaces. - * - * <p> - * The set of surfaces passed here must be a subset of the set of surfaces passed in - * the last call to {@link #setConfigurationAndWait}. - * </p> - * - * @param targets a collection of {@link android.view.Surface}s to draw into. + * Queue a new call to draw into the surfaces specified in the next available preview + * request from the {@link CaptureCollector} passed to + * {@link #setConfigurationAndWait(java.util.Collection, CaptureCollector)}; */ - public void queueNewFrame(Collection<Surface> targets) { + public void queueNewFrame() { Handler handler = mGLHandlerThread.getHandler(); /** @@ -175,7 +178,7 @@ public class GLThreadManager { * are produced, drop frames rather than allowing the queue to back up. */ if (!handler.hasMessages(MSG_NEW_FRAME)) { - handler.sendMessage(handler.obtainMessage(MSG_NEW_FRAME, targets)); + handler.sendMessage(handler.obtainMessage(MSG_NEW_FRAME)); } else { Log.e(TAG, "GLThread dropping frame. Not consuming frames quickly enough!"); } @@ -186,12 +189,14 @@ public class GLThreadManager { * this configuration has been applied. * * @param surfaces a collection of {@link android.view.Surface}s to configure. + * @param collector a {@link CaptureCollector} to retrieve requests from. */ - public void setConfigurationAndWait(Collection<Surface> surfaces) { + public void setConfigurationAndWait(Collection<Surface> surfaces, CaptureCollector collector) { + checkNotNull(collector, "collector must not be null"); Handler handler = mGLHandlerThread.getHandler(); final ConditionVariable condition = new ConditionVariable(/*closed*/false); - ConfigureHolder configure = new ConfigureHolder(condition, surfaces); + ConfigureHolder configure = new ConfigureHolder(condition, surfaces, collector); Message m = handler.obtainMessage(MSG_NEW_CONFIGURATION, /*arg1*/0, /*arg2*/0, configure); handler.sendMessage(m); diff --git a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java index 71d3d4b20289..cbf4a3d1bb06 100644 --- a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java +++ b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java @@ -135,10 +135,9 @@ public class LegacyCameraDevice implements AutoCloseable { } @Override - public void onCaptureStarted(RequestHolder holder) { + public void onCaptureStarted(RequestHolder holder, final long timestamp) { final CaptureResultExtras extras = getExtrasFromRequest(holder); - final long timestamp = System.nanoTime(); mResultHandler.post(new Runnable() { @Override public void run() { @@ -146,7 +145,6 @@ public class LegacyCameraDevice implements AutoCloseable { Log.d(TAG, "doing onCaptureStarted callback."); } try { - // TODO: Don't fake timestamp mDeviceCallbacks.onCaptureStarted(extras, timestamp); } catch (RemoteException e) { throw new IllegalStateException( @@ -167,7 +165,6 @@ public class LegacyCameraDevice implements AutoCloseable { Log.d(TAG, "doing onCaptureResult callback."); } try { - // TODO: Don't fake metadata mDeviceCallbacks.onResultReceived(result, extras); } catch (RemoteException e) { throw new IllegalStateException( @@ -483,6 +480,12 @@ public class LegacyCameraDevice implements AutoCloseable { return new Size(dimens[0], dimens[1]); } + static void setNextTimestamp(Surface surface, long timestamp) + throws BufferQueueAbandonedException { + checkNotNull(surface); + LegacyExceptionUtils.throwOnError(nativeSetNextTimestamp(surface, timestamp)); + } + private static native int nativeDetectSurfaceType(Surface surface); private static native int nativeDetectSurfaceDimens(Surface surface, @@ -506,4 +509,5 @@ public class LegacyCameraDevice implements AutoCloseable { private static native int nativeDetectTextureDimens(SurfaceTexture surfaceTexture, /*out*/int[/*2*/] dimens); + private static native int nativeSetNextTimestamp(Surface surface, long timestamp); } diff --git a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java index cc7a90eb8d19..066b416f05e2 100644 --- a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java +++ b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java @@ -38,6 +38,8 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.TimeUnit; import static com.android.internal.util.Preconditions.*; @@ -62,22 +64,20 @@ public class RequestThreadManager { private final CameraCharacteristics mCharacteristics; private final CameraDeviceState mDeviceState; + private final CaptureCollector mCaptureCollector; private static final int MSG_CONFIGURE_OUTPUTS = 1; private static final int MSG_SUBMIT_CAPTURE_REQUEST = 2; private static final int MSG_CLEANUP = 3; + private static final int MAX_IN_FLIGHT_REQUESTS = 2; + private static final int PREVIEW_FRAME_TIMEOUT = 300; // ms private static final int JPEG_FRAME_TIMEOUT = 3000; // ms (same as CTS for API2) private static final float ASPECT_RATIO_TOLERANCE = 0.01f; private boolean mPreviewRunning = false; - private volatile long mLastJpegTimestamp; - private volatile long mLastPreviewTimestamp; - private volatile RequestHolder mInFlightPreview; - private volatile RequestHolder mInFlightJpeg; - private final List<Surface> mPreviewOutputs = new ArrayList<>(); private final List<Surface> mCallbackOutputs = new ArrayList<>(); private GLThreadManager mGLThreadManager; @@ -167,16 +167,16 @@ public class RequestThreadManager { } private final ConditionVariable mReceivedJpeg = new ConditionVariable(false); - private final ConditionVariable mReceivedPreview = new ConditionVariable(false); private final Camera.PictureCallback mJpegCallback = new Camera.PictureCallback() { @Override public void onPictureTaken(byte[] data, Camera camera) { Log.i(TAG, "Received jpeg."); - RequestHolder holder = mInFlightJpeg; + Pair<RequestHolder, Long> captureInfo = mCaptureCollector.jpegProduced(); + RequestHolder holder = captureInfo.first; + long timestamp = captureInfo.second; if (holder == null) { - Log.w(TAG, "Dropping jpeg frame."); - mInFlightJpeg = null; + Log.e(TAG, "Dropping jpeg frame."); return; } for (Surface s : holder.getHolderTargets()) { @@ -184,6 +184,7 @@ public class RequestThreadManager { if (RequestHolder.jpegType(s)) { Log.i(TAG, "Producing jpeg buffer..."); LegacyCameraDevice.setSurfaceDimens(s, data.length, /*height*/1); + LegacyCameraDevice.setNextTimestamp(s, timestamp); LegacyCameraDevice.produceFrame(s, data, data.length, /*height*/1, CameraMetadataNative.NATIVE_JPEG_FORMAT); } @@ -191,6 +192,7 @@ public class RequestThreadManager { Log.w(TAG, "Surface abandoned, dropping frame. ", e); } } + mReceivedJpeg.open(); } }; @@ -198,7 +200,7 @@ public class RequestThreadManager { private final Camera.ShutterCallback mJpegShutterCallback = new Camera.ShutterCallback() { @Override public void onShutter() { - mLastJpegTimestamp = SystemClock.elapsedRealtimeNanos(); + mCaptureCollector.jpegCaptured(SystemClock.elapsedRealtimeNanos()); } }; @@ -206,29 +208,10 @@ public class RequestThreadManager { new SurfaceTexture.OnFrameAvailableListener() { @Override public void onFrameAvailable(SurfaceTexture surfaceTexture) { - RequestHolder holder = mInFlightPreview; - if (DEBUG) { mPrevCounter.countAndLog(); } - - if (holder == null) { - mGLThreadManager.queueNewFrame(null); - Log.w(TAG, "Dropping preview frame."); - return; - } - - mInFlightPreview = null; - - if (holder.hasPreviewTargets()) { - mGLThreadManager.queueNewFrame(holder.getHolderTargets()); - } - - /** - * TODO: Get timestamp from GL thread after buffer update. - */ - mLastPreviewTimestamp = surfaceTexture.getTimestamp(); - mReceivedPreview.open(); + mGLThreadManager.queueNewFrame(); } }; @@ -256,14 +239,11 @@ public class RequestThreadManager { mCamera.setPreviewTexture(mDummyTexture); startPreview(); } - mInFlightJpeg = request; - // TODO: Hook up shutter callback to CameraDeviceStateListener#onCaptureStarted mCamera.takePicture(mJpegShutterCallback, /*raw*/null, mJpegCallback); mPreviewRunning = false; } private void doPreviewCapture(RequestHolder request) throws IOException { - mInFlightPreview = request; if (mPreviewRunning) { return; // Already running } @@ -290,8 +270,6 @@ public class RequestThreadManager { mPreviewOutputs.clear(); mCallbackOutputs.clear(); mPreviewTexture = null; - mInFlightPreview = null; - mInFlightJpeg = null; int facing = mCharacteristics.get(CameraCharacteristics.LENS_FACING); int orientation = mCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); @@ -389,7 +367,7 @@ public class RequestThreadManager { mGLThreadManager.start(); } mGLThreadManager.waitUntilStarted(); - mGLThreadManager.setConfigurationAndWait(mPreviewOutputs); + mGLThreadManager.setConfigurationAndWait(mPreviewOutputs, mCaptureCollector); mGLThreadManager.allowNewFrames(); mPreviewTexture = mGLThreadManager.getCurrentSurfaceTexture(); if (mPreviewTexture != null) { @@ -553,6 +531,18 @@ public class RequestThreadManager { int sizes = config.surfaces != null ? config.surfaces.size() : 0; Log.i(TAG, "Configure outputs: " + sizes + " surfaces configured."); + + try { + boolean success = mCaptureCollector.waitForEmpty(JPEG_FRAME_TIMEOUT, + TimeUnit.MILLISECONDS); + if (!success) { + Log.e(TAG, "Timed out while queueing configure request."); + } + } catch (InterruptedException e) { + // TODO: report error to CameraDevice + Log.e(TAG, "Interrupted while waiting for requests to complete."); + } + try { configureOutputs(config.surfaces); } catch (IOException e) { @@ -571,6 +561,16 @@ public class RequestThreadManager { // Get the next burst from the request queue. Pair<BurstHolder, Long> nextBurst = mRequestQueue.getNext(); if (nextBurst == null) { + try { + boolean success = mCaptureCollector.waitForEmpty(JPEG_FRAME_TIMEOUT, + TimeUnit.MILLISECONDS); + if (!success) { + Log.e(TAG, "Timed out while waiting for empty."); + } + } catch (InterruptedException e) { + // TODO: report error to CameraDevice + Log.e(TAG, "Interrupted while waiting for requests to complete."); + } mDeviceState.setIdle(); stopPreview(); break; @@ -603,39 +603,41 @@ public class RequestThreadManager { if (!mParams.same(legacyRequest.parameters)) { mParams = legacyRequest.parameters; mCamera.setParameters(mParams); + paramsChanged = true; } } - mDeviceState.setCaptureStart(holder); - long timestamp = 0; try { + boolean success = mCaptureCollector.queueRequest(holder, + mLastRequest, JPEG_FRAME_TIMEOUT, TimeUnit.MILLISECONDS); + + if (!success) { + Log.e(TAG, "Timed out while queueing capture request."); + } if (holder.hasPreviewTargets()) { - mReceivedPreview.close(); doPreviewCapture(holder); - if (!mReceivedPreview.block(PREVIEW_FRAME_TIMEOUT)) { - // TODO: report error to CameraDevice - Log.e(TAG, "Hit timeout for preview callback!"); - } - timestamp = mLastPreviewTimestamp; } if (holder.hasJpegTargets()) { + success = mCaptureCollector. + waitForPreviewsEmpty(PREVIEW_FRAME_TIMEOUT * + MAX_IN_FLIGHT_REQUESTS, TimeUnit.MILLISECONDS); + if (!success) { + Log.e(TAG, "Timed out waiting for prior requests to complete."); + } mReceivedJpeg.close(); doJpegCapture(holder); if (!mReceivedJpeg.block(JPEG_FRAME_TIMEOUT)) { // TODO: report error to CameraDevice Log.e(TAG, "Hit timeout for jpeg callback!"); } - mInFlightJpeg = null; - timestamp = mLastJpegTimestamp; } } catch (IOException e) { - // TODO: err handling + // TODO: report error to CameraDevice throw new IOError(e); - } - - if (timestamp == 0) { - timestamp = SystemClock.elapsedRealtimeNanos(); + } catch (InterruptedException e) { + // TODO: report error to CameraDevice + Log.e(TAG, "Interrupted during capture.", e); } if (paramsChanged) { @@ -647,11 +649,6 @@ public class RequestThreadManager { // Update parameters to the latest that we think the camera is using mLastRequest.setParameters(mParams); } - - - CameraMetadataNative result = mMapper.cachedConvertResultMetadata( - mLastRequest, timestamp); - mDeviceState.setCaptureResult(holder, result); } if (DEBUG) { long totalTime = SystemClock.elapsedRealtimeNanos() - startTime; @@ -661,6 +658,16 @@ public class RequestThreadManager { break; case MSG_CLEANUP: mCleanup = true; + try { + boolean success = mCaptureCollector.waitForEmpty(JPEG_FRAME_TIMEOUT, + TimeUnit.MILLISECONDS); + if (!success) { + Log.e(TAG, "Timed out while queueing cleanup request."); + } + } catch (InterruptedException e) { + // TODO: report error to CameraDevice + Log.e(TAG, "Interrupted while waiting for requests to complete."); + } if (mGLThreadManager != null) { mGLThreadManager.quit(); } @@ -693,6 +700,7 @@ public class RequestThreadManager { String name = String.format("RequestThread-%d", cameraId); TAG = name; mDeviceState = checkNotNull(deviceState, "deviceState must not be null"); + mCaptureCollector = new CaptureCollector(MAX_IN_FLIGHT_REQUESTS, mDeviceState); mRequestThread = new RequestHandlerThread(name, mRequestHandlerCb); } diff --git a/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java b/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java index fdf9ba06d41d..068726417a0f 100644 --- a/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java +++ b/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java @@ -28,6 +28,7 @@ import android.opengl.GLES20; import android.opengl.Matrix; import android.text.format.Time; import android.util.Log; +import android.util.Pair; import android.util.Size; import android.view.Surface; import android.os.SystemProperties; @@ -599,41 +600,63 @@ public class SurfaceTextureRenderer { /** * Draw the current buffer in the {@link SurfaceTexture} returned from - * {@link #getSurfaceTexture()} into the given set of target surfaces. + * {@link #getSurfaceTexture()} into the set of target {@link Surface}s + * in the next request from the given {@link CaptureCollector}, or drop + * the frame if none is available. * * <p> - * The given surfaces must be a subset of the surfaces set in the last - * {@link #configureSurfaces(java.util.Collection)} call. + * Any {@link Surface}s targeted must be a subset of the {@link Surface}s + * set in the last {@link #configureSurfaces(java.util.Collection)} call. * </p> * - * @param targetSurfaces the surfaces to draw to. + * @param targetCollector the surfaces to draw to. */ - public void drawIntoSurfaces(Collection<Surface> targetSurfaces) { + public void drawIntoSurfaces(CaptureCollector targetCollector) { if ((mSurfaces == null || mSurfaces.size() == 0) && (mConversionSurfaces == null || mConversionSurfaces.size() == 0)) { return; } + boolean doTiming = targetCollector.hasPendingPreviewCaptures(); checkGlError("before updateTexImage"); - if (targetSurfaces == null) { - mSurfaceTexture.updateTexImage(); - return; + if (doTiming) { + beginGlTiming(); } - beginGlTiming(); - mSurfaceTexture.updateTexImage(); long timestamp = mSurfaceTexture.getTimestamp(); - addGlTimestamp(timestamp); + + Pair<RequestHolder, Long> captureHolder = targetCollector.previewCaptured(timestamp); + + // No preview request queued, drop frame. + if (captureHolder == null) { + Log.w(TAG, "Dropping preview frame."); + if (doTiming) { + endGlTiming(); + } + return; + } + + RequestHolder request = captureHolder.first; + + Collection<Surface> targetSurfaces = request.getHolderTargets(); + if (doTiming) { + addGlTimestamp(timestamp); + } List<Long> targetSurfaceIds = LegacyCameraDevice.getSurfaceIds(targetSurfaces); for (EGLSurfaceHolder holder : mSurfaces) { if (LegacyCameraDevice.containsSurfaceId(holder.surface, targetSurfaceIds)) { makeCurrent(holder.eglSurface); - drawFrame(mSurfaceTexture, holder.width, holder.height); - swapBuffers(holder.eglSurface); + try { + LegacyCameraDevice.setNextTimestamp(holder.surface, captureHolder.second); + drawFrame(mSurfaceTexture, holder.width, holder.height); + swapBuffers(holder.eglSurface); + } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) { + Log.w(TAG, "Surface abandoned, dropping frame. ", e); + } } } for (EGLSurfaceHolder holder : mConversionSurfaces) { @@ -647,6 +670,7 @@ public class SurfaceTextureRenderer { try { int format = LegacyCameraDevice.detectSurfaceType(holder.surface); + LegacyCameraDevice.setNextTimestamp(holder.surface, captureHolder.second); LegacyCameraDevice.produceFrame(holder.surface, mPBufferPixels.array(), holder.width, holder.height, format); swapBuffers(holder.eglSurface); @@ -655,8 +679,11 @@ public class SurfaceTextureRenderer { } } } + targetCollector.previewProduced(); - endGlTiming(); + if (doTiming) { + endGlTiming(); + } } /** diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java index d0dbd96b3d4d..762e1ee25774 100644 --- a/core/java/android/provider/ContactsContract.java +++ b/core/java/android/provider/ContactsContract.java @@ -6802,6 +6802,21 @@ public final class ContactsContract { default: return com.android.internal.R.string.eventTypeCustom; } } + + /** + * Return a {@link CharSequence} that best describes the given type, + * possibly substituting the given {@link #LABEL} value + * for {@link #TYPE_CUSTOM}. + */ + public static final CharSequence getTypeLabel(Resources res, int type, + CharSequence label) { + if (type == TYPE_CUSTOM && !TextUtils.isEmpty(label)) { + return label; + } else { + final int labelRes = getTypeResource(type); + return res.getText(labelRes); + } + } } /** diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java index 4bfcaff0826f..a36f06c79a58 100644 --- a/core/java/android/text/Layout.java +++ b/core/java/android/text/Layout.java @@ -926,7 +926,7 @@ public abstract class Layout { public float getLineMax(int line) { float margin = getParagraphLeadingMargin(line); float signedExtent = getLineExtent(line, false); - return margin + signedExtent >= 0 ? signedExtent : -signedExtent; + return margin + (signedExtent >= 0 ? signedExtent : -signedExtent); } /** @@ -936,7 +936,7 @@ public abstract class Layout { public float getLineWidth(int line) { float margin = getParagraphLeadingMargin(line); float signedExtent = getLineExtent(line, true); - return margin + signedExtent >= 0 ? signedExtent : -signedExtent; + return margin + (signedExtent >= 0 ? signedExtent : -signedExtent); } /** @@ -1571,6 +1571,16 @@ public abstract class Layout { int len = mt.mLen; boolean hasTabs = false; TabStops tabStops = null; + // leading margins should be taken into account when measuring a paragraph + int margin = 0; + if (text instanceof Spanned) { + Spanned spanned = (Spanned) text; + LeadingMarginSpan[] spans = getParagraphSpans(spanned, start, end, + LeadingMarginSpan.class); + for (LeadingMarginSpan lms : spans) { + margin += lms.getLeadingMargin(true); + } + } for (int i = 0; i < len; ++i) { if (chars[i] == '\t') { hasTabs = true; @@ -1588,7 +1598,7 @@ public abstract class Layout { } } tl.set(paint, text, start, end, dir, directions, hasTabs, tabStops); - return tl.metrics(null); + return margin + tl.metrics(null); } finally { TextLine.recycle(tl); MeasuredText.recycle(mt); diff --git a/core/java/android/transition/TransitionSet.java b/core/java/android/transition/TransitionSet.java index 363f97f34586..83c60af60880 100644 --- a/core/java/android/transition/TransitionSet.java +++ b/core/java/android/transition/TransitionSet.java @@ -371,6 +371,8 @@ public class TransitionSet extends Transition { private TransitionValuesMaps removeExcludes(TransitionValuesMaps values) { if (mTargetIds.isEmpty() && mTargetIdExcludes == null && mTargetTypeExcludes == null + && mTargetNames == null && mTargetTypes == null + && mTargetExcludes == null && mTargetNameExcludes == null && mTargets.isEmpty()) { return values; } diff --git a/core/java/android/widget/AbsSeekBar.java b/core/java/android/widget/AbsSeekBar.java index be7e0bc42909..2708525683b0 100644 --- a/core/java/android/widget/AbsSeekBar.java +++ b/core/java/android/widget/AbsSeekBar.java @@ -16,6 +16,7 @@ package android.widget; +import android.animation.ObjectAnimator; import android.annotation.Nullable; import android.content.Context; import android.content.res.ColorStateList; @@ -63,6 +64,9 @@ public abstract class AbsSeekBar extends ProgressBar { * progress. */ private int mKeyProgressIncrement = 1; + private ObjectAnimator mPositionAnimator; + private static final int PROGRESS_ANIMATION_DURATION = 250; + private static final int NO_ALPHA = 0xFF; private float mDisabledAlpha; @@ -361,18 +365,17 @@ public abstract class AbsSeekBar extends ProgressBar { void onProgressRefresh(float scale, boolean fromUser) { super.onProgressRefresh(scale, fromUser); - final Drawable thumb = mThumb; - if (thumb != null) { - setThumbPos(getWidth(), thumb, scale, Integer.MIN_VALUE); - - // Since we draw translated, the drawable's bounds that it signals - // for invalidation won't be the actual bounds we want invalidated, - // so just invalidate this whole view. - invalidate(); + if (!isAnimationRunning()) { + setThumbPos(scale); } } @Override + void onAnimatePosition(float scale, boolean fromUser) { + setThumbPos(scale); + } + + @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); @@ -414,6 +417,18 @@ public abstract class AbsSeekBar extends ProgressBar { return max > 0 ? getProgress() / (float) max : 0; } + private void setThumbPos(float scale) { + final Drawable thumb = mThumb; + if (thumb != null) { + setThumbPos(getWidth(), thumb, scale, Integer.MIN_VALUE); + // Since we draw translated, the drawable's bounds that it signals + // for invalidation won't be the actual bounds we want invalidated, + // so just invalidate this whole view. + invalidate(); + + } + } + /** * Updates the thumb drawable bounds. * @@ -676,13 +691,13 @@ public abstract class AbsSeekBar extends ProgressBar { switch (keyCode) { case KeyEvent.KEYCODE_DPAD_LEFT: if (progress <= 0) break; - setProgress(progress - mKeyProgressIncrement, true); + animateSetProgress(progress - mKeyProgressIncrement); onKeyChange(); return true; case KeyEvent.KEYCODE_DPAD_RIGHT: if (progress >= getMax()) break; - setProgress(progress + mKeyProgressIncrement, true); + animateSetProgress(progress + mKeyProgressIncrement); onKeyChange(); return true; } @@ -691,6 +706,38 @@ public abstract class AbsSeekBar extends ProgressBar { return super.onKeyDown(keyCode, event); } + boolean isAnimationRunning() { + return mPositionAnimator != null && mPositionAnimator.isRunning(); + } + + /** + * @hide + */ + @Override + public void setProgress(int progress, boolean fromUser) { + if (isAnimationRunning()) { + mPositionAnimator.cancel(); + } + super.setProgress(progress, fromUser); + } + + void animateSetProgress(int progress) { + float curProgress = isAnimationRunning() ? getAnimationPosition() : getProgress(); + + if (progress < 0) { + progress = 0; + } else if (progress > getMax()) { + progress = getMax(); + } + setProgressValueOnly(progress); + + mPositionAnimator = ObjectAnimator.ofFloat(this, "animationPosition", curProgress, + progress); + mPositionAnimator.setDuration(PROGRESS_ANIMATION_DURATION); + mPositionAnimator.setAutoCancel(true); + mPositionAnimator.start(); + } + @Override public void onInitializeAccessibilityEvent(AccessibilityEvent event) { super.onInitializeAccessibilityEvent(event); diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java index 20c1aa4f1481..4a308099d5a3 100644 --- a/core/java/android/widget/ProgressBar.java +++ b/core/java/android/widget/ProgressBar.java @@ -242,6 +242,8 @@ public class ProgressBar extends View { private long mUiThreadId; private boolean mShouldStartAnimationDrawable; + private float mAnimationPosition; + private boolean mInDrawing; private boolean mAttached; private boolean mRefreshIsPosted; @@ -259,7 +261,7 @@ public class ProgressBar extends View { public ProgressBar(Context context) { this(context, null); } - + public ProgressBar(Context context, AttributeSet attrs) { this(context, attrs, com.android.internal.R.attr.progressBarStyle); } @@ -276,9 +278,9 @@ public class ProgressBar extends View { final TypedArray a = context.obtainStyledAttributes( attrs, R.styleable.ProgressBar, defStyleAttr, defStyleRes); - + mNoInvalidate = true; - + final Drawable progressDrawable = a.getDrawable(R.styleable.ProgressBar_progressDrawable); if (progressDrawable != null) { // Calling this method can set mMaxHeight, make sure the corresponding @@ -297,11 +299,11 @@ public class ProgressBar extends View { mBehavior = a.getInt(R.styleable.ProgressBar_indeterminateBehavior, mBehavior); final int resID = a.getResourceId( - com.android.internal.R.styleable.ProgressBar_interpolator, + com.android.internal.R.styleable.ProgressBar_interpolator, android.R.anim.linear_interpolator); // default to linear interpolator if (resID > 0) { setInterpolator(context, resID); - } + } setMax(a.getInt(R.styleable.ProgressBar_max, mMax)); @@ -386,12 +388,12 @@ public class ProgressBar extends View { * traverse layer and state list drawables. */ private Drawable tileify(Drawable drawable, boolean clip) { - + if (drawable instanceof LayerDrawable) { LayerDrawable background = (LayerDrawable) drawable; final int N = background.getNumberOfLayers(); Drawable[] outDrawables = new Drawable[N]; - + for (int i = 0; i < N; i++) { int id = background.getId(i); outDrawables[i] = tileify(background.getDrawable(i), @@ -399,13 +401,13 @@ public class ProgressBar extends View { } LayerDrawable newBg = new LayerDrawable(outDrawables); - + for (int i = 0; i < N; i++) { newBg.setId(i, background.getId(i)); } - + return newBg; - + } else if (drawable instanceof StateListDrawable) { StateListDrawable in = (StateListDrawable) drawable; StateListDrawable out = new StateListDrawable(); @@ -414,7 +416,7 @@ public class ProgressBar extends View { out.addState(in.getStateSet(i), tileify(in.getStateDrawable(i), clip)); } return out; - + } else if (drawable instanceof BitmapDrawable) { final BitmapDrawable bitmap = (BitmapDrawable) drawable; final Bitmap tileBitmap = bitmap.getBitmap(); @@ -434,7 +436,7 @@ public class ProgressBar extends View { return clip ? new ClipDrawable( shapeDrawable, Gravity.LEFT, ClipDrawable.HORIZONTAL) : shapeDrawable; } - + return drawable; } @@ -442,7 +444,7 @@ public class ProgressBar extends View { final float[] roundedCorners = new float[] { 5, 5, 5, 5, 5, 5, 5, 5 }; return new RoundRectShape(roundedCorners, null, null); } - + /** * Convert a AnimationDrawable for use as a barberpole animation. * Each frame of the animation is wrapped in a ClipDrawable and @@ -454,7 +456,7 @@ public class ProgressBar extends View { final int N = background.getNumberOfFrames(); AnimationDrawable newBg = new AnimationDrawable(); newBg.setOneShot(background.isOneShot()); - + for (int i = 0; i < N; i++) { Drawable frame = tileify(background.getFrame(i), true); frame.setLevel(10000); @@ -465,7 +467,7 @@ public class ProgressBar extends View { } return drawable; } - + /** * <p> * Initialize the progress bar's default values: @@ -506,7 +508,7 @@ public class ProgressBar extends View { * <p>Change the indeterminate mode for this progress bar. In indeterminate * mode, the progress is ignored and the progress bar shows an infinite * animation instead.</p> - * + * * If this progress bar's style only supports indeterminate mode (such as the circular * progress bars), then this will be ignored. * @@ -657,7 +659,7 @@ public class ProgressBar extends View { setIndeterminateDrawable(d); } - + /** * <p>Get the drawable used to draw the progress bar in * progress mode.</p> @@ -963,7 +965,7 @@ public class ProgressBar extends View { setProgressDrawable(d); } - + /** * @return The drawable currently used to draw the progress bar */ @@ -1014,7 +1016,7 @@ public class ProgressBar extends View { final int count = mRefreshData.size(); for (int i = 0; i < count; i++) { final RefreshData rd = mRefreshData.get(i); - doRefreshProgress(rd.id, rd.progress, rd.fromUser, true); + doRefreshProgress(rd.id, rd.progress, rd.fromUser, true, rd.animate); rd.recycle(); } mRefreshData.clear(); @@ -1029,10 +1031,12 @@ public class ProgressBar extends View { new SynchronizedPool<RefreshData>(POOL_MAX); public int id; - public int progress; + public float progress; public boolean fromUser; + public boolean animate; - public static RefreshData obtain(int id, int progress, boolean fromUser) { + public static RefreshData obtain(int id, float progress, boolean fromUser, + boolean animate) { RefreshData rd = sPool.acquire(); if (rd == null) { rd = new RefreshData(); @@ -1040,9 +1044,10 @@ public class ProgressBar extends View { rd.id = id; rd.progress = progress; rd.fromUser = fromUser; + rd.animate = animate; return rd; } - + public void recycle() { sPool.release(this); } @@ -1064,9 +1069,19 @@ public class ProgressBar extends View { layer.mutate().setTint(tint, tintMode); } - private synchronized void doRefreshProgress(int id, int progress, boolean fromUser, + private float getScale(float progress) { + return mMax > 0 ? progress / (float) mMax : 0; + } + + private synchronized void doRefreshProgress(int id, float progress, boolean fromUser, boolean callBackToApp) { - float scale = mMax > 0 ? (float) progress / (float) mMax : 0; + doRefreshProgress(id, progress, fromUser, callBackToApp, false); + } + + private synchronized void doRefreshProgress(int id, float progress, boolean fromUser, + boolean callBackToApp, boolean animate) { + float scale = getScale(progress); + final Drawable d = mCurrentDrawable; if (d != null) { Drawable progressDrawable = null; @@ -1083,27 +1098,65 @@ public class ProgressBar extends View { } else { invalidate(); } - - if (callBackToApp && id == R.id.progress) { - onProgressRefresh(scale, fromUser); + + if (id == R.id.progress) { + if (animate) { + onAnimatePosition(scale, fromUser); + } else if (callBackToApp) { + onProgressRefresh(scale, fromUser); + } } } + /** + * Called when a ProgressBar is animating its position. + * + * @param scale Current position/progress between 0 and 1. + * @param fromUser True if the progress change was initiated by the user. + */ + void onAnimatePosition(float scale, boolean fromUser) { + } + + /** + * Sets the progress value without going through the entire refresh process. + * + * @see #setProgress(int, boolean) + * @param progress The new progress, between 0 and {@link #getMax()} + */ + void setProgressValueOnly(int progress) { + mProgress = progress; + onProgressRefresh(getScale(progress), true); + } + + void setAnimationPosition(float position) { + mAnimationPosition = position; + refreshProgress(R.id.progress, position, true, true); + } + + float getAnimationPosition() { + return mAnimationPosition; + } + void onProgressRefresh(float scale, boolean fromUser) { if (AccessibilityManager.getInstance(mContext).isEnabled()) { scheduleAccessibilityEventSender(); } } - private synchronized void refreshProgress(int id, int progress, boolean fromUser) { + private synchronized void refreshProgress(int id, float progress, boolean fromUser) { + refreshProgress(id, progress, fromUser, false); + } + + private synchronized void refreshProgress(int id, float progress, boolean fromUser, + boolean animate) { if (mUiThreadId == Thread.currentThread().getId()) { - doRefreshProgress(id, progress, fromUser, true); + doRefreshProgress(id, progress, fromUser, true, animate); } else { if (mRefreshProgressRunnable == null) { mRefreshProgressRunnable = new RefreshProgressRunnable(); } - final RefreshData rd = RefreshData.obtain(id, progress, fromUser); + final RefreshData rd = RefreshData.obtain(id, progress, fromUser, animate); mRefreshData.add(rd); if (mAttached && !mRefreshIsPosted) { post(mRefreshProgressRunnable); @@ -1111,7 +1164,7 @@ public class ProgressBar extends View { } } } - + /** * <p>Set the current progress to the specified value. Does not do anything * if the progress bar is in indeterminate mode.</p> @@ -1121,13 +1174,13 @@ public class ProgressBar extends View { * @see #setIndeterminate(boolean) * @see #isIndeterminate() * @see #getProgress() - * @see #incrementProgressBy(int) + * @see #incrementProgressBy(int) */ @android.view.RemotableViewMethod public synchronized void setProgress(int progress) { setProgress(progress, false); } - + @android.view.RemotableViewMethod synchronized void setProgress(int progress, boolean fromUser) { if (mIndeterminate) { @@ -1153,7 +1206,7 @@ public class ProgressBar extends View { * Set the current secondary progress to the specified value. Does not do * anything if the progress bar is in indeterminate mode. * </p> - * + * * @param secondaryProgress the new secondary progress, between 0 and {@link #getMax()} * @see #setIndeterminate(boolean) * @see #isIndeterminate() @@ -1234,8 +1287,8 @@ public class ProgressBar extends View { * @param max the upper range of this progress bar * * @see #getMax() - * @see #setProgress(int) - * @see #setSecondaryProgress(int) + * @see #setProgress(int) + * @see #setSecondaryProgress(int) */ @android.view.RemotableViewMethod public synchronized void setMax(int max) { @@ -1252,13 +1305,13 @@ public class ProgressBar extends View { refreshProgress(R.id.progress, mProgress, false); } } - + /** * <p>Increase the progress bar's progress by the specified amount.</p> * * @param diff the amount by which the progress must be increased * - * @see #setProgress(int) + * @see #setProgress(int) */ public synchronized final void incrementProgressBy(int diff) { setProgress(mProgress + diff); @@ -1269,7 +1322,7 @@ public class ProgressBar extends View { * * @param diff the amount by which the secondary progress must be increased * - * @see #setSecondaryProgress(int) + * @see #setSecondaryProgress(int) */ public synchronized final void incrementSecondaryProgressBy(int diff) { setSecondaryProgress(mSecondaryProgress + diff); @@ -1292,13 +1345,13 @@ public class ProgressBar extends View { if (mInterpolator == null) { mInterpolator = new LinearInterpolator(); } - + if (mTransformation == null) { mTransformation = new Transformation(); } else { mTransformation.clear(); } - + if (mAnimation == null) { mAnimation = new AlphaAnimation(0.0f, 1.0f); } else { @@ -1449,7 +1502,7 @@ public class ProgressBar extends View { } mIndeterminateDrawable.setBounds(left, top, right, bottom); } - + if (mProgressDrawable != null) { mProgressDrawable.setBounds(0, 0, right, bottom); } @@ -1519,20 +1572,20 @@ public class ProgressBar extends View { setMeasuredDimension(resolveSizeAndState(dw, widthMeasureSpec, 0), resolveSizeAndState(dh, heightMeasureSpec, 0)); } - + @Override protected void drawableStateChanged() { super.drawableStateChanged(); updateDrawableState(); } - + private void updateDrawableState() { int[] state = getDrawableState(); - + if (mProgressDrawable != null && mProgressDrawable.isStateful()) { mProgressDrawable.setState(state); } - + if (mIndeterminateDrawable != null && mIndeterminateDrawable.isStateful()) { mIndeterminateDrawable.setState(state); } @@ -1554,14 +1607,14 @@ public class ProgressBar extends View { static class SavedState extends BaseSavedState { int progress; int secondaryProgress; - + /** * Constructor called from {@link ProgressBar#onSaveInstanceState()} */ SavedState(Parcelable superState) { super(superState); } - + /** * Constructor called from {@link #CREATOR} */ @@ -1595,10 +1648,10 @@ public class ProgressBar extends View { // Force our ancestor class to save its state Parcelable superState = super.onSaveInstanceState(); SavedState ss = new SavedState(superState); - + ss.progress = mProgress; ss.secondaryProgress = mSecondaryProgress; - + return ss; } @@ -1606,7 +1659,7 @@ public class ProgressBar extends View { public void onRestoreInstanceState(Parcelable state) { SavedState ss = (SavedState) state; super.onRestoreInstanceState(ss.getSuperState()); - + setProgress(ss.progress); setSecondaryProgress(ss.secondaryProgress); } @@ -1622,7 +1675,7 @@ public class ProgressBar extends View { final int count = mRefreshData.size(); for (int i = 0; i < count; i++) { final RefreshData rd = mRefreshData.get(i); - doRefreshProgress(rd.id, rd.progress, rd.fromUser, true); + doRefreshProgress(rd.id, rd.progress, rd.fromUser, rd.animate); rd.recycle(); } mRefreshData.clear(); diff --git a/core/java/android/widget/RadialTimePickerView.java b/core/java/android/widget/RadialTimePickerView.java index 9b763c18cac5..1e30bfa3dcec 100644 --- a/core/java/android/widget/RadialTimePickerView.java +++ b/core/java/android/widget/RadialTimePickerView.java @@ -518,6 +518,9 @@ public class RadialTimePickerView extends View implements View.OnTouchListener { if (mIs24HourMode) { if (mIsOnInnerCircle) { hours = hours % 12; + if (hours == 0) { + hours = 12; + } } else { if (hours != 0) { hours += 12; diff --git a/core/java/android/widget/RatingBar.java b/core/java/android/widget/RatingBar.java index 82b490ea26c2..c4a7c0aa4efd 100644 --- a/core/java/android/widget/RatingBar.java +++ b/core/java/android/widget/RatingBar.java @@ -314,6 +314,10 @@ public class RatingBar extends AbsSeekBar { dispatchRatingChange(true); } + @Override + void animateSetProgress(int progress) { + } + void dispatchRatingChange(boolean fromUser) { if (mOnRatingBarChangeListener != null) { mOnRatingBarChangeListener.onRatingChanged(this, getRating(), diff --git a/core/java/android/widget/Toolbar.java b/core/java/android/widget/Toolbar.java index 71102e8a3eba..0b15eb60130b 100644 --- a/core/java/android/widget/Toolbar.java +++ b/core/java/android/widget/Toolbar.java @@ -17,6 +17,7 @@ package android.widget; import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.ActionBar; import android.content.Context; import android.content.res.TypedArray; @@ -654,27 +655,13 @@ public class Toolbar extends ViewGroup { } /** - * Set the icon to use for the toolbar's navigation button. - * - * <p>The navigation button appears at the start of the toolbar if present. Setting an icon - * will make the navigation button visible.</p> - * - * <p>If you use a navigation icon you should also set a description for its action using - * {@link #setNavigationDescription(int)}. This is used for accessibility and tooltips.</p> - * - * @param resId Resource ID of a drawable to set - */ - public void setNavigationIcon(int resId) { - setNavigationIcon(getContext().getDrawable(resId)); - } - - /** * Retrieve the currently configured content description for the navigation button view. * This will be used to describe the navigation action to users through mechanisms such * as screen readers or tooltips. * * @return The navigation button's content description */ + @Nullable public CharSequence getNavigationContentDescription() { return mNavButtonView != null ? mNavButtonView.getContentDescription() : null; } @@ -684,11 +671,11 @@ public class Toolbar extends ViewGroup { * description will be read via screen readers or other accessibility systems to explain * the action of the navigation button. * - * @param description Content description to set + * @param resId Resource ID of a content description string to set, or 0 to + * clear the description */ - public void setNavigationContentDescription(CharSequence description) { - ensureNavButtonView(); - mNavButtonView.setContentDescription(description); + public void setNavigationContentDescription(int resId) { + setNavigationContentDescription(resId != 0 ? getContext().getText(resId) : null); } /** @@ -696,11 +683,32 @@ public class Toolbar extends ViewGroup { * description will be read via screen readers or other accessibility systems to explain * the action of the navigation button. * - * @param resId Resource ID of a content description string to set + * @param description Content description to set, or <code>null</code> to + * clear the content description */ - public void setNavigationContentDescription(int resId) { - ensureNavButtonView(); - mNavButtonView.setContentDescription(resId != 0 ? getContext().getText(resId) : null); + public void setNavigationContentDescription(@Nullable CharSequence description) { + if (!TextUtils.isEmpty(description)) { + ensureNavButtonView(); + } + if (mNavButtonView != null) { + mNavButtonView.setContentDescription(description); + } + } + + /** + * Set the icon to use for the toolbar's navigation button. + * + * <p>The navigation button appears at the start of the toolbar if present. Setting an icon + * will make the navigation button visible.</p> + * + * <p>If you use a navigation icon you should also set a description for its action using + * {@link #setNavigationContentDescription(int)}. This is used for accessibility and + * tooltips.</p> + * + * @param resId Resource ID of a drawable to set + */ + public void setNavigationIcon(int resId) { + setNavigationIcon(getContext().getDrawable(resId)); } /** @@ -710,11 +718,12 @@ public class Toolbar extends ViewGroup { * will make the navigation button visible.</p> * * <p>If you use a navigation icon you should also set a description for its action using - * {@link #setNavigationDescription(int)}. This is used for accessibility and tooltips.</p> + * {@link #setNavigationContentDescription(int)}. This is used for accessibility and + * tooltips.</p> * - * @param icon Drawable to set + * @param icon Drawable to set, may be null to clear the icon */ - public void setNavigationIcon(Drawable icon) { + public void setNavigationIcon(@Nullable Drawable icon) { if (icon != null) { ensureNavButtonView(); if (mNavButtonView.getParent() == null) { @@ -733,40 +742,12 @@ public class Toolbar extends ViewGroup { * * @return The navigation icon drawable */ + @Nullable public Drawable getNavigationIcon() { return mNavButtonView != null ? mNavButtonView.getDrawable() : null; } /** - * Set a description for the navigation button. - * - * <p>This description string is used for accessibility, tooltips and other facilities - * to improve discoverability.</p> - * - * @param resId Resource ID of a string to set - */ - public void setNavigationDescription(int resId) { - setNavigationDescription(getContext().getText(resId)); - } - - /** - * Set a description for the navigation button. - * - * <p>This description string is used for accessibility, tooltips and other facilities - * to improve discoverability.</p> - * - * @param description String to set as the description - */ - public void setNavigationDescription(CharSequence description) { - if (!TextUtils.isEmpty(description)) { - ensureNavButtonView(); - } - if (mNavButtonView != null) { - mNavButtonView.setContentDescription(description); - } - } - - /** * Set a listener to respond to navigation events. * * <p>This listener will be called whenever the user clicks the navigation button diff --git a/core/java/com/android/internal/app/ToolbarActionBar.java b/core/java/com/android/internal/app/ToolbarActionBar.java index 1bcb684c64c5..298dd44557c6 100644 --- a/core/java/com/android/internal/app/ToolbarActionBar.java +++ b/core/java/com/android/internal/app/ToolbarActionBar.java @@ -154,7 +154,7 @@ public class ToolbarActionBar extends ActionBar { @Override public void setHomeActionContentDescription(CharSequence description) { - mToolbar.setNavigationDescription(description); + mToolbar.setNavigationContentDescription(description); } @Override @@ -164,7 +164,7 @@ public class ToolbarActionBar extends ActionBar { @Override public void setHomeActionContentDescription(int resId) { - mToolbar.setNavigationDescription(resId); + mToolbar.setNavigationContentDescription(resId); } @Override diff --git a/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java b/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java index fdd24a6b9e66..52485ddb7fa7 100644 --- a/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java +++ b/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java @@ -54,8 +54,8 @@ public class InputMethodSubtypeSwitchingController { public final CharSequence mSubtypeName; public final InputMethodInfo mImi; public final int mSubtypeId; - private final boolean mIsSystemLocale; - private final boolean mIsSystemLanguage; + public final boolean mIsSystemLocale; + public final boolean mIsSystemLanguage; public ImeSubtypeListItem(CharSequence imeName, CharSequence subtypeName, InputMethodInfo imi, int subtypeId, String subtypeLocale, String systemLocale) { @@ -68,8 +68,28 @@ public class InputMethodSubtypeSwitchingController { mIsSystemLanguage = false; } else { mIsSystemLocale = subtypeLocale.equals(systemLocale); - mIsSystemLanguage = mIsSystemLocale - || subtypeLocale.startsWith(systemLocale.substring(0, 2)); + if (mIsSystemLocale) { + mIsSystemLanguage = true; + } else { + // TODO: Use Locale#getLanguage or Locale#toLanguageTag + final String systemLanguage = parseLanguageFromLocaleString(systemLocale); + final String subtypeLanguage = parseLanguageFromLocaleString(subtypeLocale); + mIsSystemLanguage = systemLanguage.length() >= 2 && + systemLanguage.equals(subtypeLanguage); + } + } + } + + /** + * Returns the language component of a given locale string. + * TODO: Use {@link Locale#getLanguage()} instead. + */ + private static String parseLanguageFromLocaleString(final String locale) { + final int idx = locale.indexOf('_'); + if (idx < 0) { + return locale; + } else { + return locale.substring(0, idx); } } diff --git a/core/java/com/android/internal/util/Preconditions.java b/core/java/com/android/internal/util/Preconditions.java index c0d1e8843ebd..414b7bc0c407 100644 --- a/core/java/com/android/internal/util/Preconditions.java +++ b/core/java/com/android/internal/util/Preconditions.java @@ -16,6 +16,8 @@ package com.android.internal.util; +import java.util.Collection; + /** * Simple static methods to be called at the start of your own methods to verify * correct arguments and state. @@ -210,7 +212,7 @@ public class Preconditions { } /** - * Ensures that the array is not {@code null}, and none if its elements are {@code null}. + * Ensures that the array is not {@code null}, and none of its elements are {@code null}. * * @param value an array of boxed objects * @param valueName the name of the argument to use if the check fails @@ -235,6 +237,57 @@ public class Preconditions { } /** + * Ensures that the {@link Collection} is not {@code null}, and none of its elements are + * {@code null}. + * + * @param value a {@link Collection} of boxed objects + * @param valueName the name of the argument to use if the check fails + * + * @return the validated {@link Collection} + * + * @throws NullPointerException if the {@code value} or any of its elements were {@code null} + */ + public static <T> Collection<T> checkCollectionElementsNotNull(final Collection<T> value, + final String valueName) { + if (value == null) { + throw new NullPointerException(valueName + " must not be null"); + } + + long ctr = 0; + for (T elem : value) { + if (elem == null) { + throw new NullPointerException( + String.format("%s[%d] must not be null", valueName, ctr)); + } + ++ctr; + } + + return value; + } + + /** + * Ensures that the {@link Collection} is not {@code null}, and contains at least one element. + * + * @param value a {@link Collection} of boxed elements. + * @param valueName the name of the argument to use if the check fails. + + * @return the validated {@link Collection} + * + * @throws NullPointerException if the {@code value} was {@code null} + * @throws IllegalArgumentException if the {@code value} was empty + */ + public static <T> Collection<T> checkCollectionNotEmpty(final Collection<T> value, + final String valueName) { + if (value == null) { + throw new NullPointerException(valueName + " must not be null"); + } + if (value.isEmpty()) { + throw new IllegalArgumentException(valueName + " is empty"); + } + return value; + } + + /** * Ensures that all elements in the argument floating point array are within the inclusive range * * <p>While this can be used to range check against +/- infinity, note that all NaN numbers diff --git a/core/jni/android/graphics/MinikinUtils.cpp b/core/jni/android/graphics/MinikinUtils.cpp index 802f2abea212..2ad83305c564 100644 --- a/core/jni/android/graphics/MinikinUtils.cpp +++ b/core/jni/android/graphics/MinikinUtils.cpp @@ -40,8 +40,8 @@ static int snprintfcat(char* buf, int off, int size, const char* format, ...) { return off + n; } -std::string MinikinUtils::setLayoutProperties(Layout* layout, const Paint* paint, int bidiFlags, - TypefaceImpl* typeface) { +void MinikinUtils::doLayout(Layout* layout, const Paint* paint, int bidiFlags, TypefaceImpl* typeface, + const uint16_t* buf, size_t start, size_t count, size_t bufSize) { TypefaceImpl* resolvedFace = TypefaceImpl_resolveDefault(typeface); layout->setFontCollection(resolvedFace->fFontCollection); FontStyle style = resolvedFace->fStyle; @@ -62,7 +62,7 @@ std::string MinikinUtils::setLayoutProperties(Layout* layout, const Paint* paint SkPaintOptionsAndroid::FontVariant var = paint->getPaintOptionsAndroid().getFontVariant(); const char* varstr = var == SkPaintOptionsAndroid::kElegant_Variant ? "elegant" : "compact"; off = snprintfcat(css, off, sizeof(css), " -minikin-variant: %s;", varstr); - return std::string(css); + layout->doLayout(buf, start, count, bufSize, std::string(css)); } float MinikinUtils::xOffsetForTextAlign(Paint* paint, const Layout& layout) { diff --git a/core/jni/android/graphics/MinikinUtils.h b/core/jni/android/graphics/MinikinUtils.h index 0562c3b1d0e2..647cbd8376dd 100644 --- a/core/jni/android/graphics/MinikinUtils.h +++ b/core/jni/android/graphics/MinikinUtils.h @@ -45,8 +45,8 @@ class TypefaceImpl; class MinikinUtils { public: - static std::string setLayoutProperties(Layout* layout, const Paint* paint, int bidiFlags, - TypefaceImpl* typeface); + static void doLayout(Layout* layout, const Paint* paint, int bidiFlags, TypefaceImpl* typeface, + const uint16_t* buf, size_t start, size_t count, size_t bufSize); static float xOffsetForTextAlign(Paint* paint, const Layout& layout); diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp index e2b3684c9a9d..a1f09bde12e9 100644 --- a/core/jni/android/graphics/Paint.cpp +++ b/core/jni/android/graphics/Paint.cpp @@ -535,8 +535,7 @@ public: Layout layout; TypefaceImpl* typeface = GraphicsJNI::getNativeTypeface(env, jpaint); - std::string css = MinikinUtils::setLayoutProperties(&layout, paint, bidiFlags, typeface); - layout.doLayout(textArray, index, count, textLength, css); + MinikinUtils::doLayout(&layout, paint, bidiFlags, typeface, textArray, index, count, textLength); result = layout.getAdvance(); env->ReleaseCharArrayElements(text, const_cast<jchar*>(textArray), JNI_ABORT); return result; @@ -563,8 +562,7 @@ public: Layout layout; TypefaceImpl* typeface = GraphicsJNI::getNativeTypeface(env, jpaint); - std::string css = MinikinUtils::setLayoutProperties(&layout, paint, bidiFlags, typeface); - layout.doLayout(textArray, start, count, textLength, css); + MinikinUtils::doLayout(&layout, paint, bidiFlags, typeface, textArray, start, count, textLength); width = layout.getAdvance(); env->ReleaseStringChars(text, textArray); @@ -586,8 +584,7 @@ public: Layout layout; TypefaceImpl* typeface = GraphicsJNI::getNativeTypeface(env, jpaint); - std::string css = MinikinUtils::setLayoutProperties(&layout, paint, bidiFlags, typeface); - layout.doLayout(textArray, 0, textLength, textLength, css); + MinikinUtils::doLayout(&layout, paint, bidiFlags, typeface, textArray, 0, textLength, textLength); width = layout.getAdvance(); env->ReleaseStringChars(text, textArray); @@ -616,8 +613,7 @@ public: jfloat* widthsArray = autoWidths.ptr(); Layout layout; - std::string css = MinikinUtils::setLayoutProperties(&layout, paint, bidiFlags, typeface); - layout.doLayout(text, 0, count, count, css); + MinikinUtils::doLayout(&layout, paint, bidiFlags, typeface, text, 0, count, count); layout.getAdvances(widthsArray); return count; @@ -670,8 +666,7 @@ public: int bidiFlags = isRtl ? kBidi_Force_RTL : kBidi_Force_LTR; Layout layout; - std::string css = MinikinUtils::setLayoutProperties(&layout, paint, bidiFlags, typeface); - layout.doLayout(text, start, count, contextCount, css); + MinikinUtils::doLayout(&layout, paint, bidiFlags, typeface, text, start, count, contextCount); layout.getAdvances(advancesArray); totalAdvance = layout.getAdvance(); @@ -770,8 +765,7 @@ public: static void getTextPath(JNIEnv* env, Paint* paint, TypefaceImpl* typeface, const jchar* text, jint count, jint bidiFlags, jfloat x, jfloat y, SkPath* path) { Layout layout; - std::string css = MinikinUtils::setLayoutProperties(&layout, paint, bidiFlags, typeface); - layout.doLayout(text, 0, count, count, css); + MinikinUtils::doLayout(&layout, paint, bidiFlags, typeface, text, 0, count, count); size_t nGlyphs = layout.nGlyphs(); uint16_t* glyphs = new uint16_t[nGlyphs]; SkPoint* pos = new SkPoint[nGlyphs]; @@ -833,8 +827,7 @@ public: float measured = 0; Layout layout; - std::string css = MinikinUtils::setLayoutProperties(&layout, &paint, bidiFlags, typeface); - layout.doLayout(text, 0, count, count, css); + MinikinUtils::doLayout(&layout, &paint, bidiFlags, typeface, text, 0, count, count); float* advances = new float[count]; layout.getAdvances(advances); const bool forwardScan = (textBufferDirection == Paint::kForward_TextBufferDirection); @@ -914,8 +907,7 @@ public: SkIRect ir; Layout layout; - std::string css = MinikinUtils::setLayoutProperties(&layout, &paint, bidiFlags, typeface); - layout.doLayout(text, 0, count, count, css); + MinikinUtils::doLayout(&layout, &paint, bidiFlags, typeface, text, 0, count, count); MinikinRect rect; layout.getBounds(&rect); r.fLeft = rect.mLeft; diff --git a/core/jni/android/graphics/TextLayout.h b/core/jni/android/graphics/TextLayout.h deleted file mode 100644 index d58c692c1392..000000000000 --- a/core/jni/android/graphics/TextLayout.h +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "jni.h" - -#include "SkCanvas.h" -#include "SkPaint.h" -#include "unicode/utypes.h" - -#include "TextLayoutCache.h" - -namespace android { - -#define UNICODE_NOT_A_CHAR 0xffff -#define UNICODE_ZWSP 0x200b -#define UNICODE_FIRST_LOW_SURROGATE 0xdc00 -#define UNICODE_FIRST_HIGH_SURROGATE 0xd800 -#define UNICODE_FIRST_PRIVATE_USE 0xe000 -#define UNICODE_FIRST_RTL_CHAR 0x0590 - -/* - * Temporary buffer size - */ -#define CHAR_BUFFER_SIZE 80 - -/** - * Turn on for using the Cache - */ -#define USE_TEXT_LAYOUT_CACHE 1 - -enum { - kBidi_LTR = 0, - kBidi_RTL = 1, - kBidi_Default_LTR = 2, - kBidi_Default_RTL = 3, - kBidi_Force_LTR = 4, - kBidi_Force_RTL = 5, - - kBidi_Mask = 0x7 -}; - -enum { - kDirection_LTR = 0, - kDirection_RTL = 1, - - kDirection_Mask = 0x1 -}; - -class TextLayout { -public: - - static void getTextRunAdvances(SkPaint* paint, const jchar* chars, jint start, - jint count, jint contextCount, jint dirFlags, - jfloat* resultAdvances, jfloat* resultTotalAdvance); - - static void getTextPath(SkPaint* paint, const jchar* text, jsize len, - jint bidiFlags, jfloat x, jfloat y, SkPath* path); - - static void drawTextOnPath(SkPaint* paint, const jchar* text, jsize len, - int bidiFlags, jfloat hOffset, jfloat vOffset, - SkPath* path, SkCanvas* canvas); - -private: - static bool needsLayout(const jchar* text, jint len, jint bidiFlags); - - static void handleText(SkPaint* paint, const jchar* text, jsize len, - int bidiFlags, jfloat x, jfloat y, SkPath* path); -}; -} // namespace android diff --git a/core/jni/android_graphics_Canvas.cpp b/core/jni/android_graphics_Canvas.cpp index 000791275488..a9b01d0926de 100644 --- a/core/jni/android_graphics_Canvas.cpp +++ b/core/jni/android_graphics_Canvas.cpp @@ -486,8 +486,7 @@ void drawText(Canvas* canvas, const uint16_t* text, int start, int count, int co Paint paint(origPaint); Layout layout; - std::string css = MinikinUtils::setLayoutProperties(&layout, &paint, bidiFlags, typeface); - layout.doLayout(text, start, count, contextCount, css); + MinikinUtils::doLayout(&layout, &paint, bidiFlags, typeface, text, start, count, contextCount); size_t nGlyphs = layout.nGlyphs(); uint16_t* glyphs = new uint16_t[nGlyphs]; @@ -625,8 +624,7 @@ static void drawTextOnPath(Canvas* canvas, const uint16_t* text, int count, int const Paint& paint, TypefaceImpl* typeface) { Paint paintCopy(paint); Layout layout; - std::string css = MinikinUtils::setLayoutProperties(&layout, &paintCopy, bidiFlags, typeface); - layout.doLayout(text, 0, count, count, css); + MinikinUtils::doLayout(&layout, &paintCopy, bidiFlags, typeface, text, 0, count, count); hOffset += MinikinUtils::hOffsetForTextAlign(&paintCopy, layout, path); // Set align to left for drawing, as we don't want individual diff --git a/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp b/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp index d2f5b5d6d633..697cdc686514 100644 --- a/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp +++ b/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp @@ -587,7 +587,7 @@ static jint LegacyCameraDevice_nativeSetSurfaceOrientation(JNIEnv* env, jobject int32_t transform = 0; - if ((err = CameraUtils::getRotationTransform(staticMetadata, /*out*/&transform)) != OK) { + if ((err = CameraUtils::getRotationTransform(staticMetadata, /*out*/&transform)) != NO_ERROR) { ALOGE("%s: Invalid rotation transform %s (%d)", __FUNCTION__, strerror(-err), err); return err; @@ -595,7 +595,7 @@ static jint LegacyCameraDevice_nativeSetSurfaceOrientation(JNIEnv* env, jobject ALOGV("%s: Setting buffer sticky transform to %d", __FUNCTION__, transform); - if ((err = native_window_set_buffers_sticky_transform(anw.get(), transform)) != OK) { + if ((err = native_window_set_buffers_sticky_transform(anw.get(), transform)) != NO_ERROR) { ALOGE("%s: Unable to configure surface transform, error %s (%d)", __FUNCTION__, strerror(-err), err); return err; @@ -604,6 +604,26 @@ static jint LegacyCameraDevice_nativeSetSurfaceOrientation(JNIEnv* env, jobject return NO_ERROR; } +static jint LegacyCameraDevice_nativeSetNextTimestamp(JNIEnv* env, jobject thiz, jobject surface, + jlong timestamp) { + ALOGV("nativeSetNextTimestamp"); + sp<ANativeWindow> anw; + if ((anw = getNativeWindow(env, surface)) == NULL) { + ALOGE("%s: Could not retrieve native window from surface.", __FUNCTION__); + return BAD_VALUE; + } + + status_t err = NO_ERROR; + + if ((err = native_window_set_buffers_timestamp(anw.get(), static_cast<int64_t>(timestamp))) != + NO_ERROR) { + ALOGE("%s: Unable to set surface timestamp, error %s (%d)", __FUNCTION__, strerror(-err), + err); + return err; + } + return NO_ERROR; +} + } // extern "C" static JNINativeMethod gCameraDeviceMethods[] = { @@ -634,6 +654,9 @@ static JNINativeMethod gCameraDeviceMethods[] = { { "nativeSetSurfaceOrientation", "(Landroid/view/Surface;II)I", (void *)LegacyCameraDevice_nativeSetSurfaceOrientation }, + { "nativeSetNextTimestamp", + "(Landroid/view/Surface;J)I", + (void *)LegacyCameraDevice_nativeSetNextTimestamp }, }; // Get all the required offsets in java class and register native functions diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp index 0a259aa21cc3..3cd031e870ca 100644 --- a/core/jni/android_view_GLES20Canvas.cpp +++ b/core/jni/android_view_GLES20Canvas.cpp @@ -620,8 +620,7 @@ static void renderTextLayout(DisplayListRenderer* renderer, Layout* layout, static void renderText(DisplayListRenderer* renderer, const jchar* text, int count, jfloat x, jfloat y, int bidiFlags, Paint* paint, TypefaceImpl* typeface) { Layout layout; - std::string css = MinikinUtils::setLayoutProperties(&layout, paint, bidiFlags, typeface); - layout.doLayout(text, 0, count, count, css); + MinikinUtils::doLayout(&layout, paint, bidiFlags, typeface, text, 0, count, count); x += MinikinUtils::xOffsetForTextAlign(paint, layout); renderTextLayout(renderer, &layout, x, y, paint); } @@ -655,8 +654,7 @@ static void renderTextOnPath(DisplayListRenderer* renderer, const jchar* text, i SkPath* path, jfloat hOffset, jfloat vOffset, int bidiFlags, Paint* paint, TypefaceImpl* typeface) { Layout layout; - std::string css = MinikinUtils::setLayoutProperties(&layout, paint, bidiFlags, typeface); - layout.doLayout(text, 0, count, count, css); + MinikinUtils::doLayout(&layout, paint, bidiFlags, typeface, text, 0, count, count); hOffset += MinikinUtils::hOffsetForTextAlign(paint, layout, *path); Paint::Align align = paint->getTextAlign(); paint->setTextAlign(Paint::kLeft_Align); @@ -670,8 +668,7 @@ static void renderTextRun(DisplayListRenderer* renderer, const jchar* text, jint start, jint count, jint contextCount, jfloat x, jfloat y, int bidiFlags, Paint* paint, TypefaceImpl* typeface) { Layout layout; - std::string css = MinikinUtils::setLayoutProperties(&layout, paint, bidiFlags, typeface); - layout.doLayout(text, start, count, contextCount, css); + MinikinUtils::doLayout(&layout, paint, bidiFlags, typeface, text, start, count, contextCount); x += MinikinUtils::xOffsetForTextAlign(paint, layout); renderTextLayout(renderer, &layout, x, y, paint); } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 056d470034fd..2043214fbf3c 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -2798,6 +2798,13 @@ android:description="@string/permdesc_createMediaProjection" android:protectionLevel="signature" /> + <!-- @SystemApi Allows an application to read install sessions + @hide This is not a third-party API (intended for system apps). --> + <permission android:name="android.permission.READ_INSTALL_SESSIONS" + android:label="@string/permlab_readInstallSessions" + android:description="@string/permdesc_readInstallSessions" + android:protectionLevel="signature|system" /> + <!-- The system process is explicitly the only one allowed to launch the confirmation UI for full backup/restore --> <uses-permission android:name="android.permission.CONFIRM_FULL_BACKUP"/> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 351acf0a8f11..c7b558058ef4 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -3872,6 +3872,11 @@ <!-- Description of an application permission that lets it create media projection sessions. --> <string name="permdesc_createMediaProjection">Allows an application to create media projection sessions. These sessions can provide applications the ability to capture display and audio contents. Should never be needed by normal apps.</string> + <!-- Title of an application permission that lets it read install sessions. --> + <string name="permlab_readInstallSessions">Read install sessions</string> + <!-- Description of an application permission that lets it read install sessions. --> + <string name="permdesc_readInstallSessions">Allows an application to read install sessions. This allows it to see details about active package installations.</string> + <!-- Shown in the tutorial for tap twice for zoom control. --> <string name="tutorial_double_tap_to_zoom_message_short">Touch twice for zoom control</string> diff --git a/core/tests/bluetoothtests/src/android/bluetooth/le/ScanSettingsTest.java b/core/tests/bluetoothtests/src/android/bluetooth/le/ScanSettingsTest.java new file mode 100644 index 000000000000..7c42c3b46775 --- /dev/null +++ b/core/tests/bluetoothtests/src/android/bluetooth/le/ScanSettingsTest.java @@ -0,0 +1,64 @@ +/* + * 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.bluetooth.le; + +import android.test.suitebuilder.annotation.SmallTest; + +import junit.framework.TestCase; + +/** + * Test for Bluetooth LE {@link ScanSettings}. + */ +public class ScanSettingsTest extends TestCase { + + @SmallTest + public void testCallbackType() { + ScanSettings.Builder builder = new ScanSettings.Builder(); + builder.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES); + builder.setCallbackType(ScanSettings.CALLBACK_TYPE_FIRST_MATCH); + builder.setCallbackType(ScanSettings.CALLBACK_TYPE_MATCH_LOST); + builder.setCallbackType( + ScanSettings.CALLBACK_TYPE_FIRST_MATCH | ScanSettings.CALLBACK_TYPE_MATCH_LOST); + try { + builder.setCallbackType( + ScanSettings.CALLBACK_TYPE_ALL_MATCHES | ScanSettings.CALLBACK_TYPE_MATCH_LOST); + fail("should have thrown IllegalArgumentException!"); + } catch (IllegalArgumentException e) { + // nothing to do + } + + try { + builder.setCallbackType( + ScanSettings.CALLBACK_TYPE_ALL_MATCHES | + ScanSettings.CALLBACK_TYPE_FIRST_MATCH); + fail("should have thrown IllegalArgumentException!"); + } catch (IllegalArgumentException e) { + // nothing to do + } + + try { + builder.setCallbackType( + ScanSettings.CALLBACK_TYPE_ALL_MATCHES | + ScanSettings.CALLBACK_TYPE_FIRST_MATCH | + ScanSettings.CALLBACK_TYPE_MATCH_LOST); + fail("should have thrown IllegalArgumentException!"); + } catch (IllegalArgumentException e) { + // nothing to do + } + + } +} diff --git a/core/tests/inputmethodtests/src/android/os/InputMethodSubtypeSwitchingControllerTest.java b/core/tests/inputmethodtests/src/android/os/InputMethodSubtypeSwitchingControllerTest.java index ca68e939e635..3a598f266681 100644 --- a/core/tests/inputmethodtests/src/android/os/InputMethodSubtypeSwitchingControllerTest.java +++ b/core/tests/inputmethodtests/src/android/os/InputMethodSubtypeSwitchingControllerTest.java @@ -295,4 +295,33 @@ public class InputMethodSubtypeSwitchingControllerTest extends InstrumentationTe assertRotationOrder(anotherController, false /* onlyCurrentIme */, switchingUnawarelatinIme_en_UK, switchUnawareJapaneseIme_ja_JP); } + + @SmallTest + public void testImeSubtypeListItem() throws Exception { + final List<ImeSubtypeListItem> items = new ArrayList<ImeSubtypeListItem>(); + addDummyImeSubtypeListItems(items, "LatinIme", "LatinIme", + Arrays.asList("en_US", "fr", "en", "en_uk", "enn", "e", "EN_US"), + true /* supportsSwitchingToNextInputMethod*/); + final ImeSubtypeListItem item_en_US = items.get(0); + final ImeSubtypeListItem item_fr = items.get(1); + final ImeSubtypeListItem item_en = items.get(2); + final ImeSubtypeListItem item_enn = items.get(3); + final ImeSubtypeListItem item_e = items.get(4); + final ImeSubtypeListItem item_EN_US = items.get(5); + + assertTrue(item_en_US.mIsSystemLocale); + assertFalse(item_fr.mIsSystemLocale); + assertFalse(item_en.mIsSystemLocale); + assertFalse(item_en.mIsSystemLocale); + assertFalse(item_enn.mIsSystemLocale); + assertFalse(item_e.mIsSystemLocale); + assertFalse(item_EN_US.mIsSystemLocale); + + assertTrue(item_en_US.mIsSystemLanguage); + assertFalse(item_fr.mIsSystemLanguage); + assertTrue(item_en.mIsSystemLanguage); + assertFalse(item_enn.mIsSystemLocale); + assertFalse(item_e.mIsSystemLocale); + assertFalse(item_EN_US.mIsSystemLocale); + } } diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java index 8ed6776d395a..f32fa1f6fd70 100644 --- a/graphics/java/android/graphics/drawable/VectorDrawable.java +++ b/graphics/java/android/graphics/drawable/VectorDrawable.java @@ -188,6 +188,12 @@ public class VectorDrawable extends Drawable { public void draw(Canvas canvas) { final int saveCount = canvas.save(); final Rect bounds = getBounds(); + + if (bounds.width() == 0 || bounds.height() == 0) { + // too small to draw + return; + } + final boolean needMirroring = needMirroring(); canvas.translate(bounds.left, bounds.top); diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp index 763e72747e83..dd34e095738d 100644 --- a/libs/hwui/renderthread/DrawFrameTask.cpp +++ b/libs/hwui/renderthread/DrawFrameTask.cpp @@ -85,7 +85,8 @@ int DrawFrameTask::drawFrame(nsecs_t frameTimeNanos, nsecs_t recordDurationNanos void DrawFrameTask::postAndWait() { AutoMutex _lock(mLock); - mRenderThread->queueAndWait(this, mSignal, mLock); + mRenderThread->queue(this); + mSignal.wait(mLock); } void DrawFrameTask::run() { diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp index 1e91eb541047..3f030936185e 100644 --- a/libs/hwui/renderthread/RenderProxy.cpp +++ b/libs/hwui/renderthread/RenderProxy.cpp @@ -412,7 +412,8 @@ void* RenderProxy::postAndWait(MethodInvokeRenderTask* task) { task->setReturnPtr(&retval); SignalingRenderTask syncTask(task, &mSyncMutex, &mSyncCondition); AutoMutex _lock(mSyncMutex); - mRenderThread.queueAndWait(&syncTask, mSyncCondition, mSyncMutex); + mRenderThread.queue(&syncTask); + mSyncCondition.wait(mSyncMutex); return retval; } diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp index 32dc46ee57eb..03e98d5b2f03 100644 --- a/libs/hwui/renderthread/RenderThread.cpp +++ b/libs/hwui/renderthread/RenderThread.cpp @@ -20,7 +20,6 @@ #include <gui/DisplayEventReceiver.h> #include <utils/Log.h> -#include <pthread.h> #include "../RenderState.h" #include "CanvasContext.h" @@ -137,7 +136,6 @@ public: }; RenderThread::RenderThread() : Thread(true), Singleton<RenderThread>() - , mThreadId(0) , mNextWakeup(LLONG_MAX) , mDisplayEventReceiver(0) , mVsyncRequested(false) @@ -246,7 +244,6 @@ void RenderThread::requestVsync() { } bool RenderThread::threadLoop() { - mThreadId = pthread_self(); initThreadLocals(); int timeoutMillis = -1; @@ -292,16 +289,6 @@ void RenderThread::queue(RenderTask* task) { } } -void RenderThread::queueAndWait(RenderTask* task, Condition& signal, Mutex& lock) { - static nsecs_t sTimeout = milliseconds(500); - queue(task); - status_t err = signal.waitRelative(lock, sTimeout); - if (CC_UNLIKELY(err != NO_ERROR)) { - ALOGE("Timeout waiting for RenderTherad! err=%d", err); - nukeFromOrbit(); - } -} - void RenderThread::queueAtFront(RenderTask* task) { AutoMutex _lock(mLock); mQueue.queueAtFront(task); @@ -354,10 +341,6 @@ RenderTask* RenderThread::nextTask(nsecs_t* nextWakeup) { return next; } -void RenderThread::nukeFromOrbit() { - pthread_kill(mThreadId, SIGABRT); -} - } /* namespace renderthread */ } /* namespace uirenderer */ } /* namespace android */ diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h index 59843736f048..0b91e9dd97aa 100644 --- a/libs/hwui/renderthread/RenderThread.h +++ b/libs/hwui/renderthread/RenderThread.h @@ -23,7 +23,6 @@ #include <set> #include <cutils/compiler.h> -#include <utils/Condition.h> #include <utils/Looper.h> #include <utils/Mutex.h> #include <utils/Singleton.h> @@ -74,7 +73,6 @@ public: // RenderThread takes complete ownership of tasks that are queued // and will delete them after they are run ANDROID_API void queue(RenderTask* task); - void queueAndWait(RenderTask* task, Condition& signal, Mutex& lock); ANDROID_API void queueAtFront(RenderTask* task); void queueDelayed(RenderTask* task, int delayMs); void remove(RenderTask* task); @@ -108,15 +106,11 @@ private: void dispatchFrameCallbacks(); void requestVsync(); - // VERY DANGEROUS HANDLE WITH EXTREME CARE - void nukeFromOrbit(); - // Returns the next task to be run. If this returns NULL nextWakeup is set // to the time to requery for the nextTask to run. mNextWakeup is also // set to this time RenderTask* nextTask(nsecs_t* nextWakeup); - pthread_t mThreadId; sp<Looper> mLooper; Mutex mLock; diff --git a/media/java/android/media/browse/MediaBrowserService.java b/media/java/android/media/browse/MediaBrowserService.java index ffbaeddcb3fd..c940259cd522 100644 --- a/media/java/android/media/browse/MediaBrowserService.java +++ b/media/java/android/media/browse/MediaBrowserService.java @@ -371,7 +371,7 @@ public abstract class MediaBrowserService extends Service { * children are to be queried. * @return The list of children, or null if the uri is invalid. */ - protected abstract void onLoadChildren(@NonNull Uri parentUri, + public abstract void onLoadChildren(@NonNull Uri parentUri, @NonNull Result<List<MediaBrowserItem>> result); /** @@ -390,7 +390,7 @@ public abstract class MediaBrowserService extends Service { * @return The file descriptor of the thumbnail, which may then be loaded * using a bitmap factory, or null if the item does not have a thumbnail. */ - protected abstract void onLoadThumbnail(@NonNull Uri uri, int width, int height, + public abstract void onLoadThumbnail(@NonNull Uri uri, int width, int height, @NonNull Result<Bitmap> result); /** diff --git a/packages/SystemUI/res/drawable/ic_clear_all.xml b/packages/SystemUI/res/drawable/ic_clear_all.xml new file mode 100644 index 000000000000..b0f3a5a46c99 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_clear_all.xml @@ -0,0 +1,28 @@ +<!-- + ~ 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 + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" > + <size + android:width="32dp" + android:height="32dp"/> + + <viewport + android:viewportWidth="48.0" + android:viewportHeight="48.0"/> + + <path + android:fill="#FFFFFFFF" + android:pathData="M10.0,26.0l28.0,0.0l0.0,-4.0L10.0,22.0L10.0,26.0zM6.0,34.0l28.0,0.0l0.0,-4.0L6.0,30.0L6.0,34.0zM14.0,14.0l0.0,4.0l28.0,0.0l0.0,-4.0L14.0,14.0z"/> +</vector> diff --git a/packages/SystemUI/res/drawable/notification_guts_bg.xml b/packages/SystemUI/res/drawable/notification_guts_bg.xml new file mode 100644 index 000000000000..07932d1af054 --- /dev/null +++ b/packages/SystemUI/res/drawable/notification_guts_bg.xml @@ -0,0 +1,21 @@ +<?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"> + <solid android:color="@color/notification_guts_bg_color" /> + <corners android:radius="@dimen/notification_material_rounded_rect_radius" /> +</shape> diff --git a/packages/SystemUI/res/layout/notification_guts.xml b/packages/SystemUI/res/layout/notification_guts.xml new file mode 100644 index 000000000000..0e78d669a136 --- /dev/null +++ b/packages/SystemUI/res/layout/notification_guts.xml @@ -0,0 +1,106 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 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. +--> + +<FrameLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@drawable/notification_guts_bg" + android:id="@+id/notification_guts" + android:visibility="gone" + android:clickable="true" + android:gravity="top|start" + > + <LinearLayout + android:layout_width="match_parent" + android:layout_height="@android:dimen/notification_large_icon_height" + android:orientation="horizontal" + > + + <ImageView android:id="@android:id/icon" + android:layout_width="@android:dimen/notification_large_icon_width" + android:layout_height="@android:dimen/notification_large_icon_height" + android:layout_weight="0" + android:padding="8dp" + android:scaleType="centerInside" + /> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="start|center_vertical" + android:orientation="vertical" + android:paddingStart="8dp" + android:paddingEnd="8dp" + android:layout_weight="1" + > + <TextView + android:id="@+id/pkgname" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical|start" + android:layout_weight="1" + android:textAppearance="@*android:style/TextAppearance.StatusBar.Material.EventContent.Title" + android:textColor="@color/notification_guts_title_color" + /> + <DateTimeView + android:id="@+id/timestamp" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="0" + android:layout_gravity="center_vertical|start" + android:textAppearance="@*android:style/TextAppearance.StatusBar.Material.EventContent.Time" + android:textColor="@color/notification_guts_text_color" + /> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/debug_info" + android:layout_weight="0" + android:textAppearance="@*android:style/TextAppearance.StatusBar.Material.EventContent.Time" + android:layout_gravity="bottom|start" + android:visibility="gone" + android:textColor="@color/notification_guts_text_color" + /> + </LinearLayout> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:padding="8dp" + android:layout_weight="0" + android:orientation="horizontal" + android:showDividers="beginning|middle" + android:divider="@*android:drawable/list_divider_holo_dark" + android:dividerPadding="8dp" + > + <Button style="@android:style/Widget.Material.Light.Button.Borderless.Small" + android:id="@+id/notification_inspect_item" + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="1" + android:gravity="start|center_vertical" + android:drawablePadding="8dp" + android:paddingStart="8dp" + android:textColor="@color/notification_guts_btn_color" + android:textSize="14dp" + android:singleLine="true" + android:ellipsize="end" + android:text="@string/status_bar_notification_inspect_item_title" + /> + </LinearLayout> + </LinearLayout> +</FrameLayout> diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml index 1f68cd822f14..9af2879f9ebb 100644 --- a/packages/SystemUI/res/layout/status_bar_expanded.xml +++ b/packages/SystemUI/res/layout/status_bar_expanded.xml @@ -56,14 +56,6 @@ android:clipToPadding="false" android:clipChildren="false"> - <ViewStub - android:id="@+id/keyguard_user_switcher" - android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:layout_marginTop="@dimen/status_bar_header_height_keyguard" - android:layout_gravity="end" - android:layout="@layout/keyguard_user_switcher" /> - <com.android.systemui.statusbar.phone.ObservableScrollView android:id="@+id/scroll_view" android:layout_width="match_parent" @@ -103,6 +95,14 @@ android:layout_height="match_parent" android:layout_marginBottom="@dimen/close_handle_underlap"/> + <ViewStub + android:id="@+id/keyguard_user_switcher" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:layout_marginTop="@dimen/status_bar_header_height_keyguard" + android:layout_gravity="end" + android:layout="@layout/keyguard_user_switcher" /> + </com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer> <include layout="@layout/status_bar_expanded_header" /> diff --git a/packages/SystemUI/res/layout/status_bar_notification_dismiss_all.xml b/packages/SystemUI/res/layout/status_bar_notification_dismiss_all.xml new file mode 100644 index 000000000000..515270a97292 --- /dev/null +++ b/packages/SystemUI/res/layout/status_bar_notification_dismiss_all.xml @@ -0,0 +1,40 @@ +<!-- + ~ 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 + --> + +<!-- Extends Framelayout --> +<com.android.systemui.statusbar.DismissView + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:visibility="gone" + > + <Button + android:id="@+id/dismiss_text" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:minHeight="0dp" + android:textColor="#ffffffff" + android:text="@string/clear_all_notifications_text" + android:textSize="18sp" + android:textAllCaps="true" + android:paddingTop="@dimen/clear_all_padding_top" + android:paddingEnd="8dp" + android:layout_gravity="end|center_vertical" + android:drawableEnd="@drawable/ic_clear_all" + android:drawablePadding="4dp" + android:fontFamily="sans-serif-light" + android:background="@drawable/ripple_drawable" /> +</com.android.systemui.statusbar.DismissView> diff --git a/packages/SystemUI/res/layout/status_bar_notification_row.xml b/packages/SystemUI/res/layout/status_bar_notification_row.xml index 7663d54a2498..ef4e27ce3243 100644 --- a/packages/SystemUI/res/layout/status_bar_notification_row.xml +++ b/packages/SystemUI/res/layout/status_bar_notification_row.xml @@ -1,3 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 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. +--> + <com.android.systemui.statusbar.ExpandableNotificationRow xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" @@ -35,17 +52,11 @@ android:paddingStart="8dp" /> - <TextView - android:id="@+id/debug_info" - android:visibility="invisible" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="bottom|end" - android:fontFamily="sans-serif-condensed" - android:textSize="9dp" - android:textStyle="bold" - android:textColor="#00A040" - android:padding="2dp" + <include + layout="@layout/notification_guts" + android:id="@+id/notification_guts" + android:layout_width="match_parent" + android:layout_height="match_parent" /> <com.android.systemui.statusbar.NotificationScrimView diff --git a/packages/SystemUI/res/layout/super_status_bar.xml b/packages/SystemUI/res/layout/super_status_bar.xml index 6f64e1721ba7..ac998f69ca0d 100644 --- a/packages/SystemUI/res/layout/super_status_bar.xml +++ b/packages/SystemUI/res/layout/super_status_bar.xml @@ -54,7 +54,8 @@ <com.android.systemui.statusbar.phone.PanelHolder android:id="@+id/panel_holder" android:layout_width="match_parent" - android:layout_height="match_parent" > + android:layout_height="match_parent" + android:background="@color/transparent" > <include layout="@layout/status_bar_expanded" android:layout_width="match_parent" android:layout_height="match_parent" diff --git a/packages/SystemUI/res/menu/notification_popup_menu.xml b/packages/SystemUI/res/menu/notification_popup_menu.xml deleted file mode 100644 index 8923fb67ff0e..000000000000 --- a/packages/SystemUI/res/menu/notification_popup_menu.xml +++ /dev/null @@ -1,22 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -/* apps/common/assets/default/default/skins/StatusBar.xml -** -** Copyright 2012, 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. -*/ ---> -<menu xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:id="@+id/notification_inspect_item" android:title="@string/status_bar_notification_inspect_item_title" /> -</menu> diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index e9fe09e4a064..a718f4fe0e45 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -62,7 +62,7 @@ <!-- The recents task bar light dismiss icon color to be drawn on top of dark backgrounds. --> <color name="recents_task_bar_light_dismiss_color">#ffeeeeee</color> <!-- The recents task bar dark dismiss icon color to be drawn on top of light backgrounds. --> - <color name="recents_task_bar_dark_dismiss_color">#cc000000</color> + <color name="recents_task_bar_dark_dismiss_color">#99000000</color> <!-- The recents task bar highlight color. --> <color name="recents_task_bar_highlight_color">#28ffffff</color> <!-- The lock to task button background color. --> @@ -94,4 +94,10 @@ <color name="current_user_border_color">@color/system_accent_color</color> <color name="segmented_button_text_inactive">#99afbdc4</color><!-- 60% --> + + <!-- The "inside" of a notification, reached via longpress --> + <color name="notification_guts_bg_color">#ff424242</color><!-- grey 800 --> + <color name="notification_guts_title_color">#FFFFFFFF</color> + <color name="notification_guts_text_color">#99FFFFFF</color> + <color name="notification_guts_btn_color">#FFFFFFFF</color> </resources> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 4e536e487b99..fbd3eb54c551 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -371,6 +371,9 @@ <!-- Battery level padding end when in expanded QS (but not on Keyguard) --> <dimen name="battery_level_padding_end">4dp</dimen> + <!-- The top padding of the clear all button --> + <dimen name="clear_all_padding_top">4dp</dimen> + <!-- Largest size an avatar might need to be drawn in the user picker, status bar, or quick settings header --> <dimen name="max_avatar_size">48dp</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 416cd542932c..ddbddd130e68 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -380,8 +380,12 @@ <!-- Content description of the ringer silent icon in the notification panel for accessibility (not shown on the screen). [CHAR LIMIT=NONE] --> <string name="accessibility_ringer_silent">Ringer silent.</string> + <!-- Content description to tell the user that this button will remove an application from recents --> + <string name="accessibility_recents_item_will_be_dismissed">Dismiss <xliff:g id="app" example="Calendar">%s</xliff:g>.</string> <!-- Content description to tell the user an application has been removed from recents --> <string name="accessibility_recents_item_dismissed"><xliff:g id="app" example="Calendar">%s</xliff:g> dismissed.</string> + <!-- Content description to tell the user an application has been launched from recents --> + <string name="accessibility_recents_item_launched">Starting <xliff:g id="app" example="Calendar">%s</xliff:g>.</string> <!-- Content description to tell the user a notification has been removed from the notification shade --> <string name="accessibility_notification_dismissed">Notification dismissed.</string> @@ -732,6 +736,9 @@ <!-- Media projection permission dialog permanent grant check box. [CHAR LIMIT=NONE] --> <string name="media_projection_remember_text">Don\'t show again</string> + <!-- The text to clear all notifications. [CHAR LIMIT=60] --> + <string name="clear_all_notifications_text">Clear all</string> + <!-- Media projection permission dialog action text. [CHAR LIMIT=60] --> <string name="media_projection_action_text">Start now</string> </resources> diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java index d153d09766f4..6c30c898396d 100644 --- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java +++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java @@ -21,6 +21,7 @@ import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.animation.ValueAnimator.AnimatorUpdateListener; +import android.content.Context; import android.graphics.RectF; import android.os.Handler; import android.util.Log; @@ -29,6 +30,8 @@ import android.view.VelocityTracker; import android.view.View; import android.view.ViewConfiguration; import android.view.accessibility.AccessibilityEvent; +import android.view.animation.AnimationUtils; +import android.view.animation.Interpolator; import android.view.animation.LinearInterpolator; public class SwipeHelper implements Gefingerpoken { @@ -44,6 +47,7 @@ public class SwipeHelper implements Gefingerpoken { public static final int Y = 1; private static LinearInterpolator sLinearInterpolator = new LinearInterpolator(); + private final Interpolator mFastOutLinearInInterpolator; private float SWIPE_ESCAPE_VELOCITY = 100f; // dp/sec private int DEFAULT_ESCAPE_ANIMATION_DURATION = 200; // ms @@ -72,23 +76,26 @@ public class SwipeHelper implements Gefingerpoken { private float mDensityScale; private boolean mLongPressSent; - private View.OnLongClickListener mLongPressListener; + private LongPressListener mLongPressListener; private Runnable mWatchLongPress; private long mLongPressTimeout; - public SwipeHelper(int swipeDirection, Callback callback, float densityScale, - float pagingTouchSlop) { + final private int[] mTmpPos = new int[2]; + + public SwipeHelper(int swipeDirection, Callback callback, Context context) { mCallback = callback; mHandler = new Handler(); mSwipeDirection = swipeDirection; mVelocityTracker = VelocityTracker.obtain(); - mDensityScale = densityScale; - mPagingTouchSlop = pagingTouchSlop; + mDensityScale = context.getResources().getDisplayMetrics().density; + mPagingTouchSlop = ViewConfiguration.get(context).getScaledPagingTouchSlop(); mLongPressTimeout = (long) (ViewConfiguration.getLongPressTimeout() * 1.5f); // extra long-press! + mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context, + android.R.interpolator.fast_out_linear_in); } - public void setLongPressListener(View.OnLongClickListener listener) { + public void setLongPressListener(LongPressListener listener) { mLongPressListener = listener; } @@ -210,7 +217,7 @@ public class SwipeHelper implements Gefingerpoken { } } - public boolean onInterceptTouchEvent(MotionEvent ev) { + public boolean onInterceptTouchEvent(final MotionEvent ev) { final int action = ev.getAction(); switch (action) { @@ -232,8 +239,12 @@ public class SwipeHelper implements Gefingerpoken { public void run() { if (mCurrView != null && !mLongPressSent) { mLongPressSent = true; - mCurrView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED); - mLongPressListener.onLongClick(mCurrView); + mCurrView.sendAccessibilityEvent( + AccessibilityEvent.TYPE_VIEW_LONG_CLICKED); + mCurrView.getLocationOnScreen(mTmpPos); + final int x = (int) ev.getRawX() - mTmpPos[0]; + final int y = (int) ev.getRawY() - mTmpPos[1]; + mLongPressListener.onLongPress(mCurrView, x, y); } } }; @@ -262,14 +273,16 @@ public class SwipeHelper implements Gefingerpoken { case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: + final boolean captured = (mDragging || mLongPressSent); mDragging = false; mCurrView = null; mCurrAnimView = null; mLongPressSent = false; removeLongPressCallback(); + if (captured) return true; break; } - return mDragging; + return mDragging || mLongPressSent; } /** @@ -277,6 +290,19 @@ public class SwipeHelper implements Gefingerpoken { * @param velocity The desired pixels/second speed at which the view should move */ public void dismissChild(final View view, float velocity) { + dismissChild(view, velocity, null, 0, false, 0); + } + + /** + * @param view The view to be dismissed + * @param velocity The desired pixels/second speed at which the view should move + * @param endAction The action to perform at the end + * @param delay The delay after which we should start + * @param useAccelerateInterpolator Should an accelerating Interpolator be used + * @param fixedDuration If not 0, this exact duration will be taken + */ + public void dismissChild(final View view, float velocity, final Runnable endAction, + long delay, boolean useAccelerateInterpolator, long fixedDuration) { final View animView = mCallback.getChildContentView(view); final boolean canAnimViewBeDismissed = mCallback.canChildBeDismissed(view); float newPos; @@ -289,22 +315,38 @@ public class SwipeHelper implements Gefingerpoken { } else { newPos = getSize(animView); } - int duration = MAX_ESCAPE_ANIMATION_DURATION; - if (velocity != 0) { - duration = Math.min(duration, - (int) (Math.abs(newPos - getTranslation(animView)) * 1000f / Math - .abs(velocity))); + long duration; + if (fixedDuration == 0) { + duration = MAX_ESCAPE_ANIMATION_DURATION; + if (velocity != 0) { + duration = Math.min(duration, + (int) (Math.abs(newPos - getTranslation(animView)) * 1000f / Math + .abs(velocity)) + ); + } else { + duration = DEFAULT_ESCAPE_ANIMATION_DURATION; + } } else { - duration = DEFAULT_ESCAPE_ANIMATION_DURATION; + duration = fixedDuration; } animView.setLayerType(View.LAYER_TYPE_HARDWARE, null); ObjectAnimator anim = createTranslationAnimation(animView, newPos); - anim.setInterpolator(sLinearInterpolator); + if (useAccelerateInterpolator) { + anim.setInterpolator(mFastOutLinearInInterpolator); + } else { + anim.setInterpolator(sLinearInterpolator); + } anim.setDuration(duration); + if (delay > 0) { + anim.setStartDelay(delay); + } anim.addListener(new AnimatorListenerAdapter() { public void onAnimationEnd(Animator animation) { mCallback.onChildDismissed(view); + if (endAction != null) { + endAction.run(); + } animView.setLayerType(View.LAYER_TYPE_NONE, null); } }); @@ -426,4 +468,15 @@ public class SwipeHelper implements Gefingerpoken { */ boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress); } + + /** + * Equivalent to View.OnLongClickListener with coordinates + */ + public interface LongPressListener { + /** + * Equivalent to {@link View.OnLongClickListener#onLongClick(View)} with coordinates + * @return whether the longpress was handled + */ + boolean onLongPress(View v, int x, int y); + } } diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java index 72a33410747f..25a62ae819fb 100644 --- a/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java +++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java @@ -56,9 +56,7 @@ public class RecentsHorizontalScrollView extends HorizontalScrollView public RecentsHorizontalScrollView(Context context, AttributeSet attrs) { super(context, attrs, 0); - float densityScale = getResources().getDisplayMetrics().density; - float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop(); - mSwipeHelper = new SwipeHelper(SwipeHelper.Y, this, densityScale, pagingTouchSlop); + mSwipeHelper = new SwipeHelper(SwipeHelper.Y, this, context); mFadedEdgeDrawHelper = FadedEdgeDrawHelper.create(context, attrs, this, false); mRecycledViews = new HashSet<View>(); } diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java index 1213375c83f4..e8e9d529e246 100644 --- a/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java +++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java @@ -56,9 +56,7 @@ public class RecentsVerticalScrollView extends ScrollView public RecentsVerticalScrollView(Context context, AttributeSet attrs) { super(context, attrs, 0); - float densityScale = getResources().getDisplayMetrics().density; - float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop(); - mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, densityScale, pagingTouchSlop); + mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, context); mFadedEdgeDrawHelper = FadedEdgeDrawHelper.create(context, attrs, this, true); mRecycledViews = new HashSet<View>(); diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java index fd636edcd1b2..dc8f0db33389 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java @@ -163,6 +163,7 @@ class TaskBarView extends FrameLayout { } else if (t.applicationIcon != null) { mApplicationIcon.setImageDrawable(t.applicationIcon); } + mApplicationIcon.setContentDescription(t.activityLabel); if (!mActivityDescription.getText().toString().equals(t.activityLabel)) { mActivityDescription.setText(t.activityLabel); } @@ -176,6 +177,9 @@ class TaskBarView extends FrameLayout { mConfig.taskBarViewLightTextColor : mConfig.taskBarViewDarkTextColor); mDismissButton.setImageDrawable(t.useLightOnPrimaryColor ? mLightDismissDrawable : mDarkDismissDrawable); + mDismissButton.setContentDescription( + getContext().getString(R.string.accessibility_recents_item_will_be_dismissed, + t.activityLabel)); } /** Unbinds the bar view from the task */ diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java index 32b9d8ab2625..f7f96da6740e 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java @@ -27,12 +27,12 @@ import android.graphics.Rect; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; +import android.view.accessibility.AccessibilityEvent; import android.widget.FrameLayout; import android.widget.OverScroller; import com.android.systemui.R; import com.android.systemui.recents.Constants; import com.android.systemui.recents.RecentsConfiguration; -import com.android.systemui.recents.misc.Console; import com.android.systemui.recents.misc.DozeTrigger; import com.android.systemui.recents.misc.ReferenceCountedTrigger; import com.android.systemui.recents.misc.Utilities; @@ -1045,4 +1045,4 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } } } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java index 04fc02c3c78d..48fc4ec2b824 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java @@ -16,6 +16,9 @@ package com.android.systemui.statusbar; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.TimeInterpolator; import android.app.ActivityManager; import android.app.ActivityManagerNative; import android.app.Notification; @@ -33,6 +36,7 @@ import android.content.pm.UserInfo; import android.content.res.Configuration; import android.database.ContentObserver; import android.graphics.Rect; +import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Build; @@ -57,18 +61,17 @@ import android.util.SparseBooleanArray; import android.view.Display; import android.view.IWindowManager; import android.view.LayoutInflater; -import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; +import android.view.ViewAnimationUtils; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; -import android.view.ViewStub; import android.view.WindowManager; import android.view.WindowManagerGlobal; +import android.view.animation.AnimationUtils; import android.widget.DateTimeView; import android.widget.ImageView; import android.widget.LinearLayout; -import android.widget.PopupMenu; import android.widget.RemoteViews; import android.widget.TextView; @@ -79,6 +82,7 @@ import com.android.internal.util.NotificationColorUtil; import com.android.systemui.R; import com.android.systemui.RecentsComponent; import com.android.systemui.SearchPanelView; +import com.android.systemui.SwipeHelper; import com.android.systemui.SystemUI; import com.android.systemui.statusbar.NotificationData.Entry; import com.android.systemui.statusbar.phone.KeyguardTouchDelegate; @@ -141,8 +145,6 @@ public abstract class BaseStatusBar extends SystemUI implements // Search panel protected SearchPanelView mSearchPanelView; - protected PopupMenu mNotificationBlamePopup; - protected int mCurrentUserId = 0; final protected SparseArray<UserInfo> mCurrentProfiles = new SparseArray<UserInfo>(); @@ -184,6 +186,11 @@ public abstract class BaseStatusBar extends SystemUI implements protected int mZenMode; + // which notification is currently being longpress-examined by the user + private View mNotificationGutsExposed; + + private TimeInterpolator mLinearOutSlowIn, mFastOutLinearIn; + /** * The {@link StatusBarState} of the status bar. */ @@ -192,6 +199,7 @@ public abstract class BaseStatusBar extends SystemUI implements protected boolean mShowLockscreenNotifications; protected NotificationOverflowContainer mKeyguardIconOverflowContainer; + protected DismissView mDismissView; public boolean isDeviceProvisioned() { return mDeviceProvisioned; @@ -415,6 +423,11 @@ public abstract class BaseStatusBar extends SystemUI implements mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); + mLinearOutSlowIn = AnimationUtils.loadInterpolator(mContext, + android.R.interpolator.linear_out_slow_in); + mFastOutLinearIn = AnimationUtils.loadInterpolator(mContext, + android.R.interpolator.fast_out_linear_in); + // Connect in to the status bar manager service StatusBarIconList iconList = new StatusBarIconList(); mCommandQueue = new CommandQueue(this, iconList); @@ -592,29 +605,61 @@ public abstract class BaseStatusBar extends SystemUI implements null, UserHandle.CURRENT); } - protected View.OnLongClickListener getNotificationLongClicker() { - return new View.OnLongClickListener() { + private static final int max(int...args) { + switch (args.length) { + case 0: + return 0; + case 1: + return args[0]; + case 2: + return args[1] > args[0] ? args[1] : args[0]; + default: + int m = args[0]; + for (int i = 0; i < args.length; i++) { + if (args[i] > m) { + m = args[i]; + } + } + return m; + } + } + + protected SwipeHelper.LongPressListener getNotificationLongClicker() { + return new SwipeHelper.LongPressListener() { @Override - public boolean onLongClick(View v) { + public boolean onLongPress(View v, int x, int y) { + dismissPopups(); + final String packageNameF = (String) v.getTag(); if (packageNameF == null) return false; if (v.getWindowToken() == null) return false; - mNotificationBlamePopup = new PopupMenu(mContext, v); - mNotificationBlamePopup.getMenuInflater().inflate( - R.menu.notification_popup_menu, - mNotificationBlamePopup.getMenu()); - mNotificationBlamePopup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { - public boolean onMenuItemClick(MenuItem item) { - if (item.getItemId() == R.id.notification_inspect_item) { - startApplicationDetailsActivity(packageNameF); - animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE); - } else { - return false; - } - return true; + + // Assume we are a status_bar_notification_row + final View guts = v.findViewById(R.id.notification_guts); + if (guts == null) return false; + + // Already showing? + if (guts.getVisibility() == View.VISIBLE) return false; + + final View button = guts.findViewById(R.id.notification_inspect_item); + button.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + startApplicationDetailsActivity(packageNameF); + animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE); } }); - mNotificationBlamePopup.show(); + + guts.setVisibility(View.VISIBLE); + final double horz = Math.max(v.getWidth() - x, x); + final double vert = Math.max(v.getHeight() - y, y); + final float r = (float) Math.hypot(horz, vert); + final Animator a + = ViewAnimationUtils.createCircularReveal(guts, x, y, 0, r); + a.setDuration(400); + a.setInterpolator(mLinearOutSlowIn); + a.start(); + + mNotificationGutsExposed = guts; return true; } @@ -622,9 +667,24 @@ public abstract class BaseStatusBar extends SystemUI implements } public void dismissPopups() { - if (mNotificationBlamePopup != null) { - mNotificationBlamePopup.dismiss(); - mNotificationBlamePopup = null; + if (mNotificationGutsExposed != null) { + final View v = mNotificationGutsExposed; + mNotificationGutsExposed = null; + + final int x = (v.getLeft() + v.getRight()) / 2; + final int y = (v.getTop() + v.getBottom()) / 2; + final Animator a = ViewAnimationUtils.createCircularReveal(v, + x, y, x, 0); + a.setDuration(200); + a.setInterpolator(mFastOutLinearIn); + a.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + v.setVisibility(View.GONE); + } + }); + a.start(); } } @@ -909,6 +969,27 @@ public abstract class BaseStatusBar extends SystemUI implements return inflateViews(entry, parent, true); } + private Drawable loadPackageIconDrawable(String pkg, int userId) { + Drawable icon = null; + try { + icon = mContext.getPackageManager().getApplicationIcon(pkg); + } catch (PackageManager.NameNotFoundException e) { + } + + return icon; + } + + private CharSequence loadPackageName(String pkg) { + final PackageManager pm = mContext.getPackageManager(); + try { + ApplicationInfo info = pm.getApplicationInfo(pkg, + PackageManager.GET_UNINSTALLED_PACKAGES); + if (info != null) return pm.getApplicationLabel(info); + } catch (PackageManager.NameNotFoundException e) { + } + return pkg; + } + private boolean inflateViews(NotificationData.Entry entry, ViewGroup parent, boolean isHeadsUp) { int maxHeight = mRowMaxHeight; StatusBarNotification sbn = entry.notification; @@ -956,8 +1037,15 @@ public abstract class BaseStatusBar extends SystemUI implements parent, false); } - // for blaming (see SwipeHelper.setLongPressListener) + // the notification inspector (see SwipeHelper.setLongPressListener) row.setTag(sbn.getPackageName()); + final View guts = row.findViewById(R.id.notification_guts); + final Drawable pkgicon = loadPackageIconDrawable(entry.notification.getPackageName(), + entry.notification.getUserId()); + final String pkgname = loadPackageName(entry.notification.getPackageName()).toString(); + ((ImageView) row.findViewById(android.R.id.icon)).setImageDrawable(pkgicon); + ((DateTimeView) row.findViewById(R.id.timestamp)).setTime(entry.notification.getPostTime()); + ((TextView) row.findViewById(R.id.pkgname)).setText(pkgname); workAroundBadLayerDrawableOpacity(row); View vetoButton = updateNotificationVetoButton(row, sbn); @@ -1201,6 +1289,9 @@ public abstract class BaseStatusBar extends SystemUI implements protected void visibilityChanged(boolean visible) { if (mPanelSlightlyVisible != visible) { mPanelSlightlyVisible = visible; + if (!visible) { + dismissPopups(); + } try { if (visible) { mBarService.onPanelRevealed(); @@ -1330,9 +1421,12 @@ public abstract class BaseStatusBar extends SystemUI implements } else { mKeyguardIconOverflowContainer.setVisibility(View.GONE); } - // Move overflow container to last position. + // Move overflow container to second last position. mStackScroller.changeViewPosition(mKeyguardIconOverflowContainer, - mStackScroller.getChildCount() - 1); + mStackScroller.getChildCount() - 2); + + // Now move dismissView to the last position. + mStackScroller.changeViewPosition(mDismissView, mStackScroller.getChildCount() - 1); } private boolean shouldShowOnKeyguard(StatusBarNotification sbn) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/DismissView.java b/packages/SystemUI/src/com/android/systemui/statusbar/DismissView.java new file mode 100644 index 000000000000..6cc1a575a87d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/DismissView.java @@ -0,0 +1,129 @@ +/* + * 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 com.android.systemui.statusbar; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.view.animation.AnimationUtils; +import android.view.animation.Interpolator; +import android.view.animation.PathInterpolator; +import android.widget.Button; +import android.widget.TextView; +import com.android.systemui.R; + +public class DismissView extends ExpandableView { + + private Button mClearAllText; + private boolean mIsVisible; + private final Interpolator mAppearInterpolator = new PathInterpolator(0f, 0.2f, 1f, 1f); + private final Interpolator mDisappearInterpolator = new PathInterpolator(0f, 0f, 0.8f, 1f); + + public DismissView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mClearAllText = (Button) findViewById(R.id.dismiss_text); + setInvisible(); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + setOutlineProvider(null); + } + + @Override + public boolean isTransparent() { + return true; + } + + public void performVisibilityAnimation(boolean nowVisible) { + animateText(nowVisible, null /* onFinishedRunnable */); + } + + public void performVisibilityAnimation(boolean nowVisible, Runnable onFinishedRunnable) { + animateText(nowVisible, onFinishedRunnable); + } + + /** + * Animate the text to a new visibility. + * + * @param nowVisible should it now be visible + * @param onFinishedRunnable A runnable which should be run when the animation is + * finished. + */ + public void animateText(boolean nowVisible, Runnable onFinishedRunnable) { + if (nowVisible != mIsVisible) { + // Animate text + float endValue = nowVisible ? 1.0f : 0.0f; + Interpolator interpolator; + if (nowVisible) { + interpolator = mAppearInterpolator; + } else { + interpolator = mDisappearInterpolator; + } + mClearAllText.animate() + .alpha(endValue) + .setInterpolator(interpolator) + .withEndAction(onFinishedRunnable) + .setDuration(260) + .withLayer(); + mIsVisible = nowVisible; + } else { + if (onFinishedRunnable != null) { + onFinishedRunnable.run(); + } + } + } + + public void setInvisible() { + mClearAllText.setAlpha(0.0f); + mIsVisible = false; + } + + @Override + public void performRemoveAnimation(float translationDirection, Runnable onFinishedRunnable) { + performVisibilityAnimation(false); + } + + @Override + public void performAddAnimation(long delay) { + performVisibilityAnimation(true); + } + + @Override + public void setScrimAmount(float scrimAmount) { + // We don't need to scrim the dismissView + } + + public void setOnButtonClickListener(OnClickListener onClickListener) { + mClearAllText.setOnClickListener(onClickListener); + } + + @Override + public boolean hasOverlappingRendering() { + return false; + } + + public void cancelAnimation() { + mClearAllText.animate().cancel(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java index f41e78dff08c..7c6e47cf257a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java @@ -17,23 +17,71 @@ package com.android.systemui.statusbar.phone; import android.content.Context; +import android.graphics.Canvas; import android.graphics.Rect; import android.util.AttributeSet; +import android.view.View; +import android.view.ViewStub; import android.widget.FrameLayout; +import com.android.systemui.R; + /** * The container with notification stack scroller and quick settings inside. */ -public class NotificationsQuickSettingsContainer extends FrameLayout { +public class NotificationsQuickSettingsContainer extends FrameLayout + implements ViewStub.OnInflateListener { + + private View mScrollView; + private View mUserSwitcher; + private View mStackScroller; + private boolean mInflated; public NotificationsQuickSettingsContainer(Context context, AttributeSet attrs) { super(context, attrs); } @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mScrollView = findViewById(R.id.scroll_view); + mStackScroller = findViewById(R.id.notification_stack_scroller); + ViewStub userSwitcher = (ViewStub) findViewById(R.id.keyguard_user_switcher); + userSwitcher.setOnInflateListener(this); + mUserSwitcher = userSwitcher; + } + + @Override protected boolean fitSystemWindows(Rect insets) { setPadding(0, 0, 0, insets.bottom); insets.bottom = 0; return true; } + + @Override + protected boolean drawChild(Canvas canvas, View child, long drawingTime) { + boolean userSwitcherVisible = mInflated && mUserSwitcher.getVisibility() == View.VISIBLE; + + // Invert the order of the scroll view and user switcher such that the notifications receive + // touches first but the panel gets drawn above. + if (child == mScrollView) { + return super.drawChild(canvas, mStackScroller, drawingTime); + } else if (child == mStackScroller) { + return super.drawChild(canvas, userSwitcherVisible ? mUserSwitcher : mScrollView, + drawingTime); + } else if (child == mUserSwitcher) { + return super.drawChild(canvas, userSwitcherVisible ? mScrollView : mUserSwitcher, + drawingTime); + } else { + return super.drawChild(canvas, child, drawingTime); + } + } + + @Override + public void onInflate(ViewStub stub, View inflated) { + if (stub == mUserSwitcher) { + mUserSwitcher = inflated; + mInflated = true; + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java index acfdb06ac66a..13fad7b34d3d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -118,6 +118,7 @@ import com.android.systemui.qs.QSTile; import com.android.systemui.statusbar.ActivatableNotificationView; import com.android.systemui.statusbar.BaseStatusBar; import com.android.systemui.statusbar.CommandQueue; +import com.android.systemui.statusbar.DismissView; import com.android.systemui.statusbar.DragDownHelper; import com.android.systemui.statusbar.ExpandableNotificationRow; import com.android.systemui.statusbar.GestureRecorder; @@ -676,6 +677,15 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, SpeedBumpView speedBump = (SpeedBumpView) LayoutInflater.from(mContext).inflate( R.layout.status_bar_notification_speed_bump, mStackScroller, false); mStackScroller.setSpeedBumpView(speedBump); + mDismissView = (DismissView) LayoutInflater.from(mContext).inflate( + R.layout.status_bar_notification_dismiss_all, mStackScroller, false); + mDismissView.setOnButtonClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + clearAllNotifications(); + } + }); + mStackScroller.setDismissView(mDismissView); mExpandedContents = mStackScroller; mScrimController = new ScrimController(mStatusBarWindow.findViewById(R.id.scrim_behind), @@ -828,6 +838,73 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, return mStatusBarView; } + private void clearAllNotifications() { + + // animate-swipe all dismissable notifications, then animate the shade closed + int numChildren = mStackScroller.getChildCount(); + + final ArrayList<View> viewsToHide = new ArrayList<View>(numChildren); + for (int i = 0; i < numChildren; i++) { + final View child = mStackScroller.getChildAt(i); + if (mStackScroller.canChildBeDismissed(child)) { + if (child.getVisibility() == View.VISIBLE) { + viewsToHide.add(child); + } + } + } + if (viewsToHide.isEmpty()) { + animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE); + return; + } + + mPostCollapseCleanup = new Runnable() { + @Override + public void run() { + try { + mBarService.onClearAllNotifications(mCurrentUserId); + } catch (Exception ex) { } + } + }; + + performDismissAllAnimations(viewsToHide); + + } + + private void performDismissAllAnimations(ArrayList<View> hideAnimatedList) { + Runnable animationFinishAction = new Runnable() { + @Override + public void run() { + mStackScroller.post(new Runnable() { + @Override + public void run() { + mStackScroller.setDismissAllInProgress(false); + } + }); + animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE); + } + }; + + // let's disable our normal animations + mStackScroller.setDismissAllInProgress(true); + + // Decrease the delay for every row we animate to give the sense of + // accelerating the swipes + int rowDelayDecrement = 10; + int currentDelay = 140; + int totalDelay = 0; + int numItems = hideAnimatedList.size(); + for (int i = 0; i < numItems; i++) { + View view = hideAnimatedList.get(i); + Runnable endRunnable = null; + if (i == numItems - 1) { + endRunnable = animationFinishAction; + } + mStackScroller.dismissViewAnimated(view, endRunnable, totalDelay, 260); + currentDelay = Math.max(50, currentDelay - rowDelayDecrement); + totalDelay += currentDelay; + } + } + /** * Hack to improve glyph rasterization for scaled text views. */ @@ -1338,10 +1415,32 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } updateRowStates(); updateSpeedbump(); + updateClearAll(); + mNotificationPanel.setQsExpansionEnabled(provisioned && mUserSetup); mShadeUpdates.check(); } + private void updateClearAll() { + boolean showDismissView = false; + if (mState != StatusBarState.KEYGUARD) { + for (int i = 0; i < mNotificationData.size(); i++) { + Entry entry = mNotificationData.get(i); + if (entry.row.getParent() == null) { + // This view isn't even added, so the stack scroller doesn't + // know about it. Ignore completely. + continue; + } + if (entry.row.getVisibility() != View.GONE && entry.expanded != null + && entry.notification.isClearable()) { + showDismissView = true; + break; + } + } + } + mStackScroller.updateDismissView(showDismissView); + } + private void updateSpeedbump() { int speedbumpIndex = -1; int currentIndex = 0; @@ -2021,6 +2120,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, public void animateCollapsePanels(int flags, boolean force) { if (!force && (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED)) { + if (mPostCollapseCleanup != null) { + mPostCollapseCleanup.run(); + mPostCollapseCleanup = null; + } return; } if (SPEW) { @@ -3446,6 +3549,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } public void onTrackingStarted() { + if (mPostCollapseCleanup != null) { + mPostCollapseCleanup.run(); + mPostCollapseCleanup = null; + } } public void onUnlockHintStarted() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java index bac1d5b7d43c..65359ee4d0f1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java @@ -98,7 +98,7 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL private ActivityStarter mActivityStarter; private BatteryController mBatteryController; private QSPanel mQSPanel; - private boolean mHasKeyguardUserSwitcher; + private KeyguardUserSwitcher mKeyguardUserSwitcher; private final Rect mClipBounds = new Rect(); private final StatusIconClipper mStatusIconClipper = new StatusIconClipper(); @@ -300,6 +300,9 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL ? VISIBLE : GONE); mBatteryLevel.setVisibility(mKeyguardShowing && mCharging || mExpanded && !mOverscrolled ? View.VISIBLE : View.GONE); + if (mExpanded && !mOverscrolled && mKeyguardUserSwitcherShowing) { + mKeyguardUserSwitcher.hide(); + } } private void updateSystemIconsLayoutParams() { @@ -377,7 +380,7 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL mDateTime.setClickable(mExpanded); boolean keyguardSwitcherAvailable = - mHasKeyguardUserSwitcher && mKeyguardShowing && !mExpanded; + mKeyguardUserSwitcher != null && mKeyguardShowing && !mExpanded; mMultiUserSwitch.setClickable(mExpanded || keyguardSwitcherAvailable); mMultiUserSwitch.setKeyguardMode(keyguardSwitcherAvailable); mSystemIconsSuperContainer.setClickable(mExpanded); @@ -516,7 +519,7 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL } public void setKeyguarUserSwitcher(KeyguardUserSwitcher keyguardUserSwitcher) { - mHasKeyguardUserSwitcher = true; + mKeyguardUserSwitcher = keyguardUserSwitcher; mMultiUserSwitch.setKeyguardUserSwitcher(keyguardUserSwitcher); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java index f3ff8ad7b092..ac260dbb39a9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java @@ -176,11 +176,9 @@ public class HeadsUpNotificationView extends FrameLayout implements SwipeHelper. @Override public void onAttachedToWindow() { - float densityScale = getResources().getDisplayMetrics().density; final ViewConfiguration viewConfiguration = ViewConfiguration.get(getContext()); - float pagingTouchSlop = viewConfiguration.getScaledPagingTouchSlop(); float touchSlop = viewConfiguration.getScaledTouchSlop(); - mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, densityScale, pagingTouchSlop); + mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, getContext()); mSwipeHelper.setMaxSwipeProgress(mMaxAlpha); mEdgeSwipeHelper = new EdgeSwipeHelper(touchSlop); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java index b0bab48d6c34..2be566cf5e83 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java @@ -88,7 +88,7 @@ public class KeyguardUserSwitcher { } } - private void hide() { + public void hide() { if (mUserSwitcher != null) { // TODO: animate mUserSwitcher.setVisibility(View.GONE); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java index 9bcffd1b1735..8b4e79fd7bc8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java @@ -35,6 +35,7 @@ import com.android.systemui.ExpandHelper; import com.android.systemui.R; import com.android.systemui.SwipeHelper; import com.android.systemui.statusbar.ActivatableNotificationView; +import com.android.systemui.statusbar.DismissView; import com.android.systemui.statusbar.ExpandableNotificationRow; import com.android.systemui.statusbar.ExpandableView; import com.android.systemui.statusbar.SpeedBumpView; @@ -139,6 +140,8 @@ public class NotificationStackScrollLayout extends ViewGroup private boolean mExpandingNotification; private boolean mExpandedInThisMotion; private boolean mScrollingEnabled; + private DismissView mDismissView; + private boolean mDismissAllInProgress; /** * Was the scroller scrolled to the top when the down motion was observed? @@ -161,7 +164,7 @@ public class NotificationStackScrollLayout extends ViewGroup * motion. */ private int mMaxScrollAfterExpand; - private OnLongClickListener mLongClickListener; + private SwipeHelper.LongPressListener mLongPressListener; /** * Should in this touch motion only be scrolling allowed? It's true when the scroller was @@ -172,6 +175,7 @@ public class NotificationStackScrollLayout extends ViewGroup private boolean mInterceptDelegateEnabled; private boolean mDelegateToScrollView; private boolean mDisallowScrollingInThisMotion; + private boolean mDismissWillBeGone; private ViewTreeObserver.OnPreDrawListener mChildrenUpdater = new ViewTreeObserver.OnPreDrawListener() { @@ -238,8 +242,8 @@ public class NotificationStackScrollLayout extends ViewGroup mOverflingDistance = configuration.getScaledOverflingDistance(); float densityScale = getResources().getDisplayMetrics().density; float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop(); - mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, densityScale, pagingTouchSlop); - mSwipeHelper.setLongPressListener(mLongClickListener); + mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, getContext()); + mSwipeHelper.setLongPressListener(mLongPressListener); mSidePaddings = context.getResources() .getDimensionPixelSize(R.dimen.notification_side_padding); @@ -467,9 +471,9 @@ public class NotificationStackScrollLayout extends ViewGroup return mBottomStackPeekSize; } - public void setLongPressListener(View.OnLongClickListener listener) { + public void setLongPressListener(SwipeHelper.LongPressListener listener) { mSwipeHelper.setLongPressListener(listener); - mLongClickListener = listener; + mLongPressListener = listener; } public void setScrollView(ViewGroup scrollView) { @@ -481,6 +485,9 @@ public class NotificationStackScrollLayout extends ViewGroup } public void onChildDismissed(View v) { + if (mDismissAllInProgress) { + return; + } if (DEBUG) Log.v(TAG, "onChildDismissed: " + v); final View veto = v.findViewById(R.id.veto); if (veto != null && veto.getVisibility() != View.GONE) { @@ -627,8 +634,9 @@ public class NotificationStackScrollLayout extends ViewGroup initView(getContext()); } - public void dismissRowAnimated(View child, int vel) { - mSwipeHelper.dismissChild(child, vel); + public void dismissViewAnimated(View child, Runnable endRunnable, int delay, long duration) { + child.setClipBounds(null); + mSwipeHelper.dismissChild(child, 0, endRunnable, delay, true, duration); } @Override @@ -1526,12 +1534,13 @@ public class NotificationStackScrollLayout extends ViewGroup * @param newIndex the new index */ public void changeViewPosition(View child, int newIndex) { - if (child != null && child.getParent() == this) { + int currentIndex = indexOfChild(child); + if (child != null && child.getParent() == this && currentIndex != newIndex) { mChangePositionInProgress = true; removeView(child); addView(child, newIndex); mChangePositionInProgress = false; - if (mIsExpanded && mAnimationsEnabled) { + if (mIsExpanded && mAnimationsEnabled && child.getVisibility() != View.GONE) { mChildrenChangingPositions.add(child); mNeedsAnimation = true; } @@ -1918,7 +1927,8 @@ public class NotificationStackScrollLayout extends ViewGroup } public void goToFullShade() { - updateSpeedBump(true); + updateSpeedBump(true /* visibility */); + mDismissView.setInvisible(); } public void cancelExpandHelper() { @@ -1962,6 +1972,40 @@ public class NotificationStackScrollLayout extends ViewGroup requestChildrenUpdate(); } + public void setDismissView(DismissView dismissView) { + mDismissView = dismissView; + addView(mDismissView); + } + + public void updateDismissView(boolean visible) { + int oldVisibility = mDismissWillBeGone ? GONE : mDismissView.getVisibility(); + int newVisibility = visible ? VISIBLE : GONE; + if (oldVisibility != newVisibility) { + if (oldVisibility == GONE) { + if (mDismissWillBeGone) { + mDismissView.cancelAnimation(); + } else { + mDismissView.setInvisible(); + mDismissView.setVisibility(newVisibility); + } + mDismissWillBeGone = false; + } else { + mDismissWillBeGone = true; + mDismissView.performVisibilityAnimation(false, new Runnable() { + @Override + public void run() { + mDismissView.setVisibility(GONE); + mDismissWillBeGone = false; + } + }); + } + } + } + + public void setDismissAllInProgress(boolean dismissAllInProgress) { + mDismissAllInProgress = dismissAllInProgress; + } + /** * A listener that is notified when some child locations might have changed. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java index f48739c708f9..76115a7a75ad 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java @@ -21,6 +21,8 @@ import android.util.Log; import android.view.View; import android.view.ViewGroup; +import com.android.systemui.R; +import com.android.systemui.statusbar.DismissView; import com.android.systemui.statusbar.ExpandableView; import com.android.systemui.statusbar.SpeedBumpView; @@ -38,10 +40,13 @@ public class StackScrollState { private final ViewGroup mHostView; private Map<ExpandableView, ViewState> mStateMap; private final Rect mClipRect = new Rect(); + private final int mClearAllTopPadding; public StackScrollState(ViewGroup hostView) { mHostView = hostView; mStateMap = new HashMap<ExpandableView, ViewState>(); + mClearAllTopPadding = hostView.getContext().getResources().getDimensionPixelSize( + R.dimen.clear_all_padding_top); } public ViewGroup getHostView() { @@ -90,6 +95,7 @@ public class StackScrollState { if (!state.gone) { float alpha = child.getAlpha(); float yTranslation = child.getTranslationY(); + float xTranslation = child.getTranslationX(); float zTranslation = child.getTranslationZ(); float scale = child.getScaleX(); int height = child.getActualHeight(); @@ -99,7 +105,7 @@ public class StackScrollState { float newScale = state.scale; int newHeight = state.height; boolean becomesInvisible = newAlpha == 0.0f; - if (alpha != newAlpha) { + if (alpha != newAlpha && xTranslation == 0) { // apply layer type boolean becomesFullyVisible = newAlpha == 1.0f; boolean newLayerTypeIsHardware = !becomesInvisible && !becomesFullyVisible; @@ -167,6 +173,10 @@ public class StackScrollState { if(child instanceof SpeedBumpView) { float lineEnd = newYTranslation + newHeight / 2; performSpeedBumpAnimation(i, (SpeedBumpView) child, lineEnd); + } else if (child instanceof DismissView) { + DismissView dismissView = (DismissView) child; + boolean visible = state.topOverLap < mClearAllTopPadding; + dismissView.performVisibilityAnimation(visible); } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java index 0c8467569f3e..71524ec46498 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java @@ -159,7 +159,7 @@ public class StackStateAnimator { } // start alpha animation - if (alphaChanging) { + if (alphaChanging && child.getTranslationX() == 0) { startAlphaAnimation(child, viewState, delay); } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index f38b2804c812..3f95ae2b3c49 100755 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -3098,7 +3098,8 @@ public final class ActivityManagerService extends ActivityManagerNative final BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics(); if (resumed) { if (mUsageStatsService != null) { - mUsageStatsService.reportEvent(component.realActivity, System.currentTimeMillis(), + mUsageStatsService.reportEvent(component.realActivity, component.userId, + System.currentTimeMillis(), UsageStats.Event.MOVE_TO_FOREGROUND); } synchronized (stats) { @@ -3106,7 +3107,8 @@ public final class ActivityManagerService extends ActivityManagerNative } } else { if (mUsageStatsService != null) { - mUsageStatsService.reportEvent(component.realActivity, System.currentTimeMillis(), + mUsageStatsService.reportEvent(component.realActivity, component.userId, + System.currentTimeMillis(), UsageStats.Event.MOVE_TO_BACKGROUND); } synchronized (stats) { diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index 390fbba3448c..1c0057bc568a 100755 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -3789,7 +3789,9 @@ final class ActivityStack { if (r.app == app) { Slog.w(TAG, " Force finishing activity " + r.intent.getComponent().flattenToShortString()); - finishActivityLocked(r, Activity.RESULT_CANCELED, null, "crashed", false); + // Force the destroy to skip right to removal. + r.app = null; + finishCurrentActivityLocked(r, FINISH_IMMEDIATELY, false); } } } diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index db915e2a2a7f..6036bcfd2d90 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -24,10 +24,11 @@ import android.app.AppOpsManager; import android.content.Context; import android.content.pm.IPackageDeleteObserver; import android.content.pm.IPackageInstaller; -import android.content.pm.IPackageInstallerObserver; +import android.content.pm.IPackageInstallerCallback; import android.content.pm.IPackageInstallerSession; import android.content.pm.InstallSessionInfo; import android.content.pm.InstallSessionParams; +import android.content.pm.PackageManager; import android.os.Binder; import android.os.FileUtils; import android.os.HandlerThread; @@ -54,6 +55,7 @@ import java.io.FilenameFilter; import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.Objects; public class PackageInstallerService extends IPackageInstaller.Stub { private static final String TAG = "PackageInstaller"; @@ -80,7 +82,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub { @GuardedBy("mSessions") private final SparseArray<PackageInstallerSession> mHistoricalSessions = new SparseArray<>(); - private RemoteCallbackList<IPackageInstallerObserver> mObservers = new RemoteCallbackList<>(); + private RemoteCallbackList<IPackageInstallerCallback> mCallbacks = new RemoteCallbackList<>(); private static final FilenameFilter sStageFilter = new FilenameFilter() { @Override @@ -152,8 +154,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub { } @Override - public int createSession(String installerPackageName, InstallSessionParams params, - int userId) { + public int createSession(InstallSessionParams params, String installerPackageName, int userId) { final int callingUid = Binder.getCallingUid(); mPm.enforceCrossUserPermission(callingUid, userId, true, "createSession"); @@ -172,14 +173,18 @@ public class PackageInstallerService extends IPackageInstaller.Stub { params.installFlags |= INSTALL_REPLACE_EXISTING; } - if (params.mode == InstallSessionParams.MODE_INVALID) { - throw new IllegalArgumentException("Params must have valid mode set"); + switch (params.mode) { + case InstallSessionParams.MODE_FULL_INSTALL: + case InstallSessionParams.MODE_INHERIT_EXISTING: + break; + default: + throw new IllegalArgumentException("Params must have valid mode set"); } // Sanity check that install could fit - if (params.deltaSize > 0) { + if (params.sizeBytes > 0) { try { - mPm.freeStorage(params.deltaSize); + mPm.freeStorage(params.sizeBytes); } catch (IOException e) { throw ExceptionUtils.wrap(e); } @@ -248,8 +253,22 @@ public class PackageInstallerService extends IPackageInstaller.Stub { } @Override - public List<InstallSessionInfo> getSessions(int userId) { - mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "getSessions"); + public InstallSessionInfo getSessionInfo(int sessionId) { + synchronized (mSessions) { + final PackageInstallerSession session = mSessions.get(sessionId); + final boolean isOwner = (session != null) + && (session.installerUid == Binder.getCallingUid()); + if (!isOwner) { + enforceCallerCanReadSessions(); + } + return session != null ? session.generateInfo() : null; + } + } + + @Override + public List<InstallSessionInfo> getAllSessions(int userId) { + mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "getAllSessions"); + enforceCallerCanReadSessions(); final List<InstallSessionInfo> result = new ArrayList<>(); synchronized (mSessions) { @@ -264,9 +283,29 @@ public class PackageInstallerService extends IPackageInstaller.Stub { } @Override + public List<InstallSessionInfo> getMySessions(String installerPackageName, int userId) { + mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "getMySessions"); + mAppOps.checkPackage(Binder.getCallingUid(), installerPackageName); + + final List<InstallSessionInfo> result = new ArrayList<>(); + synchronized (mSessions) { + for (int i = 0; i < mSessions.size(); i++) { + final PackageInstallerSession session = mSessions.valueAt(i); + if (Objects.equals(session.installerPackageName, installerPackageName) + && session.userId == userId) { + result.add(session.generateInfo()); + } + } + } + return result; + } + + @Override public void uninstall(String packageName, int flags, IPackageDeleteObserver observer, int userId) { mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "uninstall"); + + // TODO: enforce installer of record or permission mPm.deletePackageAsUser(packageName, observer, userId, flags); } @@ -280,17 +319,16 @@ public class PackageInstallerService extends IPackageInstaller.Stub { } @Override - public void registerObserver(IPackageInstallerObserver observer, int userId) { - mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "registerObserver"); + public void registerCallback(IPackageInstallerCallback callback, int userId) { + mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "registerCallback"); + enforceCallerCanReadSessions(); - // TODO: consider restricting to active launcher app only - mObservers.register(observer, new UserHandle(userId)); + mCallbacks.register(callback, new UserHandle(userId)); } @Override - public void unregisterObserver(IPackageInstallerObserver observer, int userId) { - mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "unregisterObserver"); - mObservers.unregister(observer); + public void unregisterCallback(IPackageInstallerCallback callback) { + mCallbacks.unregister(callback); } private int getSessionUserId(int sessionId) { @@ -299,52 +337,68 @@ public class PackageInstallerService extends IPackageInstaller.Stub { } } + /** + * We allow those with permission, or the current home app. + */ + private void enforceCallerCanReadSessions() { + final boolean hasPermission = (mContext.checkCallingOrSelfPermission( + android.Manifest.permission.READ_INSTALL_SESSIONS) + == PackageManager.PERMISSION_GRANTED); + final boolean isHomeApp = mPm.checkCallerIsHomeApp(); + if (hasPermission || isHomeApp) { + return; + } else { + throw new SecurityException("Caller must be current home app to read install sessions"); + } + } + private void notifySessionCreated(InstallSessionInfo info) { final int userId = getSessionUserId(info.sessionId); - final int n = mObservers.beginBroadcast(); + final int n = mCallbacks.beginBroadcast(); for (int i = 0; i < n; i++) { - final IPackageInstallerObserver observer = mObservers.getBroadcastItem(i); - final UserHandle user = (UserHandle) mObservers.getBroadcastCookie(i); + final IPackageInstallerCallback callback = mCallbacks.getBroadcastItem(i); + final UserHandle user = (UserHandle) mCallbacks.getBroadcastCookie(i); + // TODO: dispatch notifications for slave profiles if (userId == user.getIdentifier()) { try { - observer.onSessionCreated(info); + callback.onSessionCreated(info.sessionId); } catch (RemoteException ignored) { } } } - mObservers.finishBroadcast(); + mCallbacks.finishBroadcast(); } - private void notifySessionProgress(int sessionId, int progress) { + private void notifySessionProgressChanged(int sessionId, float progress) { final int userId = getSessionUserId(sessionId); - final int n = mObservers.beginBroadcast(); + final int n = mCallbacks.beginBroadcast(); for (int i = 0; i < n; i++) { - final IPackageInstallerObserver observer = mObservers.getBroadcastItem(i); - final UserHandle user = (UserHandle) mObservers.getBroadcastCookie(i); + final IPackageInstallerCallback callback = mCallbacks.getBroadcastItem(i); + final UserHandle user = (UserHandle) mCallbacks.getBroadcastCookie(i); if (userId == user.getIdentifier()) { try { - observer.onSessionProgress(sessionId, progress); + callback.onSessionProgressChanged(sessionId, progress); } catch (RemoteException ignored) { } } } - mObservers.finishBroadcast(); + mCallbacks.finishBroadcast(); } private void notifySessionFinished(int sessionId, boolean success) { final int userId = getSessionUserId(sessionId); - final int n = mObservers.beginBroadcast(); + final int n = mCallbacks.beginBroadcast(); for (int i = 0; i < n; i++) { - final IPackageInstallerObserver observer = mObservers.getBroadcastItem(i); - final UserHandle user = (UserHandle) mObservers.getBroadcastCookie(i); + final IPackageInstallerCallback callback = mCallbacks.getBroadcastItem(i); + final UserHandle user = (UserHandle) mCallbacks.getBroadcastCookie(i); if (userId == user.getIdentifier()) { try { - observer.onSessionFinished(sessionId, success); + callback.onSessionFinished(sessionId, success); } catch (RemoteException ignored) { } } } - mObservers.finishBroadcast(); + mCallbacks.finishBroadcast(); } void dump(IndentingPrintWriter pw) { @@ -374,8 +428,8 @@ public class PackageInstallerService extends IPackageInstaller.Stub { } class Callback { - public void onSessionProgress(PackageInstallerSession session, int progress) { - notifySessionProgress(session.sessionId, progress); + public void onSessionProgressChanged(PackageInstallerSession session, float progress) { + notifySessionProgressChanged(session.sessionId, progress); } public void onSessionFinished(PackageInstallerSession session, boolean success) { diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 0e6a3f07e0fc..06e1d5359899 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -71,6 +71,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { // TODO: enforce INSTALL_ALLOW_DOWNGRADE // TODO: handle INSTALL_EXTERNAL, INSTALL_INTERNAL + // TODO: treat INHERIT_EXISTING as installExistingPackage() + private final PackageInstallerService.Callback mCallback; private final PackageManagerService mPm; private final Handler mHandler; @@ -84,7 +86,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { public final long createdMillis; public final File sessionStageDir; - private static final int MSG_INSTALL = 0; + private static final int MSG_COMMIT = 0; private Handler.Callback mHandlerCallback = new Handler.Callback() { @Override @@ -95,7 +97,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } try { - installLocked(); + commitLocked(); } catch (PackageManagerException e) { Slog.e(TAG, "Install failed: " + e); destroyInternal(); @@ -114,8 +116,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { private final Object mLock = new Object(); - private int mClientProgress; - private int mProgress = 0; + private float mClientProgress; + private float mProgress = 0; private String mPackageName; private int mVersionCode; @@ -168,23 +170,23 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { info.progress = mProgress; info.mode = params.mode; - info.packageName = params.packageName; - info.icon = params.icon; - info.title = params.title; + info.sizeBytes = params.sizeBytes; + info.appPackageName = params.appPackageName; + info.appIcon = params.appIcon; + info.appLabel = params.appLabel; return info; } @Override - public void setClientProgress(int progress) { + public void setClientProgress(float progress) { mClientProgress = progress; - mProgress = MathUtils.constrain( - (int) (((float) mClientProgress) / ((float) params.progressMax)) * 80, 0, 80); - mCallback.onSessionProgress(this, mProgress); + mProgress = MathUtils.constrain(mClientProgress * 0.8f, 0f, 0.8f); + mCallback.onSessionProgressChanged(this, mProgress); } @Override - public void addClientProgress(int progress) { + public void addClientProgress(float progress) { setClientProgress(mClientProgress + progress); } @@ -250,12 +252,12 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } @Override - public void install(IPackageInstallObserver2 observer) { + public void commit(IPackageInstallObserver2 observer) { Preconditions.checkNotNull(observer); - mHandler.obtainMessage(MSG_INSTALL, observer).sendToTarget(); + mHandler.obtainMessage(MSG_COMMIT, observer).sendToTarget(); } - private void installLocked() throws PackageManagerException { + private void commitLocked() throws PackageManagerException { if (mInvalid) { throw new PackageManagerException(INSTALL_FAILED_ALREADY_EXISTS, "Invalid session"); } @@ -295,7 +297,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } // TODO: surface more granular state from dexopt - mCallback.onSessionProgress(this, 90); + mCallback.onSessionProgressChanged(this, 0.9f); // TODO: for ASEC based applications, grow and stream in packages @@ -458,7 +460,12 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } @Override - public void destroy() { + public void close() { + // Currently ignored + } + + @Override + public void abandon() { try { destroyInternal(); } finally { diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 7ddde626a5a1..9878d1c0059c 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -11686,6 +11686,47 @@ public class PackageManagerService extends IPackageManager.Stub { preferred.activityInfo.name); } + /** + * Check if calling UID is the current home app. This handles both the case + * where the user has selected a specific home app, and where there is only + * one home app. + */ + public boolean checkCallerIsHomeApp() { + final Intent intent = new Intent(Intent.ACTION_MAIN); + intent.addCategory(Intent.CATEGORY_HOME); + + final int callingUid = Binder.getCallingUid(); + final int callingUserId = UserHandle.getCallingUserId(); + final List<ResolveInfo> allHomes = queryIntentActivities(intent, null, 0, callingUserId); + final ResolveInfo preferredHome = findPreferredActivity(intent, null, 0, allHomes, 0, true, + false, false, callingUserId); + + if (preferredHome != null) { + if (callingUid == preferredHome.activityInfo.applicationInfo.uid) { + return true; + } + } else { + for (ResolveInfo info : allHomes) { + if (callingUid == info.activityInfo.applicationInfo.uid) { + return true; + } + } + } + + return false; + } + + /** + * Enforce that calling UID is the current home app. This handles both the + * case where the user has selected a specific home app, and where there is + * only one home app. + */ + public void enforceCallerIsHomeApp() { + if (!checkCallerIsHomeApp()) { + throw new SecurityException("Caller is not currently selected home app"); + } + } + @Override public void setApplicationEnabledSetting(String appPackageName, int newState, int flags, int userId, String callingPackage) { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index e0612eb2e0e8..dc55e6d5bbba 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -4291,9 +4291,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (Settings.Secure.getIntForUser(resolver, Settings.Secure.USER_SETUP_COMPLETE, 0, userHandle) != 0) { DevicePolicyData policy = getUserData(userHandle); - policy.mUserSetupComplete = true; - synchronized (this) { - saveSettingsLocked(userHandle); + if (!policy.mUserSetupComplete) { + policy.mUserSetupComplete = true; + synchronized (this) { + saveSettingsLocked(userHandle); + } } } } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 9e67908cca49..0cc6e0fcd28d 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -352,6 +352,9 @@ public final class SystemServer { mFirstBoot = mPackageManagerService.isFirstBoot(); mPackageManager = mSystemContext.getPackageManager(); + Slog.i(TAG, "User Service"); + ServiceManager.addService(Context.USER_SERVICE, UserManagerService.getInstance()); + // Initialize attribute cache used to cache resources from packages. AttributeCache.init(mSystemContext); @@ -434,10 +437,6 @@ public final class SystemServer { Slog.i(TAG, "Entropy Mixer"); ServiceManager.addService("entropy", new EntropyMixer(context)); - Slog.i(TAG, "User Service"); - ServiceManager.addService(Context.USER_SERVICE, - UserManagerService.getInstance()); - mContentResolver = context.getContentResolver(); // The AccountManager must come before the ContentService diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index 4018deff7f1b..1c20d5d834a1 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -19,52 +19,56 @@ package com.android.server.usage; import android.Manifest; import android.app.AppOpsManager; import android.app.usage.IUsageStatsManager; -import android.app.usage.PackageUsageStats; -import android.app.usage.TimeSparseArray; import android.app.usage.UsageStats; import android.app.usage.UsageStatsManager; import android.app.usage.UsageStatsManagerInternal; +import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.PackageManager; +import android.content.pm.UserInfo; import android.os.Binder; import android.os.Environment; import android.os.Handler; import android.os.Looper; import android.os.Message; +import android.os.UserHandle; +import android.os.UserManager; import android.util.ArraySet; import android.util.Slog; +import android.util.SparseArray; + import com.android.internal.os.BackgroundThread; import com.android.server.SystemService; import java.io.File; -import java.io.IOException; -import java.text.SimpleDateFormat; -import java.util.Calendar; +import java.util.Arrays; +import java.util.List; -public class UsageStatsService extends SystemService { +public class UsageStatsService extends SystemService implements + UserUsageStatsService.StatsUpdatedListener { static final String TAG = "UsageStatsService"; static final boolean DEBUG = false; private static final long TEN_SECONDS = 10 * 1000; private static final long TWENTY_MINUTES = 20 * 60 * 1000; private static final long FLUSH_INTERVAL = DEBUG ? TEN_SECONDS : TWENTY_MINUTES; - private static final int USAGE_STAT_RESULT_LIMIT = 10; - private static final SimpleDateFormat sDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + static final int USAGE_STAT_RESULT_LIMIT = 10; // Handler message types. static final int MSG_REPORT_EVENT = 0; static final int MSG_FLUSH_TO_DISK = 1; + static final int MSG_REMOVE_USER = 2; - final Object mLock = new Object(); + private final Object mLock = new Object(); Handler mHandler; AppOpsManager mAppOps; + UserManager mUserManager; - private UsageStatsDatabase mDatabase; - private UsageStats[] mCurrentStats = new UsageStats[UsageStatsManager.BUCKET_COUNT]; - private TimeSparseArray<UsageStats.Event> mCurrentEvents = new TimeSparseArray<>(); - private boolean mStatsChanged = false; - private Calendar mDailyExpiryDate; + private final SparseArray<UserUsageStatsService> mUserState = new SparseArray<>(); + private File mUsageStatsDir; public UsageStatsService(Context context) { super(context); @@ -72,115 +76,96 @@ public class UsageStatsService extends SystemService { @Override public void onStart() { - mDailyExpiryDate = Calendar.getInstance(); mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE); + mUserManager = (UserManager) getContext().getSystemService(Context.USER_SERVICE); mHandler = new H(BackgroundThread.get().getLooper()); File systemDataDir = new File(Environment.getDataDirectory(), "system"); - mDatabase = new UsageStatsDatabase(new File(systemDataDir, "usagestats")); - mDatabase.init(); + mUsageStatsDir = new File(systemDataDir, "usagestats"); + mUsageStatsDir.mkdirs(); + if (!mUsageStatsDir.exists()) { + throw new IllegalStateException("Usage stats directory does not exist: " + + mUsageStatsDir.getAbsolutePath()); + } + + getContext().registerReceiver(new UserRemovedReceiver(), + new IntentFilter(Intent.ACTION_USER_REMOVED)); synchronized (mLock) { - initLocked(); + cleanUpRemovedUsersLocked(); } publishLocalService(UsageStatsManagerInternal.class, new LocalService()); publishBinderService(Context.USAGE_STATS_SERVICE, new BinderService()); } - private void initLocked() { - int nullCount = 0; - for (int i = 0; i < mCurrentStats.length; i++) { - mCurrentStats[i] = mDatabase.getLatestUsageStats(i); - if (mCurrentStats[i] == null) { - nullCount++; + private class UserRemovedReceiver extends BroadcastReceiver { + + @Override + public void onReceive(Context context, Intent intent) { + if (intent != null && intent.getAction().equals(Intent.ACTION_USER_REMOVED)) { + final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); + if (userId >= 0) { + mHandler.obtainMessage(MSG_REMOVE_USER, userId, 0).sendToTarget(); + } } } + } - if (nullCount > 0) { - if (nullCount != mCurrentStats.length) { - // This is weird, but we shouldn't fail if something like this - // happens. - Slog.w(TAG, "Some stats have no latest available"); - } else { - // This must be first boot. - } + @Override + public void onStatsUpdated() { + mHandler.sendEmptyMessageDelayed(MSG_FLUSH_TO_DISK, FLUSH_INTERVAL); + } - // By calling loadActiveStatsLocked, we will - // generate new stats for each bucket. - loadActiveStatsLocked(); - } else { - // Set up the expiry date to be one day from the latest daily stat. - // This may actually be today and we will rollover on the first event - // that is reported. - mDailyExpiryDate.setTimeInMillis( - mCurrentStats[UsageStatsManager.DAILY_BUCKET].mBeginTimeStamp); - mDailyExpiryDate.add(Calendar.DAY_OF_YEAR, 1); - UsageStatsUtils.truncateDateTo(UsageStatsManager.DAILY_BUCKET, mDailyExpiryDate); - Slog.i(TAG, "Rollover scheduled for " + sDateFormat.format(mDailyExpiryDate.getTime())); + private void cleanUpRemovedUsersLocked() { + final List<UserInfo> users = mUserManager.getUsers(true); + if (users == null || users.size() == 0) { + throw new IllegalStateException("There can't be no users"); } - // Now close off any events that were open at the time this was saved. - for (UsageStats stat : mCurrentStats) { - final int pkgCount = stat.getPackageCount(); - for (int i = 0; i < pkgCount; i++) { - PackageUsageStats pkgStats = stat.getPackage(i); - if (pkgStats.mLastEvent == UsageStats.Event.MOVE_TO_FOREGROUND || - pkgStats.mLastEvent == UsageStats.Event.CONTINUE_PREVIOUS_DAY) { - updateStatsLocked(stat, pkgStats.mPackageName, stat.mLastTimeSaved, - UsageStats.Event.END_OF_DAY); - notifyStatsChangedLocked(); - } - } + ArraySet<String> toDelete = new ArraySet<>(); + String[] fileNames = mUsageStatsDir.list(); + if (fileNames == null) { + // No users to delete. + return; } - } - private void rolloverStatsLocked() { - final long startTime = System.currentTimeMillis(); - Slog.i(TAG, "Rolling over usage stats"); - - // Finish any ongoing events with an END_OF_DAY event. Make a note of which components - // need a new CONTINUE_PREVIOUS_DAY entry. - ArraySet<String> continuePreviousDay = new ArraySet<>(); - for (UsageStats stat : mCurrentStats) { - final int pkgCount = stat.getPackageCount(); - for (int i = 0; i < pkgCount; i++) { - PackageUsageStats pkgStats = stat.getPackage(i); - if (pkgStats.mLastEvent == UsageStats.Event.MOVE_TO_FOREGROUND || - pkgStats.mLastEvent == UsageStats.Event.CONTINUE_PREVIOUS_DAY) { - continuePreviousDay.add(pkgStats.mPackageName); - updateStatsLocked(stat, pkgStats.mPackageName, - mDailyExpiryDate.getTimeInMillis() - 1, UsageStats.Event.END_OF_DAY); - mStatsChanged = true; - } - } + toDelete.addAll(Arrays.asList(fileNames)); + + final int userCount = users.size(); + for (int i = 0; i < userCount; i++) { + final UserInfo userInfo = users.get(i); + toDelete.remove(Integer.toString(userInfo.id)); } - persistActiveStatsLocked(); - mDatabase.prune(); - loadActiveStatsLocked(); - - final int continueCount = continuePreviousDay.size(); - for (int i = 0; i < continueCount; i++) { - String name = continuePreviousDay.valueAt(i); - for (UsageStats stat : mCurrentStats) { - updateStatsLocked(stat, name, - mCurrentStats[UsageStatsManager.DAILY_BUCKET].mBeginTimeStamp, - UsageStats.Event.CONTINUE_PREVIOUS_DAY); - mStatsChanged = true; + final int deleteCount = toDelete.size(); + for (int i = 0; i < deleteCount; i++) { + deleteRecursively(new File(mUsageStatsDir, toDelete.valueAt(i))); + } + } + + private static void deleteRecursively(File f) { + File[] files = f.listFiles(); + if (files != null) { + for (File subFile : files) { + deleteRecursively(subFile); } } - persistActiveStatsLocked(); - final long totalTime = System.currentTimeMillis() - startTime; - Slog.i(TAG, "Rolling over usage stats complete. Took " + totalTime + " milliseconds"); + if (!f.delete()) { + Slog.e(TAG, "Failed to delete " + f); + } } - private void notifyStatsChangedLocked() { - if (!mStatsChanged) { - mStatsChanged = true; - mHandler.sendEmptyMessageDelayed(MSG_FLUSH_TO_DISK, FLUSH_INTERVAL); + private UserUsageStatsService getUserDataAndInitializeIfNeededLocked(int userId) { + UserUsageStatsService service = mUserState.get(userId); + if (service == null) { + service = new UserUsageStatsService(userId, + new File(mUsageStatsDir, Integer.toString(userId)), this); + service.init(); + mUserState.put(userId, service); } + return service; } /** @@ -189,58 +174,45 @@ public class UsageStatsService extends SystemService { void shutdown() { synchronized (mLock) { mHandler.removeMessages(MSG_REPORT_EVENT); - mHandler.removeMessages(MSG_FLUSH_TO_DISK); - persistActiveStatsLocked(); + flushToDiskLocked(); } } - private static String eventToString(int eventType) { - switch (eventType) { - case UsageStats.Event.NONE: - return "NONE"; - case UsageStats.Event.MOVE_TO_BACKGROUND: - return "MOVE_TO_BACKGROUND"; - case UsageStats.Event.MOVE_TO_FOREGROUND: - return "MOVE_TO_FOREGROUND"; - case UsageStats.Event.END_OF_DAY: - return "END_OF_DAY"; - case UsageStats.Event.CONTINUE_PREVIOUS_DAY: - return "CONTINUE_PREVIOUS_DAY"; - default: - return "UNKNOWN"; + /** + * Called by the Binder stub. + */ + void reportEvent(UsageStats.Event event, int userId) { + synchronized (mLock) { + final UserUsageStatsService service = getUserDataAndInitializeIfNeededLocked(userId); + service.reportEvent(event); } } /** * Called by the Binder stub. */ - void reportEvent(UsageStats.Event event) { + void flushToDisk() { synchronized (mLock) { - if (DEBUG) { - Slog.d(TAG, "Got usage event for " + event.packageName - + "[" + event.timeStamp + "]: " - + eventToString(event.eventType)); - } - - if (event.timeStamp >= mDailyExpiryDate.getTimeInMillis()) { - // Need to rollover - rolloverStatsLocked(); - } - - mCurrentEvents.append(event.timeStamp, event); + flushToDiskLocked(); + } + } - for (UsageStats stats : mCurrentStats) { - updateStatsLocked(stats, event.packageName, event.timeStamp, event.eventType); - } - notifyStatsChangedLocked(); + /** + * Called by the Binder stub. + */ + void removeUser(int userId) { + synchronized (mLock) { + Slog.i(TAG, "Removing user " + userId + " and all data."); + mUserState.remove(userId); + cleanUpRemovedUsersLocked(); } } /** * Called by the Binder stub. */ - UsageStats[] getUsageStats(int bucketType, long beginTime) { - if (bucketType < 0 || bucketType >= mCurrentStats.length) { + UsageStats[] getUsageStats(int userId, int bucketType, long beginTime) { + if (bucketType < 0 || bucketType >= UsageStatsManager.BUCKET_COUNT) { return UsageStats.EMPTY_STATS; } @@ -250,110 +222,26 @@ public class UsageStatsService extends SystemService { } synchronized (mLock) { - if (beginTime >= mCurrentStats[bucketType].mEndTimeStamp) { - if (DEBUG) { - Slog.d(TAG, "Requesting stats after " + beginTime + " but latest is " - + mCurrentStats[bucketType].mEndTimeStamp); - } - // Nothing newer available. - return UsageStats.EMPTY_STATS; - } else if (beginTime >= mCurrentStats[bucketType].mBeginTimeStamp) { - if (DEBUG) { - Slog.d(TAG, "Returning in-memory stats"); - } - // Fast path for retrieving in-memory state. - // TODO(adamlesinski): This copy just to parcel the object is wasteful. - // It would be nice to parcel it here and send that back, but the Binder API - // would need to change. - return new UsageStats[] { new UsageStats(mCurrentStats[bucketType]) }; - } else { - // Flush any changes that were made to disk before we do a disk query. - persistActiveStatsLocked(); - } + UserUsageStatsService service = getUserDataAndInitializeIfNeededLocked(userId); + return service.getUsageStats(bucketType, beginTime); } - - if (DEBUG) { - Slog.d(TAG, "SELECT * FROM " + bucketType + " WHERE beginTime >= " - + beginTime + " LIMIT " + USAGE_STAT_RESULT_LIMIT); - } - - UsageStats[] results = mDatabase.getUsageStats(bucketType, beginTime, - USAGE_STAT_RESULT_LIMIT); - - if (DEBUG) { - Slog.d(TAG, "Results: " + results.length); - } - return results; } /** * Called by the Binder stub. */ - UsageStats.Event[] getEvents(long time) { + UsageStats.Event[] getEvents(int userId, long time) { return UsageStats.Event.EMPTY_EVENTS; } - private void loadActiveStatsLocked() { - final long timeNow = System.currentTimeMillis(); - - Calendar tempCal = mDailyExpiryDate; - for (int i = 0; i < mCurrentStats.length; i++) { - tempCal.setTimeInMillis(timeNow); - UsageStatsUtils.truncateDateTo(i, tempCal); - - if (mCurrentStats[i] != null && - mCurrentStats[i].mBeginTimeStamp == tempCal.getTimeInMillis()) { - // These are the same, no need to load them (in memory stats are always newer - // than persisted stats). - continue; - } - - UsageStats[] stats = mDatabase.getUsageStats(i, timeNow, 1); - if (stats != null && stats.length > 0) { - mCurrentStats[i] = stats[stats.length - 1]; - } else { - mCurrentStats[i] = UsageStats.create(tempCal.getTimeInMillis(), timeNow); - } + private void flushToDiskLocked() { + final int userCount = mUserState.size(); + for (int i = 0; i < userCount; i++) { + UserUsageStatsService service = mUserState.valueAt(i); + service.persistActiveStats(); } - mStatsChanged = false; - mDailyExpiryDate.setTimeInMillis(timeNow); - mDailyExpiryDate.add(Calendar.DAY_OF_YEAR, 1); - UsageStatsUtils.truncateDateTo(UsageStatsManager.DAILY_BUCKET, mDailyExpiryDate); - Slog.i(TAG, "Rollover scheduled for " + sDateFormat.format(mDailyExpiryDate.getTime())); - } - - private void persistActiveStatsLocked() { - if (mStatsChanged) { - Slog.i(TAG, "Flushing usage stats to disk"); - try { - for (int i = 0; i < mCurrentStats.length; i++) { - mDatabase.putUsageStats(i, mCurrentStats[i]); - } - mStatsChanged = false; - mHandler.removeMessages(MSG_FLUSH_TO_DISK); - } catch (IOException e) { - Slog.e(TAG, "Failed to persist active stats", e); - } - } - } - - private void updateStatsLocked(UsageStats stats, String packageName, long timeStamp, - int eventType) { - PackageUsageStats pkgStats = stats.getOrCreatePackageUsageStats(packageName); - - // TODO(adamlesinski): Ensure that we recover from incorrect event sequences - // like double MOVE_TO_BACKGROUND, etc. - if (eventType == UsageStats.Event.MOVE_TO_BACKGROUND || - eventType == UsageStats.Event.END_OF_DAY) { - if (pkgStats.mLastEvent == UsageStats.Event.MOVE_TO_FOREGROUND || - pkgStats.mLastEvent == UsageStats.Event.CONTINUE_PREVIOUS_DAY) { - pkgStats.mTotalTimeSpent += timeStamp - pkgStats.mLastTimeUsed; - } - } - pkgStats.mLastEvent = eventType; - pkgStats.mLastTimeUsed = timeStamp; - stats.mEndTimeStamp = timeStamp; + mHandler.removeMessages(MSG_FLUSH_TO_DISK); } class H extends Handler { @@ -365,13 +253,15 @@ public class UsageStatsService extends SystemService { public void handleMessage(Message msg) { switch (msg.what) { case MSG_REPORT_EVENT: - reportEvent((UsageStats.Event) msg.obj); + reportEvent((UsageStats.Event) msg.obj, msg.arg1); break; case MSG_FLUSH_TO_DISK: - synchronized (mLock) { - persistActiveStatsLocked(); - } + flushToDisk(); + break; + + case MSG_REMOVE_USER: + removeUser(msg.arg1); break; default: @@ -401,9 +291,10 @@ public class UsageStatsService extends SystemService { return UsageStats.EMPTY_STATS; } - long token = Binder.clearCallingIdentity(); + final int userId = UserHandle.getCallingUserId(); + final long token = Binder.clearCallingIdentity(); try { - return getUsageStats(bucketType, time); + return getUsageStats(userId, bucketType, time); } finally { Binder.restoreCallingIdentity(token); } @@ -415,9 +306,10 @@ public class UsageStatsService extends SystemService { return UsageStats.Event.EMPTY_EVENTS; } - long token = Binder.clearCallingIdentity(); + final int userId = UserHandle.getCallingUserId(); + final long token = Binder.clearCallingIdentity(); try { - return getEvents(time); + return getEvents(userId, time); } finally { Binder.restoreCallingIdentity(token); } @@ -432,10 +324,11 @@ public class UsageStatsService extends SystemService { private class LocalService extends UsageStatsManagerInternal { @Override - public void reportEvent(ComponentName component, long timeStamp, int eventType) { + public void reportEvent(ComponentName component, int userId, + long timeStamp, int eventType) { UsageStats.Event event = new UsageStats.Event(component.getPackageName(), timeStamp, eventType); - mHandler.obtainMessage(MSG_REPORT_EVENT, event).sendToTarget(); + mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget(); } @Override diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java new file mode 100644 index 000000000000..d12418883964 --- /dev/null +++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java @@ -0,0 +1,275 @@ +package com.android.server.usage; + +import android.app.usage.PackageUsageStats; +import android.app.usage.UsageStats; +import android.app.usage.UsageStatsManager; +import android.util.ArraySet; +import android.util.Slog; + +import java.io.File; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Calendar; + +/** + * A per-user UsageStatsService. All methods are meant to be called with the main lock held + * in UsageStatsService. + */ +class UserUsageStatsService { + private static final String TAG = "UsageStatsService"; + private static final boolean DEBUG = UsageStatsService.DEBUG; + private static final SimpleDateFormat sDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + + private final UsageStatsDatabase mDatabase; + private final UsageStats[] mCurrentStats = new UsageStats[UsageStatsManager.BUCKET_COUNT]; + private boolean mStatsChanged = false; + private final Calendar mDailyExpiryDate; + private final StatsUpdatedListener mListener; + private final String mLogPrefix; + + interface StatsUpdatedListener { + void onStatsUpdated(); + } + + UserUsageStatsService(int userId, File usageStatsDir, StatsUpdatedListener listener) { + mDailyExpiryDate = Calendar.getInstance(); + mDatabase = new UsageStatsDatabase(usageStatsDir); + mListener = listener; + mLogPrefix = "User[" + Integer.toString(userId) + "] "; + } + + void init() { + mDatabase.init(); + + int nullCount = 0; + for (int i = 0; i < mCurrentStats.length; i++) { + mCurrentStats[i] = mDatabase.getLatestUsageStats(i); + if (mCurrentStats[i] == null) { + nullCount++; + } + } + + if (nullCount > 0) { + if (nullCount != mCurrentStats.length) { + // This is weird, but we shouldn't fail if something like this + // happens. + Slog.w(TAG, mLogPrefix + "Some stats have no latest available"); + } else { + // This must be first boot. + } + + // By calling loadActiveStats, we will + // generate new stats for each bucket. + loadActiveStats(); + } else { + // Set up the expiry date to be one day from the latest daily stat. + // This may actually be today and we will rollover on the first event + // that is reported. + mDailyExpiryDate.setTimeInMillis( + mCurrentStats[UsageStatsManager.DAILY_BUCKET].mBeginTimeStamp); + mDailyExpiryDate.add(Calendar.DAY_OF_YEAR, 1); + UsageStatsUtils.truncateDateTo(UsageStatsManager.DAILY_BUCKET, mDailyExpiryDate); + Slog.i(TAG, mLogPrefix + "Rollover scheduled for " + + sDateFormat.format(mDailyExpiryDate.getTime())); + } + + // Now close off any events that were open at the time this was saved. + for (UsageStats stat : mCurrentStats) { + final int pkgCount = stat.getPackageCount(); + for (int i = 0; i < pkgCount; i++) { + PackageUsageStats pkgStats = stat.getPackage(i); + if (pkgStats.mLastEvent == UsageStats.Event.MOVE_TO_FOREGROUND || + pkgStats.mLastEvent == UsageStats.Event.CONTINUE_PREVIOUS_DAY) { + updateStats(stat, pkgStats.mPackageName, stat.mLastTimeSaved, + UsageStats.Event.END_OF_DAY); + notifyStatsChanged(); + } + } + } + } + + void reportEvent(UsageStats.Event event) { + if (DEBUG) { + Slog.d(TAG, mLogPrefix + "Got usage event for " + event.packageName + + "[" + event.timeStamp + "]: " + + eventToString(event.eventType)); + } + + if (event.timeStamp >= mDailyExpiryDate.getTimeInMillis()) { + // Need to rollover + rolloverStats(); + } + + for (UsageStats stats : mCurrentStats) { + updateStats(stats, event.packageName, event.timeStamp, event.eventType); + } + + notifyStatsChanged(); + } + + UsageStats[] getUsageStats(int bucketType, long beginTime) { + if (beginTime >= mCurrentStats[bucketType].mEndTimeStamp) { + if (DEBUG) { + Slog.d(TAG, mLogPrefix + "Requesting stats after " + beginTime + " but latest is " + + mCurrentStats[bucketType].mEndTimeStamp); + } + // Nothing newer available. + return UsageStats.EMPTY_STATS; + + } else if (beginTime >= mCurrentStats[bucketType].mBeginTimeStamp) { + if (DEBUG) { + Slog.d(TAG, mLogPrefix + "Returning in-memory stats"); + } + // Fast path for retrieving in-memory state. + // TODO(adamlesinski): This copy just to parcel the object is wasteful. + // It would be nice to parcel it here and send that back, but the Binder API + // would need to change. + return new UsageStats[] { new UsageStats(mCurrentStats[bucketType]) }; + + } else { + // Flush any changes that were made to disk before we do a disk query. + persistActiveStats(); + } + + if (DEBUG) { + Slog.d(TAG, mLogPrefix + "SELECT * FROM " + bucketType + " WHERE beginTime >= " + + beginTime + " LIMIT " + UsageStatsService.USAGE_STAT_RESULT_LIMIT); + } + + final UsageStats[] results = mDatabase.getUsageStats(bucketType, beginTime, + UsageStatsService.USAGE_STAT_RESULT_LIMIT); + + if (DEBUG) { + Slog.d(TAG, mLogPrefix + "Results: " + results.length); + } + return results; + } + + void persistActiveStats() { + if (mStatsChanged) { + Slog.i(TAG, mLogPrefix + "Flushing usage stats to disk"); + try { + for (int i = 0; i < mCurrentStats.length; i++) { + mDatabase.putUsageStats(i, mCurrentStats[i]); + } + mStatsChanged = false; + } catch (IOException e) { + Slog.e(TAG, mLogPrefix + "Failed to persist active stats", e); + } + } + } + + private void rolloverStats() { + final long startTime = System.currentTimeMillis(); + Slog.i(TAG, mLogPrefix + "Rolling over usage stats"); + + // Finish any ongoing events with an END_OF_DAY event. Make a note of which components + // need a new CONTINUE_PREVIOUS_DAY entry. + ArraySet<String> continuePreviousDay = new ArraySet<>(); + for (UsageStats stat : mCurrentStats) { + final int pkgCount = stat.getPackageCount(); + for (int i = 0; i < pkgCount; i++) { + PackageUsageStats pkgStats = stat.getPackage(i); + if (pkgStats.mLastEvent == UsageStats.Event.MOVE_TO_FOREGROUND || + pkgStats.mLastEvent == UsageStats.Event.CONTINUE_PREVIOUS_DAY) { + continuePreviousDay.add(pkgStats.mPackageName); + updateStats(stat, pkgStats.mPackageName, + mDailyExpiryDate.getTimeInMillis() - 1, UsageStats.Event.END_OF_DAY); + mStatsChanged = true; + } + } + } + + persistActiveStats(); + mDatabase.prune(); + loadActiveStats(); + + final int continueCount = continuePreviousDay.size(); + for (int i = 0; i < continueCount; i++) { + String name = continuePreviousDay.valueAt(i); + for (UsageStats stat : mCurrentStats) { + updateStats(stat, name, + mCurrentStats[UsageStatsManager.DAILY_BUCKET].mBeginTimeStamp, + UsageStats.Event.CONTINUE_PREVIOUS_DAY); + mStatsChanged = true; + } + } + persistActiveStats(); + + final long totalTime = System.currentTimeMillis() - startTime; + Slog.i(TAG, mLogPrefix + "Rolling over usage stats complete. Took " + totalTime + + " milliseconds"); + } + + private void notifyStatsChanged() { + if (!mStatsChanged) { + mStatsChanged = true; + mListener.onStatsUpdated(); + } + } + + private void loadActiveStats() { + final long timeNow = System.currentTimeMillis(); + + Calendar tempCal = mDailyExpiryDate; + for (int i = 0; i < mCurrentStats.length; i++) { + tempCal.setTimeInMillis(timeNow); + UsageStatsUtils.truncateDateTo(i, tempCal); + + if (mCurrentStats[i] != null && + mCurrentStats[i].mBeginTimeStamp == tempCal.getTimeInMillis()) { + // These are the same, no need to load them (in memory stats are always newer + // than persisted stats). + continue; + } + + UsageStats[] stats = mDatabase.getUsageStats(i, timeNow, 1); + if (stats != null && stats.length > 0) { + mCurrentStats[i] = stats[stats.length - 1]; + } else { + mCurrentStats[i] = UsageStats.create(tempCal.getTimeInMillis(), timeNow); + } + } + mStatsChanged = false; + mDailyExpiryDate.setTimeInMillis(timeNow); + mDailyExpiryDate.add(Calendar.DAY_OF_YEAR, 1); + UsageStatsUtils.truncateDateTo(UsageStatsManager.DAILY_BUCKET, mDailyExpiryDate); + Slog.i(TAG, mLogPrefix + "Rollover scheduled for " + + sDateFormat.format(mDailyExpiryDate.getTime())); + } + + private void updateStats(UsageStats stats, String packageName, long timeStamp, + int eventType) { + PackageUsageStats pkgStats = stats.getOrCreatePackageUsageStats(packageName); + + // TODO(adamlesinski): Ensure that we recover from incorrect event sequences + // like double MOVE_TO_BACKGROUND, etc. + if (eventType == UsageStats.Event.MOVE_TO_BACKGROUND || + eventType == UsageStats.Event.END_OF_DAY) { + if (pkgStats.mLastEvent == UsageStats.Event.MOVE_TO_FOREGROUND || + pkgStats.mLastEvent == UsageStats.Event.CONTINUE_PREVIOUS_DAY) { + pkgStats.mTotalTimeSpent += timeStamp - pkgStats.mLastTimeUsed; + } + } + pkgStats.mLastEvent = eventType; + pkgStats.mLastTimeUsed = timeStamp; + stats.mEndTimeStamp = timeStamp; + } + + private static String eventToString(int eventType) { + switch (eventType) { + case UsageStats.Event.NONE: + return "NONE"; + case UsageStats.Event.MOVE_TO_BACKGROUND: + return "MOVE_TO_BACKGROUND"; + case UsageStats.Event.MOVE_TO_FOREGROUND: + return "MOVE_TO_FOREGROUND"; + case UsageStats.Event.END_OF_DAY: + return "END_OF_DAY"; + case UsageStats.Event.CONTINUE_PREVIOUS_DAY: + return "CONTINUE_PREVIOUS_DAY"; + default: + return "UNKNOWN"; + } + } +} diff --git a/telecomm/java/android/telecomm/VideoCallProvider.java b/telecomm/java/android/telecomm/VideoCallProvider.java index f3fec11ab5d2..de0126d6ad3c 100644 --- a/telecomm/java/android/telecomm/VideoCallProvider.java +++ b/telecomm/java/android/telecomm/VideoCallProvider.java @@ -51,6 +51,7 @@ public abstract class VideoCallProvider { switch (msg.what) { case MSG_SET_VIDEO_CALL_LISTENER: mVideoCallListener = IVideoCallCallback.Stub.asInterface((IBinder) msg.obj); + break; case MSG_SET_CAMERA: onSetCamera((String) msg.obj); break; diff --git a/test-runner/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java index 36c90f286d8c..8ce7888c4f67 100644 --- a/test-runner/src/android/test/mock/MockPackageManager.java +++ b/test-runner/src/android/test/mock/MockPackageManager.java @@ -750,7 +750,7 @@ public class MockPackageManager extends PackageManager { } /** {@hide} */ - public PackageInstaller getInstaller() { + public PackageInstaller getPackageInstaller() { throw new UnsupportedOperationException(); } diff --git a/tests/MusicServiceDemo/src/com/example/android/musicservicedemo/BrowserService.java b/tests/MusicServiceDemo/src/com/example/android/musicservicedemo/BrowserService.java index 0e7fe133d03d..035b3ea4800c 100644 --- a/tests/MusicServiceDemo/src/com/example/android/musicservicedemo/BrowserService.java +++ b/tests/MusicServiceDemo/src/com/example/android/musicservicedemo/BrowserService.java @@ -116,12 +116,12 @@ public class BrowserService extends MediaBrowserService { } @Override - protected BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) { + public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) { return new BrowserRoot(BROWSE_URI, null); } @Override - protected void onLoadChildren(final Uri parentUri, + public void onLoadChildren(final Uri parentUri, final Result<List<MediaBrowserItem>> result) { new Handler().postDelayed(new Runnable() { public void run() { @@ -142,7 +142,7 @@ public class BrowserService extends MediaBrowserService { } @Override - protected void onLoadThumbnail(Uri uri, int width, int height, Result<Bitmap> result) { + public void onLoadThumbnail(Uri uri, int width, int height, Result<Bitmap> result) { result.sendResult(null); } |