Merge "Adding missing break statement in VideoCallProvider." into lmp-dev
diff --git a/Android.mk b/Android.mk
index e3d3433..30cb9c6 100644
--- a/Android.mk
+++ b/Android.mk
@@ -131,7 +131,7 @@
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/api/current.txt b/api/current.txt
index 9a77586..a94d10b 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -8454,32 +8454,31 @@
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 @@
}
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 @@
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 @@
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;
@@ -30130,7 +30134,6 @@
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);
@@ -30139,6 +30142,7 @@
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;
@@ -39750,8 +39754,6 @@
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 faf5622..96019b3 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.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 @@
return;
}
- if ("install-destroy".equals(op)) {
- runInstallDestroy();
+ if ("install-abandon".equals(op) || "install-destroy".equals(op)) {
+ runInstallAbandon();
return;
}
@@ -770,7 +771,7 @@
}
}
- class LocalCommitResultCallback extends CommitResultCallback {
+ class LocalCommitCallback extends CommitCallback {
boolean finished;
boolean success;
String msg;
@@ -790,7 +791,7 @@
}
@Override
- public void onFailure(String msg) {
+ public void onFailure(int failureReason, String msg, Bundle extras) {
setResult(false, msg);
}
}
@@ -996,10 +997,9 @@
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 @@
} 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 @@
}
}
- 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 @@
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 @@
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 @@
}
}
- 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 @@
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 @@
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 1cb0fd4..68d4cf1 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -61,7 +61,10 @@
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 @@
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 @@
}
@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/content/pm/IPackageInstaller.aidl b/core/java/android/content/pm/IPackageInstaller.aidl
index 32460c9..0c65034 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 @@
/** {@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
similarity index 77%
rename from core/java/android/content/pm/IPackageInstallerObserver.aidl
rename to core/java/android/content/pm/IPackageInstallerCallback.aidl
index 85660e4..a31ae54 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 d6775d4..2fd7ddb 100644
--- a/core/java/android/content/pm/IPackageInstallerSession.aidl
+++ b/core/java/android/content/pm/IPackageInstallerSession.aidl
@@ -21,11 +21,12 @@
/** {@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 3336727..a9c574a 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 @@
/** {@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 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 @@
/**
* 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 @@
* 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 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 e039699..3de9863 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 @@
*/
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 @@
/** {@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 @@
/** {@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 @@
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 @@
}
/**
- * 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.
+ * Optionally provide a set of certificates for the app being installed.
+ * <p>
+ * 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 setSignatures(Signature[] signatures) {
+ public void setSignatures(@Nullable 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.
- * <p>
- * This value should only reflect APK sizes.
- */
- public void setDeltaSize(long deltaSize) {
- this.deltaSize = deltaSize;
- }
-
- /**
- * 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 @@
*
* @see Intent#EXTRA_ORIGINATING_URI
*/
- public void setOriginatingUri(Uri originatingUri) {
+ public void setOriginatingUri(@Nullable Uri originatingUri) {
this.originatingUri = originatingUri;
}
@@ -177,7 +175,7 @@
*
* @see Intent#EXTRA_REFERRER
*/
- public void setReferrerUri(Uri referrerUri) {
+ public void setReferrerUri(@Nullable Uri referrerUri) {
this.referrerUri = referrerUri;
}
@@ -187,11 +185,10 @@
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 @@
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 df82d26..a114bb8 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 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 @@
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 @@
}
/**
- * 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 @@
* @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 @@
}
/**
- * 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 @@
}
/**
- * 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 @@
* 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 @@
*
* @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 @@
/**
* 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);
}
- /**
- * Register to watch for session lifecycle events.
- */
- public void registerSessionObserver(SessionObserver observer) {
- try {
- mInstaller.registerObserver(observer.getBinder(), mUserId);
- } catch (RemoteException e) {
- throw e.rethrowAsRuntimeException();
+ /** {@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();
}
}
/**
- * Unregister an existing observer.
+ * Register to watch for session lifecycle events. To succeed, the caller
+ * must be the current home app.
*/
- public void unregisterSessionObserver(SessionObserver observer) {
- try {
- mInstaller.unregisterObserver(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 callback.
+ */
+ 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 @@
* 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 @@
}
/**
- * 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 @@
}
/** {@hide} */
- public void addProgress(int progress) {
+ public void addProgress(float progress) {
try {
mSession.addClientProgress(progress);
} catch (RemoteException e) {
@@ -278,15 +340,33 @@
}
/**
- * 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 @@
* 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 @@
* 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 @@
*/
@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 @@
/**
* 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 @@
/**
* 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 @@
* 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 @@
* 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 @@
* 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;
+ if (returnCode == PackageManager.INSTALL_SUCCEEDED) {
+ target.onSuccess();
+ } else {
+ final int failureReason = PackageManager.installStatusToFailureReason(returnCode);
+ msg = PackageManager.installStatusToString(returnCode) + ": " + msg;
- 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 (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 8b6ae41..62611efa 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.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 @@
* 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 @@
/** {@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 ab7e844..2fa9d85 100644
--- a/core/java/android/hardware/camera2/legacy/CameraDeviceState.java
+++ b/core/java/android/hardware/camera2/legacy/CameraDeviceState.java
@@ -65,7 +65,7 @@
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 @@
* </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 @@
}
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 @@
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 0000000..a9834d0
--- /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 5d44fd2..06521cf 100644
--- a/core/java/android/hardware/camera2/legacy/GLThreadManager.java
+++ b/core/java/android/hardware/camera2/legacy/GLThreadManager.java
@@ -25,6 +25,8 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
/**
- * 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 @@
* 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 @@
* 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 71d3d4b..cbf4a3d 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
@@ -135,10 +135,9 @@
}
@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 @@
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 @@
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 @@
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 @@
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 cc7a90e..066b416 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.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 @@
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 @@
}
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 @@
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 @@
Log.w(TAG, "Surface abandoned, dropping frame. ", e);
}
}
+
mReceivedJpeg.open();
}
};
@@ -198,7 +200,7 @@
private final Camera.ShutterCallback mJpegShutterCallback = new Camera.ShutterCallback() {
@Override
public void onShutter() {
- mLastJpegTimestamp = SystemClock.elapsedRealtimeNanos();
+ mCaptureCollector.jpegCaptured(SystemClock.elapsedRealtimeNanos());
}
};
@@ -206,29 +208,10 @@
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 @@
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 @@
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 @@
mGLThreadManager.start();
}
mGLThreadManager.waitUntilStarted();
- mGLThreadManager.setConfigurationAndWait(mPreviewOutputs);
+ mGLThreadManager.setConfigurationAndWait(mPreviewOutputs, mCaptureCollector);
mGLThreadManager.allowNewFrames();
mPreviewTexture = mGLThreadManager.getCurrentSurfaceTexture();
if (mPreviewTexture != null) {
@@ -553,6 +531,18 @@
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 @@
// 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 @@
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 @@
// 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 @@
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 @@
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 fdf9ba0..0687264 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.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 @@
/**
* 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 @@
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 @@
}
}
}
+ targetCollector.previewProduced();
- endGlTiming();
+ if (doTiming) {
+ endGlTiming();
+ }
}
/**
diff --git a/core/java/android/transition/TransitionSet.java b/core/java/android/transition/TransitionSet.java
index 363f97f..83c60af 100644
--- a/core/java/android/transition/TransitionSet.java
+++ b/core/java/android/transition/TransitionSet.java
@@ -371,6 +371,8 @@
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/Toolbar.java b/core/java/android/widget/Toolbar.java
index 71102e8..0b15eb6 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 @@
}
/**
- * 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 @@
* 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,16 @@
* 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);
+ }
}
/**
@@ -710,11 +702,28 @@
* 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 resId Resource ID of a drawable to set
*/
- public void setNavigationIcon(Drawable icon) {
+ public void setNavigationIcon(int resId) {
+ setNavigationIcon(getContext().getDrawable(resId));
+ }
+
+ /**
+ * 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 icon Drawable to set, may be null to clear the icon
+ */
+ public void setNavigationIcon(@Nullable Drawable icon) {
if (icon != null) {
ensureNavButtonView();
if (mNavButtonView.getParent() == null) {
@@ -733,40 +742,12 @@
*
* @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 1bcb684..298dd44 100644
--- a/core/java/com/android/internal/app/ToolbarActionBar.java
+++ b/core/java/com/android/internal/app/ToolbarActionBar.java
@@ -154,7 +154,7 @@
@Override
public void setHomeActionContentDescription(CharSequence description) {
- mToolbar.setNavigationDescription(description);
+ mToolbar.setNavigationContentDescription(description);
}
@Override
@@ -164,7 +164,7 @@
@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 fdd24a6..52485dd 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java
@@ -54,8 +54,8 @@
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 @@
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 c0d1e88..414b7bc 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 @@
}
/**
- * 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 @@
}
/**
+ * 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 802f2ab..2ad8330 100644
--- a/core/jni/android/graphics/MinikinUtils.cpp
+++ b/core/jni/android/graphics/MinikinUtils.cpp
@@ -40,8 +40,8 @@
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 @@
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 0562c3b1..647cbd8 100644
--- a/core/jni/android/graphics/MinikinUtils.h
+++ b/core/jni/android/graphics/MinikinUtils.h
@@ -45,8 +45,8 @@
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 e2b3684..a1f09bd 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -535,8 +535,7 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 d58c692..0000000
--- 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 0007912..a9b01d0 100644
--- a/core/jni/android_graphics_Canvas.cpp
+++ b/core/jni/android_graphics_Canvas.cpp
@@ -486,8 +486,7 @@
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 @@
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 d2f5b5d..697cdc6 100644
--- a/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp
+++ b/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp
@@ -587,7 +587,7 @@
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 @@
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 @@
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 @@
{ "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 0a259aa..3cd031e 100644
--- a/core/jni/android_view_GLES20Canvas.cpp
+++ b/core/jni/android_view_GLES20Canvas.cpp
@@ -620,8 +620,7 @@
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 @@
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 @@
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 056d470..2043214 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 351acf0..c7b5580 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/inputmethodtests/src/android/os/InputMethodSubtypeSwitchingControllerTest.java b/core/tests/inputmethodtests/src/android/os/InputMethodSubtypeSwitchingControllerTest.java
index ca68e93..3a598f2 100644
--- a/core/tests/inputmethodtests/src/android/os/InputMethodSubtypeSwitchingControllerTest.java
+++ b/core/tests/inputmethodtests/src/android/os/InputMethodSubtypeSwitchingControllerTest.java
@@ -295,4 +295,33 @@
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/packages/SystemUI/res/drawable/notification_guts_bg.xml b/packages/SystemUI/res/drawable/notification_guts_bg.xml
new file mode 100644
index 0000000..07932d1
--- /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 0000000..0e78d66
--- /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 1f68cd8..9af2879 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_row.xml b/packages/SystemUI/res/layout/status_bar_notification_row.xml
index 7663d54..ef4e27c 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/menu/notification_popup_menu.xml b/packages/SystemUI/res/menu/notification_popup_menu.xml
deleted file mode 100644
index 8923fb6..0000000
--- 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 e9fe09e..a718f4f 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/strings.xml b/packages/SystemUI/res/values/strings.xml
index 5d4b536..ddbddd1 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>
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index 6e1ba3c..6c30c89 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -76,10 +76,12 @@
private float mDensityScale;
private boolean mLongPressSent;
- private View.OnLongClickListener mLongPressListener;
+ private LongPressListener mLongPressListener;
private Runnable mWatchLongPress;
private long mLongPressTimeout;
+ final private int[] mTmpPos = new int[2];
+
public SwipeHelper(int swipeDirection, Callback callback, Context context) {
mCallback = callback;
mHandler = new Handler();
@@ -93,7 +95,7 @@
android.R.interpolator.fast_out_linear_in);
}
- public void setLongPressListener(View.OnLongClickListener listener) {
+ public void setLongPressListener(LongPressListener listener) {
mLongPressListener = listener;
}
@@ -215,7 +217,7 @@
}
}
- public boolean onInterceptTouchEvent(MotionEvent ev) {
+ public boolean onInterceptTouchEvent(final MotionEvent ev) {
final int action = ev.getAction();
switch (action) {
@@ -237,8 +239,12 @@
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);
}
}
};
@@ -267,14 +273,16 @@
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;
}
/**
@@ -460,4 +468,15 @@
*/
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/recents/views/TaskBarView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java
index fd636ed..dc8f0db 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 @@
} 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 @@
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 32b9d8a..f7f96da 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.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 @@
}
}
}
-}
\ 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 a2595bf..48fc4ec 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.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.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.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 @@
// Search panel
protected SearchPanelView mSearchPanelView;
- protected PopupMenu mNotificationBlamePopup;
-
protected int mCurrentUserId = 0;
final protected SparseArray<UserInfo> mCurrentProfiles = new SparseArray<UserInfo>();
@@ -184,6 +186,11 @@
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.
*/
@@ -416,6 +423,11 @@
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);
@@ -593,29 +605,61 @@
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;
}
@@ -623,9 +667,24 @@
}
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();
}
}
@@ -910,6 +969,27 @@
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;
@@ -957,8 +1037,15 @@
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);
@@ -1202,6 +1289,9 @@
protected void visibilityChanged(boolean visible) {
if (mPanelSlightlyVisible != visible) {
mPanelSlightlyVisible = visible;
+ if (!visible) {
+ dismissPopups();
+ }
try {
if (visible) {
mBarService.onPanelRevealed();
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 f41e78d..7c6e47c 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/StatusBarHeaderView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
index bac1d5b..65359ee 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 @@
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 @@
? 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 @@
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 void setKeyguarUserSwitcher(KeyguardUserSwitcher keyguardUserSwitcher) {
- mHasKeyguardUserSwitcher = true;
+ mKeyguardUserSwitcher = keyguardUserSwitcher;
mMultiUserSwitch.setKeyguardUserSwitcher(keyguardUserSwitcher);
}
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 b0bab48..2be566c 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 @@
}
}
- 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 079cb88..8b4e79f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -164,7 +164,7 @@
* 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
@@ -243,7 +243,7 @@
float densityScale = getResources().getDisplayMetrics().density;
float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop();
mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, getContext());
- mSwipeHelper.setLongPressListener(mLongClickListener);
+ mSwipeHelper.setLongPressListener(mLongPressListener);
mSidePaddings = context.getResources()
.getDimensionPixelSize(R.dimen.notification_side_padding);
@@ -471,9 +471,9 @@
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) {
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index db915e2..6036bcf 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.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.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 @@
@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 @@
}
@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 @@
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 @@
}
@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 @@
}
@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 @@
}
@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 @@
}
}
- private void notifySessionCreated(InstallSessionInfo info) {
- final int userId = getSessionUserId(info.sessionId);
- final int n = mObservers.beginBroadcast();
- for (int i = 0; i < n; i++) {
- final IPackageInstallerObserver observer = mObservers.getBroadcastItem(i);
- final UserHandle user = (UserHandle) mObservers.getBroadcastCookie(i);
- if (userId == user.getIdentifier()) {
- try {
- observer.onSessionCreated(info);
- } catch (RemoteException ignored) {
- }
- }
+ /**
+ * 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");
}
- mObservers.finishBroadcast();
}
- private void notifySessionProgress(int sessionId, int progress) {
- final int userId = getSessionUserId(sessionId);
- final int n = mObservers.beginBroadcast();
+ private void notifySessionCreated(InstallSessionInfo info) {
+ final int userId = getSessionUserId(info.sessionId);
+ 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.onSessionProgress(sessionId, progress);
+ callback.onSessionCreated(info.sessionId);
} catch (RemoteException ignored) {
}
}
}
- mObservers.finishBroadcast();
+ mCallbacks.finishBroadcast();
+ }
+
+ private void notifySessionProgressChanged(int sessionId, float progress) {
+ final int userId = getSessionUserId(sessionId);
+ final int n = mCallbacks.beginBroadcast();
+ for (int i = 0; i < n; i++) {
+ final IPackageInstallerCallback callback = mCallbacks.getBroadcastItem(i);
+ final UserHandle user = (UserHandle) mCallbacks.getBroadcastCookie(i);
+ if (userId == user.getIdentifier()) {
+ try {
+ callback.onSessionProgressChanged(sessionId, progress);
+ } catch (RemoteException ignored) {
+ }
+ }
+ }
+ 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 @@
}
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 0e6a3f0..06e1d53 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 @@
// 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 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 @@
}
try {
- installLocked();
+ commitLocked();
} catch (PackageManagerException e) {
Slog.e(TAG, "Install failed: " + e);
destroyInternal();
@@ -114,8 +116,8 @@
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 @@
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 @@
}
@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 @@
}
// 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 @@
}
@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 7ddde62..9878d1c 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 @@
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/test-runner/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java
index 36c90f2..8ce7888 100644
--- a/test-runner/src/android/test/mock/MockPackageManager.java
+++ b/test-runner/src/android/test/mock/MockPackageManager.java
@@ -750,7 +750,7 @@
}
/** {@hide} */
- public PackageInstaller getInstaller() {
+ public PackageInstaller getPackageInstaller() {
throw new UnsupportedOperationException();
}