Merge "Correctly set the window callback for ToolbarActionBar"
diff --git a/Android.mk b/Android.mk
index b12a987..589279d 100644
--- a/Android.mk
+++ b/Android.mk
@@ -130,6 +130,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/IPackageInstallerSession.aidl \
core/java/android/content/pm/IPackageManager.aidl \
core/java/android/content/pm/IPackageMoveObserver.aidl \
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index 92e9290..6cca03b 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -27,10 +27,11 @@
import android.content.pm.IPackageDeleteObserver;
import android.content.pm.IPackageInstaller;
import android.content.pm.IPackageManager;
+import android.content.pm.InstallSessionParams;
import android.content.pm.InstrumentationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
-import android.content.pm.PackageInstallerParams;
+import android.content.pm.PackageInstaller.InstallResultCallback;
import android.content.pm.PackageItemInfo;
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
@@ -768,6 +769,31 @@
}
}
+ class LocalInstallResultCallback extends InstallResultCallback {
+ boolean finished;
+ boolean success;
+ String msg;
+
+ private void setResult(boolean success, String msg) {
+ synchronized (this) {
+ this.finished = true;
+ this.success = success;
+ this.msg = msg;
+ notifyAll();
+ }
+ }
+
+ @Override
+ public void onSuccess() {
+ setResult(true, null);
+ }
+
+ @Override
+ public void onFailure(String msg) {
+ setResult(false, msg);
+ }
+ }
+
/**
* Converts a failure code into a string by using reflection to find a matching constant
* in PackageManager.
@@ -989,7 +1015,7 @@
private void runInstallCreate() throws RemoteException {
String installerPackageName = null;
- final PackageInstallerParams params = new PackageInstallerParams();
+ final InstallSessionParams params = new InstallSessionParams();
params.installFlags = PackageManager.INSTALL_ALL_USERS;
params.fullInstall = true;
@@ -1084,21 +1110,21 @@
try {
session = new PackageInstaller.Session(mInstaller.openSession(sessionId));
- final LocalPackageInstallObserver observer = new LocalPackageInstallObserver();
- session.install(observer);
+ final LocalInstallResultCallback callback = new LocalInstallResultCallback();
+ session.install(callback);
- synchronized (observer) {
- while (!observer.finished) {
+ synchronized (callback) {
+ while (!callback.finished) {
try {
- observer.wait();
+ callback.wait();
} catch (InterruptedException e) {
}
}
- if (observer.result != PackageManager.INSTALL_SUCCEEDED) {
- throw new IllegalStateException(
- "Failure [" + installFailureToString(observer) + "]");
+ if (!callback.success) {
+ throw new IllegalStateException("Failure [" + callback.msg + "]");
}
}
+
System.out.println("Success");
} finally {
IoUtils.closeQuietly(session);
diff --git a/core/java/android/app/PackageInstallObserver.java b/core/java/android/app/PackageInstallObserver.java
index 941efbd..7117111 100644
--- a/core/java/android/app/PackageInstallObserver.java
+++ b/core/java/android/app/PackageInstallObserver.java
@@ -23,13 +23,14 @@
public class PackageInstallObserver {
private final IPackageInstallObserver2.Stub mBinder = new IPackageInstallObserver2.Stub() {
@Override
- public void packageInstalled(String basePackageName, Bundle extras, int returnCode) {
+ public void packageInstalled(String basePackageName, Bundle extras, int returnCode,
+ String msg) {
PackageInstallObserver.this.packageInstalled(basePackageName, extras, returnCode);
}
};
/** {@hide} */
- public IPackageInstallObserver2.Stub getBinder() {
+ public IPackageInstallObserver2 getBinder() {
return mBinder;
}
@@ -50,4 +51,9 @@
*/
public void packageInstalled(String basePackageName, Bundle extras, int returnCode) {
}
+
+ public void packageInstalled(String basePackageName, Bundle extras, int returnCode,
+ String msg) {
+ packageInstalled(basePackageName, extras, returnCode);
+ }
}
diff --git a/core/java/android/app/PackageUninstallObserver.java b/core/java/android/app/PackageUninstallObserver.java
index 0a960a7..83fc380 100644
--- a/core/java/android/app/PackageUninstallObserver.java
+++ b/core/java/android/app/PackageUninstallObserver.java
@@ -28,7 +28,7 @@
};
/** {@hide} */
- public IPackageDeleteObserver.Stub getBinder() {
+ public IPackageDeleteObserver getBinder() {
return mBinder;
}
diff --git a/core/java/android/content/pm/IPackageInstallObserver2.aidl b/core/java/android/content/pm/IPackageInstallObserver2.aidl
index 7205ce7..824d730 100644
--- a/core/java/android/content/pm/IPackageInstallObserver2.aidl
+++ b/core/java/android/content/pm/IPackageInstallObserver2.aidl
@@ -40,5 +40,5 @@
* </tr>
* </table>
*/
- void packageInstalled(String basePackageName, in Bundle extras, int returnCode);
+ void packageInstalled(String basePackageName, in Bundle extras, int returnCode, String msg);
}
diff --git a/core/java/android/content/pm/IPackageInstaller.aidl b/core/java/android/content/pm/IPackageInstaller.aidl
index 4d6ee64..32460c9 100644
--- a/core/java/android/content/pm/IPackageInstaller.aidl
+++ b/core/java/android/content/pm/IPackageInstaller.aidl
@@ -17,17 +17,22 @@
package android.content.pm;
import android.content.pm.IPackageDeleteObserver;
+import android.content.pm.IPackageInstallerObserver;
import android.content.pm.IPackageInstallerSession;
-import android.content.pm.PackageInstallerParams;
+import android.content.pm.InstallSessionInfo;
+import android.content.pm.InstallSessionParams;
import android.os.ParcelFileDescriptor;
/** {@hide} */
interface IPackageInstaller {
- int createSession(String installerPackageName, in PackageInstallerParams params, int userId);
+ int createSession(String installerPackageName, in InstallSessionParams params, int userId);
IPackageInstallerSession openSession(int sessionId);
- int[] getSessions(String installerPackageName, int userId);
+ List<InstallSessionInfo> getSessions(int userId);
- void uninstall(String basePackageName, int flags, in IPackageDeleteObserver observer, int userId);
- void uninstallSplit(String basePackageName, String splitName, int flags, in IPackageDeleteObserver observer, int userId);
+ void registerObserver(IPackageInstallerObserver observer, int userId);
+ void unregisterObserver(IPackageInstallerObserver observer, int userId);
+
+ 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/IPackageInstallerObserver.aidl
new file mode 100644
index 0000000..85660e4
--- /dev/null
+++ b/core/java/android/content/pm/IPackageInstallerObserver.aidl
@@ -0,0 +1,26 @@
+/*
+ * 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.content.pm;
+
+import android.content.pm.InstallSessionInfo;
+
+/** {@hide} */
+oneway interface IPackageInstallerObserver {
+ void onSessionCreated(in InstallSessionInfo info);
+ void onSessionProgress(int sessionId, int progress);
+ void onSessionFinished(int sessionId, boolean success);
+}
diff --git a/core/java/android/content/pm/InstallSessionInfo.aidl b/core/java/android/content/pm/InstallSessionInfo.aidl
new file mode 100644
index 0000000..3d21bbd
--- /dev/null
+++ b/core/java/android/content/pm/InstallSessionInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.content.pm;
+
+parcelable InstallSessionInfo;
diff --git a/core/java/android/content/pm/InstallSessionInfo.java b/core/java/android/content/pm/InstallSessionInfo.java
new file mode 100644
index 0000000..45606d1
--- /dev/null
+++ b/core/java/android/content/pm/InstallSessionInfo.java
@@ -0,0 +1,79 @@
+/*
+ * 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.content.pm;
+
+import android.graphics.Bitmap;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** {@hide} */
+public class InstallSessionInfo implements Parcelable {
+ public int sessionId;
+ public String installerPackageName;
+ public int progress;
+
+ public boolean fullInstall;
+ public String packageName;
+ public Bitmap icon;
+ public CharSequence title;
+
+ /** {@hide} */
+ public InstallSessionInfo() {
+ }
+
+ /** {@hide} */
+ public InstallSessionInfo(Parcel source) {
+ sessionId = source.readInt();
+ installerPackageName = source.readString();
+ progress = source.readInt();
+
+ fullInstall = source.readInt() != 0;
+ packageName = source.readString();
+ icon = source.readParcelable(null);
+ title = source.readString();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(sessionId);
+ dest.writeString(installerPackageName);
+ dest.writeInt(progress);
+
+ dest.writeInt(fullInstall ? 1 : 0);
+ dest.writeString(packageName);
+ dest.writeParcelable(icon, flags);
+ dest.writeString(title != null ? title.toString() : null);
+ }
+
+ public static final Parcelable.Creator<InstallSessionInfo>
+ CREATOR = new Parcelable.Creator<InstallSessionInfo>() {
+ @Override
+ public InstallSessionInfo createFromParcel(Parcel p) {
+ return new InstallSessionInfo(p);
+ }
+
+ @Override
+ public InstallSessionInfo[] newArray(int size) {
+ return new InstallSessionInfo[size];
+ }
+ };
+}
diff --git a/core/java/android/content/pm/InstallSessionParams.aidl b/core/java/android/content/pm/InstallSessionParams.aidl
new file mode 100644
index 0000000..81b7574
--- /dev/null
+++ b/core/java/android/content/pm/InstallSessionParams.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.content.pm;
+
+parcelable InstallSessionParams;
diff --git a/core/java/android/content/pm/PackageInstallerParams.java b/core/java/android/content/pm/InstallSessionParams.java
similarity index 72%
rename from core/java/android/content/pm/PackageInstallerParams.java
rename to core/java/android/content/pm/InstallSessionParams.java
index 527edee..f683523 100644
--- a/core/java/android/content/pm/PackageInstallerParams.java
+++ b/core/java/android/content/pm/InstallSessionParams.java
@@ -21,12 +21,8 @@
import android.os.Parcel;
import android.os.Parcelable;
-/**
- * Parameters that define an installation session.
- *
- * {@hide}
- */
-public class PackageInstallerParams implements Parcelable {
+/** {@hide} */
+public class InstallSessionParams implements Parcelable {
// TODO: extend to support remaining VerificationParams
@@ -41,11 +37,11 @@
/** {@hide} */
public long deltaSize = -1;
/** {@hide} */
- public String basePackageName;
+ public String packageName;
/** {@hide} */
public Bitmap icon;
/** {@hide} */
- public String title;
+ public CharSequence title;
/** {@hide} */
public Uri originatingUri;
/** {@hide} */
@@ -53,20 +49,18 @@
/** {@hide} */
public String abiOverride;
- public PackageInstallerParams() {
+ public InstallSessionParams() {
}
/** {@hide} */
- public PackageInstallerParams(Parcel source) {
+ public InstallSessionParams(Parcel source) {
fullInstall = source.readInt() != 0;
installFlags = source.readInt();
installLocation = source.readInt();
signatures = (Signature[]) source.readParcelableArray(null);
deltaSize = source.readLong();
- basePackageName = source.readString();
- if (source.readInt() != 0) {
- icon = Bitmap.CREATOR.createFromParcel(source);
- }
+ packageName = source.readString();
+ icon = source.readParcelable(null);
title = source.readString();
originatingUri = source.readParcelable(null);
referrerUri = source.readParcelable(null);
@@ -93,8 +87,8 @@
this.deltaSize = deltaSize;
}
- public void setBasePackageName(String basePackageName) {
- this.basePackageName = basePackageName;
+ public void setPackageName(String packageName) {
+ this.packageName = packageName;
}
public void setIcon(Bitmap icon) {
@@ -102,7 +96,7 @@
}
public void setTitle(CharSequence title) {
- this.title = (title != null) ? title.toString() : null;
+ this.title = title;
}
public void setOriginatingUri(Uri originatingUri) {
@@ -125,29 +119,24 @@
dest.writeInt(installLocation);
dest.writeParcelableArray(signatures, flags);
dest.writeLong(deltaSize);
- dest.writeString(basePackageName);
- if (icon != null) {
- dest.writeInt(1);
- icon.writeToParcel(dest, flags);
- } else {
- dest.writeInt(0);
- }
- dest.writeString(title);
+ dest.writeString(packageName);
+ dest.writeParcelable(icon, flags);
+ dest.writeString(title != null ? title.toString() : null);
dest.writeParcelable(originatingUri, flags);
dest.writeParcelable(referrerUri, flags);
dest.writeString(abiOverride);
}
- public static final Parcelable.Creator<PackageInstallerParams>
- CREATOR = new Parcelable.Creator<PackageInstallerParams>() {
+ public static final Parcelable.Creator<InstallSessionParams>
+ CREATOR = new Parcelable.Creator<InstallSessionParams>() {
@Override
- public PackageInstallerParams createFromParcel(Parcel p) {
- return new PackageInstallerParams(p);
+ public InstallSessionParams createFromParcel(Parcel p) {
+ return new InstallSessionParams(p);
}
@Override
- public PackageInstallerParams[] newArray(int size) {
- return new PackageInstallerParams[size];
+ public InstallSessionParams[] newArray(int size) {
+ return new InstallSessionParams[size];
}
};
}
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index a60a2fe..9d756f7 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -19,12 +19,14 @@
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.ParcelFileDescriptor;
import android.os.RemoteException;
import java.io.Closeable;
import java.io.OutputStream;
+import java.util.List;
/** {@hide} */
public class PackageInstaller {
@@ -42,9 +44,9 @@
mUserId = userId;
}
- public boolean isPackageAvailable(String basePackageName) {
+ public boolean isPackageAvailable(String packageName) {
try {
- final ApplicationInfo info = mPm.getApplicationInfo(basePackageName,
+ final ApplicationInfo info = mPm.getApplicationInfo(packageName,
PackageManager.GET_UNINSTALLED_PACKAGES);
return ((info.flags & ApplicationInfo.FLAG_INSTALLED) != 0);
} catch (NameNotFoundException e) {
@@ -52,17 +54,17 @@
}
}
- public void installAvailablePackage(String basePackageName, PackageInstallObserver observer) {
+ public void installAvailablePackage(String packageName, PackageInstallObserver observer) {
int returnCode;
try {
- returnCode = mPm.installExistingPackage(basePackageName);
+ returnCode = mPm.installExistingPackage(packageName);
} catch (NameNotFoundException e) {
returnCode = PackageManager.INSTALL_FAILED_PACKAGE_CHANGED;
}
- observer.packageInstalled(basePackageName, null, returnCode);
+ observer.packageInstalled(packageName, null, returnCode);
}
- public int createSession(PackageInstallerParams params) {
+ public int createSession(InstallSessionParams params) {
try {
return mInstaller.createSession(mInstallerPackageName, params, mUserId);
} catch (RemoteException e) {
@@ -78,26 +80,74 @@
}
}
- public int[] getSessions() {
+ public List<InstallSessionInfo> getSessions() {
+ // TODO: filter based on caller
+ // TODO: let launcher app see all active sessions
try {
- return mInstaller.getSessions(mInstallerPackageName, mUserId);
+ return mInstaller.getSessions(mUserId);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
}
- public void uninstall(String basePackageName, PackageUninstallObserver observer) {
+ public void uninstall(String packageName, UninstallResultCallback callback) {
try {
- mInstaller.uninstall(basePackageName, 0, observer.getBinder(), mUserId);
+ mInstaller.uninstall(packageName, 0,
+ new UninstallResultCallbackDelegate(callback).getBinder(), mUserId);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
}
- public void uninstall(String basePackageName, String splitName,
- PackageUninstallObserver observer) {
+ public void uninstall(String packageName, String splitName, UninstallResultCallback callback) {
try {
- mInstaller.uninstallSplit(basePackageName, splitName, 0, observer.getBinder(), mUserId);
+ mInstaller.uninstallSplit(packageName, splitName, 0,
+ new UninstallResultCallbackDelegate(callback).getBinder(), mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ 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.onFinished(sessionId, success);
+ }
+ };
+
+ /** {@hide} */
+ public IPackageInstallerObserver getBinder() {
+ return mBinder;
+ }
+
+ public abstract void onCreated(InstallSessionInfo info);
+ public abstract void onProgress(int sessionId, int progress);
+ public abstract void onFinished(int sessionId, boolean success);
+ }
+
+ public void registerObserver(SessionObserver observer) {
+ // TODO: consider restricting to current launcher app
+ try {
+ mInstaller.registerObserver(observer.getBinder(), mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ public void unregisterObserver(SessionObserver observer) {
+ try {
+ mInstaller.unregisterObserver(observer.getBinder(), mUserId);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
@@ -147,9 +197,9 @@
}
}
- public void install(PackageInstallObserver observer) {
+ public void install(InstallResultCallback callback) {
try {
- mSession.install(observer.getBinder());
+ mSession.install(new InstallResultCallbackDelegate(callback).getBinder());
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
@@ -168,4 +218,143 @@
}
}
}
+
+ public static abstract class UninstallResultCallback {
+ public abstract void onSuccess();
+ public abstract void onFailure(String msg);
+ }
+
+ private static class UninstallResultCallbackDelegate extends PackageUninstallObserver {
+ private final UninstallResultCallback target;
+
+ public UninstallResultCallbackDelegate(UninstallResultCallback 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;
+ }
+ }
+ }
+
+ public static abstract class InstallResultCallback {
+ /**
+ * The session installed successfully.
+ */
+ public abstract void onSuccess();
+
+ /**
+ * General unclassified failure. You may be interested in overriding
+ * more granular classifications.
+ */
+ public abstract void onFailure(String msg);
+
+ /**
+ * One or more of the APKs included in the session was invalid. For
+ * example, they might be malformed, corrupt, incorrectly signed,
+ * mismatched, etc. The installer may want to try downloading and
+ * installing again.
+ */
+ public void onFailureInvalid(String msg) {
+ onFailure(msg);
+ }
+
+ /**
+ * 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.
+ */
+ public void onFailureConflict(String msg, String packageName) {
+ onFailure(msg);
+ }
+
+ /**
+ * This install session failed due to storage issues. For example,
+ * the device may be running low on space, or the required external
+ * 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);
+ }
+
+ /**
+ * This install session is fundamentally incompatible with this
+ * device. For example, the package may require a hardware feature
+ * that doesn't exist, it may be missing native code for the device
+ * ABI, or it requires a newer SDK version, etc. This install would
+ * never succeed.
+ */
+ public void onFailureIncompatible(String msg) {
+ onFailure(msg);
+ }
+ }
+
+ private static class InstallResultCallbackDelegate extends PackageInstallObserver {
+ private final InstallResultCallback target;
+
+ public InstallResultCallbackDelegate(InstallResultCallback target) {
+ this.target = target;
+ }
+
+ @Override
+ public void packageInstalled(String basePackageName, Bundle extras, int returnCode,
+ String msg) {
+ final String otherPackage = null;
+
+ switch (returnCode) {
+ case PackageManager.INSTALL_SUCCEEDED: target.onSuccess(); break;
+ case PackageManager.INSTALL_FAILED_ALREADY_EXISTS: target.onFailureConflict("INSTALL_FAILED_ALREADY_EXISTS: " + msg, otherPackage); break;
+ case PackageManager.INSTALL_FAILED_INVALID_APK: target.onFailureInvalid("INSTALL_FAILED_INVALID_APK: " + msg); break;
+ case PackageManager.INSTALL_FAILED_INVALID_URI: target.onFailureInvalid("INSTALL_FAILED_INVALID_URI: " + msg); break;
+ case PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE: target.onFailureStorage("INSTALL_FAILED_INSUFFICIENT_STORAGE: " + msg); break;
+ case PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE: target.onFailureConflict("INSTALL_FAILED_DUPLICATE_PACKAGE: " + msg, otherPackage); break;
+ case PackageManager.INSTALL_FAILED_NO_SHARED_USER: target.onFailureConflict("INSTALL_FAILED_NO_SHARED_USER: " + msg, otherPackage); break;
+ case PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE: target.onFailureConflict("INSTALL_FAILED_UPDATE_INCOMPATIBLE: " + msg, otherPackage); break;
+ case PackageManager.INSTALL_FAILED_SHARED_USER_INCOMPATIBLE: target.onFailureConflict("INSTALL_FAILED_SHARED_USER_INCOMPATIBLE: " + msg, otherPackage); break;
+ case PackageManager.INSTALL_FAILED_MISSING_SHARED_LIBRARY: target.onFailureIncompatible("INSTALL_FAILED_MISSING_SHARED_LIBRARY: " + msg); break;
+ case PackageManager.INSTALL_FAILED_REPLACE_COULDNT_DELETE: target.onFailureConflict("INSTALL_FAILED_REPLACE_COULDNT_DELETE: " + msg, otherPackage); break;
+ case PackageManager.INSTALL_FAILED_DEXOPT: target.onFailureInvalid("INSTALL_FAILED_DEXOPT: " + msg); break;
+ case PackageManager.INSTALL_FAILED_OLDER_SDK: target.onFailureIncompatible("INSTALL_FAILED_OLDER_SDK: " + msg); break;
+ case PackageManager.INSTALL_FAILED_CONFLICTING_PROVIDER: target.onFailureConflict("INSTALL_FAILED_CONFLICTING_PROVIDER: " + msg, otherPackage); break;
+ case PackageManager.INSTALL_FAILED_NEWER_SDK: target.onFailureIncompatible("INSTALL_FAILED_NEWER_SDK: " + msg); break;
+ case PackageManager.INSTALL_FAILED_TEST_ONLY: target.onFailureInvalid("INSTALL_FAILED_TEST_ONLY: " + msg); break;
+ case PackageManager.INSTALL_FAILED_CPU_ABI_INCOMPATIBLE: target.onFailureIncompatible("INSTALL_FAILED_CPU_ABI_INCOMPATIBLE: " + msg); break;
+ case PackageManager.INSTALL_FAILED_MISSING_FEATURE: target.onFailureIncompatible("INSTALL_FAILED_MISSING_FEATURE: " + msg); break;
+ case PackageManager.INSTALL_FAILED_CONTAINER_ERROR: target.onFailureStorage("INSTALL_FAILED_CONTAINER_ERROR: " + msg); break;
+ case PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION: target.onFailureStorage("INSTALL_FAILED_INVALID_INSTALL_LOCATION: " + msg); break;
+ case PackageManager.INSTALL_FAILED_MEDIA_UNAVAILABLE: target.onFailureStorage("INSTALL_FAILED_MEDIA_UNAVAILABLE: " + msg); break;
+ case PackageManager.INSTALL_FAILED_VERIFICATION_TIMEOUT: target.onFailure("INSTALL_FAILED_VERIFICATION_TIMEOUT: " + msg); break;
+ case PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE: target.onFailure("INSTALL_FAILED_VERIFICATION_FAILURE: " + msg); break;
+ case PackageManager.INSTALL_FAILED_PACKAGE_CHANGED: target.onFailureInvalid("INSTALL_FAILED_PACKAGE_CHANGED: " + msg); break;
+ case PackageManager.INSTALL_FAILED_UID_CHANGED: target.onFailureInvalid("INSTALL_FAILED_UID_CHANGED: " + msg); break;
+ case PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE: target.onFailureInvalid("INSTALL_FAILED_VERSION_DOWNGRADE: " + msg); break;
+ case PackageManager.INSTALL_PARSE_FAILED_NOT_APK: target.onFailureInvalid("INSTALL_PARSE_FAILED_NOT_APK: " + msg); break;
+ case PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST: target.onFailureInvalid("INSTALL_PARSE_FAILED_BAD_MANIFEST: " + msg); break;
+ case PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION: target.onFailureInvalid("INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION: " + msg); break;
+ case PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES: target.onFailureInvalid("INSTALL_PARSE_FAILED_NO_CERTIFICATES: " + msg); break;
+ case PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES: target.onFailureInvalid("INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES: " + msg); break;
+ case PackageManager.INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING: target.onFailureInvalid("INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING: " + msg); break;
+ case PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME: target.onFailureInvalid("INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME: " + msg); break;
+ case PackageManager.INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID: target.onFailureInvalid("INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID: " + msg); break;
+ case PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED: target.onFailureInvalid("INSTALL_PARSE_FAILED_MANIFEST_MALFORMED: " + msg); break;
+ case PackageManager.INSTALL_PARSE_FAILED_MANIFEST_EMPTY: target.onFailureInvalid("INSTALL_PARSE_FAILED_MANIFEST_EMPTY: " + msg); break;
+ case PackageManager.INSTALL_FAILED_INTERNAL_ERROR: target.onFailure("INSTALL_FAILED_INTERNAL_ERROR: " + msg); break;
+ case PackageManager.INSTALL_FAILED_USER_RESTRICTED: target.onFailureIncompatible("INSTALL_FAILED_USER_RESTRICTED: " + msg); break;
+ case PackageManager.INSTALL_FAILED_DUPLICATE_PERMISSION: target.onFailureConflict("INSTALL_FAILED_DUPLICATE_PERMISSION: " + msg, otherPackage); break;
+ case PackageManager.INSTALL_FAILED_NO_MATCHING_ABIS: target.onFailureInvalid("INSTALL_FAILED_NO_MATCHING_ABIS: " + msg); break;
+ default: target.onFailure(msg); break;
+ }
+ }
+ }
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index a0e56f6..7e783eb 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -17,9 +17,9 @@
package android.content.pm;
import android.annotation.IntDef;
-import android.annotation.SystemApi;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemApi;
import android.app.PackageInstallObserver;
import android.content.ComponentName;
import android.content.Context;
@@ -29,13 +29,12 @@
import android.content.pm.PackageParser.PackageParserException;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
-import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.net.Uri;
+import android.os.Bundle;
import android.os.Environment;
import android.os.UserHandle;
import android.util.AndroidException;
-import android.util.DisplayMetrics;
import java.io.File;
import java.lang.annotation.Retention;
@@ -750,7 +749,7 @@
* permission that is already defined by some existing package.
*
* <p>The package name of the app which has already defined the permission is passed to
- * a {@link IPackageInstallObserver2}, if any, as the {@link #EXTRA_EXISTING_PACKAGE}
+ * a {@link PackageInstallObserver}, if any, as the {@link #EXTRA_EXISTING_PACKAGE}
* string extra; and the name of the permission being redefined is passed in the
* {@link #EXTRA_EXISTING_PERMISSION} string extra.
* @hide
@@ -1544,7 +1543,7 @@
= "android.content.pm.extra.PERMISSION_LIST";
/**
- * String extra for {@link IPackageInstallObserver2} in the 'extras' Bundle in case of
+ * String extra for {@link PackageInstallObserver} in the 'extras' Bundle in case of
* {@link #INSTALL_FAILED_DUPLICATE_PERMISSION}. This extra names the package which provides
* the existing definition for the permission.
* @hide
@@ -1553,7 +1552,7 @@
= "android.content.pm.extra.FAILURE_EXISTING_PACKAGE";
/**
- * String extra for {@link IPackageInstallObserver2} in the 'extras' Bundle in case of
+ * String extra for {@link PackageInstallObserver} in the 'extras' Bundle in case of
* {@link #INSTALL_FAILED_DUPLICATE_PERMISSION}. This extra names the permission that is
* being redundantly defined by the package being installed.
* @hide
@@ -2941,26 +2940,29 @@
}
/**
- * @hide
- *
- * Install a package. Since this may take a little while, the result will
- * be posted back to the given observer. An installation will fail if the calling context
- * lacks the {@link android.Manifest.permission#INSTALL_PACKAGES} permission, if the
- * package named in the package file's manifest is already installed, or if there's no space
- * available on the device.
- *
- * @param packageURI The location of the package file to install. This can be a 'file:' or a
- * 'content:' URI.
- * @param observer An observer callback to get notified when the package installation is
- * complete. {@link IPackageInstallObserver#packageInstalled(String, int)} will be
- * called when that happens. This parameter must not be null.
+ * @hide Install a package. Since this may take a little while, the result
+ * will be posted back to the given observer. An installation will
+ * fail if the calling context lacks the
+ * {@link android.Manifest.permission#INSTALL_PACKAGES} permission, if
+ * the package named in the package file's manifest is already
+ * installed, or if there's no space available on the device.
+ * @param packageURI The location of the package file to install. This can
+ * be a 'file:' or a 'content:' URI.
+ * @param observer An observer callback to get notified when the package
+ * installation is complete.
+ * {@link IPackageInstallObserver#packageInstalled(String, int)}
+ * will be called when that happens. This parameter must not be
+ * null.
* @param flags - possible values: {@link #INSTALL_FORWARD_LOCK},
- * {@link #INSTALL_REPLACE_EXISTING}, {@link #INSTALL_ALLOW_TEST}.
- * @param installerPackageName Optional package name of the application that is performing the
- * installation. This identifies which market the package came from.
- * @deprecated Use {@link #installPackage(Uri, IPackageInstallObserver2, int, String)}
- * instead. This method will continue to be supported but the older observer interface
- * will not get additional failure details.
+ * {@link #INSTALL_REPLACE_EXISTING},
+ * {@link #INSTALL_ALLOW_TEST}.
+ * @param installerPackageName Optional package name of the application that
+ * is performing the installation. This identifies which market
+ * the package came from.
+ * @deprecated Use {@link #installPackage(Uri, PackageInstallObserver, int,
+ * String)} instead. This method will continue to be supported
+ * but the older observer interface will not get additional
+ * failure details.
*/
// @SystemApi
public abstract void installPackage(
@@ -2977,9 +2979,11 @@
* @param observer An observer callback to get notified when the package
* installation is complete.
* {@link IPackageInstallObserver#packageInstalled(String, int)}
- * will be called when that happens. This parameter must not be null.
+ * will be called when that happens. This parameter must not be
+ * null.
* @param flags - possible values: {@link #INSTALL_FORWARD_LOCK},
- * {@link #INSTALL_REPLACE_EXISTING}, {@link #INSTALL_ALLOW_TEST}.
+ * {@link #INSTALL_REPLACE_EXISTING},
+ * {@link #INSTALL_ALLOW_TEST}.
* @param installerPackageName Optional package name of the application that
* is performing the installation. This identifies which market
* the package came from.
@@ -2992,10 +2996,11 @@
* these parameters describing the encryption and authentication
* used. May be {@code null}.
* @hide
- * @deprecated Use {@link #installPackageWithVerification(Uri, IPackageInstallObserver2,
- * int, String, Uri, ManifestDigest, ContainerEncryptionParams)} instead. This method will
- * continue to be supported but the older observer interface will not get additional failure
- * details.
+ * @deprecated Use {@link #installPackageWithVerification(Uri,
+ * PackageInstallObserver, int, String, Uri, ManifestDigest,
+ * ContainerEncryptionParams)} instead. This method will
+ * continue to be supported but the older observer interface
+ * will not get additional failure details.
*/
// @SystemApi
public abstract void installPackageWithVerification(Uri packageURI,
@@ -3013,9 +3018,11 @@
* @param observer An observer callback to get notified when the package
* installation is complete.
* {@link IPackageInstallObserver#packageInstalled(String, int)}
- * will be called when that happens. This parameter must not be null.
+ * will be called when that happens. This parameter must not be
+ * null.
* @param flags - possible values: {@link #INSTALL_FORWARD_LOCK},
- * {@link #INSTALL_REPLACE_EXISTING}, {@link #INSTALL_ALLOW_TEST}.
+ * {@link #INSTALL_REPLACE_EXISTING},
+ * {@link #INSTALL_ALLOW_TEST}.
* @param installerPackageName Optional package name of the application that
* is performing the installation. This identifies which market
* the package came from.
@@ -3024,12 +3031,12 @@
* @param encryptionParams if the package to be installed is encrypted,
* these parameters describing the encryption and authentication
* used. May be {@code null}.
- *
* @hide
* @deprecated Use {@link #installPackageWithVerificationAndEncryption(Uri,
- * IPackageInstallObserver2, int, String, VerificationParams,
- * ContainerEncryptionParams)} instead. This method will continue to be
- * supported but the older observer interface will not get additional failure details.
+ * PackageInstallObserver, int, String, VerificationParams,
+ * ContainerEncryptionParams)} instead. This method will
+ * continue to be supported but the older observer interface
+ * will not get additional failure details.
*/
@Deprecated
public abstract void installPackageWithVerificationAndEncryption(Uri packageURI,
diff --git a/packages/SystemUI/res/drawable/ic_lock_to_app_24dp.xml b/packages/SystemUI/res/drawable/ic_lock_to_app_24dp.xml
new file mode 100644
index 0000000..e5737ee
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_lock_to_app_24dp.xml
@@ -0,0 +1,28 @@
+<!--
+Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android" >
+ <size
+ android:width="24.0dp"
+ android:height="24.0dp"/>
+
+ <viewport
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"/>
+
+ <path
+ android:fill="@color/recents_task_view_lock_to_app_button_color"
+ android:pathData="M18.0,8.0l-1.0,0.0L17.0,6.0c0.0,-2.8 -2.2,-5.0 -5.0,-5.0C9.2,1.0 7.0,3.2 7.0,6.0l0.0,2.0L6.0,8.0c-1.1,0.0 -2.0,0.9 -2.0,2.0l0.0,10.0c0.0,1.1 0.9,2.0 2.0,2.0l12.0,0.0c1.1,0.0 2.0,-0.9 2.0,-2.0L20.0,10.0C20.0,8.9 19.1,8.0 18.0,8.0zM12.0,17.0c-1.1,0.0 -2.0,-0.9 -2.0,-2.0s0.9,-2.0 2.0,-2.0c1.1,0.0 2.0,0.9 2.0,2.0S13.1,17.0 12.0,17.0zM15.1,8.0L8.9,8.0L8.9,6.0c0.0,-1.7 1.4,-3.1 3.1,-3.1c1.7,0.0 3.1,1.4 3.1,3.1L15.1,8.0z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/recents_lock_to_task_button_bg.xml b/packages/SystemUI/res/drawable/recents_lock_to_task_button_bg.xml
new file mode 100644
index 0000000..d38c8a4
--- /dev/null
+++ b/packages/SystemUI/res/drawable/recents_lock_to_task_button_bg.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="#ffdadada">
+ <item android:drawable="@color/recents_task_view_lock_to_app_button_background_color" />
+</ripple>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/recents_task_view.xml b/packages/SystemUI/res/layout/recents_task_view.xml
index 2b50a95..7e8bfd32 100644
--- a/packages/SystemUI/res/layout/recents_task_view.xml
+++ b/packages/SystemUI/res/layout/recents_task_view.xml
@@ -62,6 +62,27 @@
android:visibility="invisible"
android:src="@drawable/recents_dismiss_light" />
</com.android.systemui.recents.views.TaskBarView>
+ <FrameLayout
+ android:id="@+id/lock_to_app"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/recents_task_view_lock_to_app_button_height"
+ android:layout_gravity="center_horizontal|bottom"
+ android:background="@drawable/recents_lock_to_task_button_bg"
+ android:visibility="invisible">
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_gravity="center_horizontal"
+ android:gravity="center"
+ android:drawableLeft="@drawable/ic_lock_to_app_24dp"
+ android:drawablePadding="8dp"
+ android:textSize="16sp"
+ android:textColor="@color/recents_task_view_lock_to_app_button_color"
+ android:text="@string/recents_lock_to_app_button_label"
+ android:fontFamily="sans-serif-medium"
+ android:singleLine="true"
+ android:textAllCaps="true" />
+ </FrameLayout>
</com.android.systemui.recents.views.TaskView>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index ef8302d..4cc0bb5 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -53,13 +53,17 @@
<!-- The recents task bar light text color to be drawn on top of dark backgrounds. -->
<color name="recents_task_bar_light_text_color">#ffeeeeee</color>
<!-- The recents task bar dark text color to be drawn on top of light backgrounds. -->
- <color name="recents_task_bar_dark_text_color">#ff333333</color>
+ <color name="recents_task_bar_dark_text_color">#cc000000</color>
<!-- 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">#ff333333</color>
+ <color name="recents_task_bar_dark_dismiss_color">#cc000000</color>
<!-- The recents task bar highlight color. -->
<color name="recents_task_bar_highlight_color">#28ffffff</color>
+ <!-- The lock to task button background color. -->
+ <color name="recents_task_view_lock_to_app_button_background_color">#ffe6e6e6</color>
+ <!-- The lock to task button foreground color. -->
+ <color name="recents_task_view_lock_to_app_button_color">#ff666666</color>
<color name="keyguard_affordance">#ffffffff</color>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index f8b04ae..94fcc23 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -127,6 +127,10 @@
<integer name="recents_animate_task_enter_from_home_duration">275</integer>
<!-- The animation stagger to apply to each task animation when transitioning from home. -->
<integer name="recents_animate_task_enter_from_home_delay">10</integer>
+ <!-- The short duration when animating in/out the lock to app button. -->
+ <integer name="recents_animate_lock_to_app_button_short_duration">150</integer>
+ <!-- The long duration when animating in/out the lock to app button. -->
+ <integer name="recents_animate_lock_to_app_button_long_duration">300</integer>
<!-- The min animation duration for animating the nav bar scrim in. -->
<integer name="recents_nav_bar_scrim_enter_duration">400</integer>
<!-- The animation duration for animating the removal of a task view. -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index e20947f..e86aa0a 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -210,6 +210,9 @@
<!-- The amount of highlight to make on each task view. -->
<dimen name="recents_task_view_highlight">1dp</dimen>
+ <!-- The height of the lock-to-app button. -->
+ <dimen name="recents_task_view_lock_to_app_button_height">48dp</dimen>
+
<!-- The height of a task view bar. -->
<dimen name="recents_task_bar_height">56dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 848fdf8..48670fb 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -563,6 +563,8 @@
<string name="recents_empty_message">No recent apps</string>
<!-- Recents: The info panel app info button string. [CHAR LIMIT=NONE] -->
<string name="recents_app_info_button_label">Application Info</string>
+ <!-- Recents: The lock-to-app button. [CHAR LIMIT=NONE] -->
+ <string name="recents_lock_to_app_button_label">lock to app</string>
<!-- Recents: Temporary string for the button in the recents search bar. [CHAR LIMIT=NONE] -->
<string name="recents_search_bar_label">search</string>
diff --git a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
index e375433..a9a606f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
@@ -68,6 +68,7 @@
// Recents service binding
Handler mHandler;
boolean mBootCompleted = false;
+ boolean mStartAnimationTriggered = false;
// Task launching
RecentsConfiguration mConfig;
@@ -252,6 +253,7 @@
* Creates the activity options for a unknown state->recents transition.
*/
ActivityOptions getUnknownTransitionActivityOptions() {
+ mStartAnimationTriggered = false;
return ActivityOptions.makeCustomAnimation(mContext,
R.anim.recents_from_unknown_enter,
R.anim.recents_from_unknown_exit, mHandler, this);
@@ -261,6 +263,7 @@
* Creates the activity options for a home->recents transition.
*/
ActivityOptions getHomeTransitionActivityOptions() {
+ mStartAnimationTriggered = false;
return ActivityOptions.makeCustomAnimation(mContext,
R.anim.recents_from_launcher_enter,
R.anim.recents_from_launcher_exit, mHandler, this);
@@ -279,6 +282,7 @@
// Take the full screenshot
sLastScreenshot = mSystemServicesProxy.takeAppScreenshot();
if (sLastScreenshot != null) {
+ mStartAnimationTriggered = false;
return ActivityOptions.makeCustomAnimation(mContext,
R.anim.recents_from_app_enter,
R.anim.recents_from_app_exit, mHandler, this);
@@ -302,6 +306,7 @@
c.setBitmap(null);
// Recycle the old thumbnail
firstThumbnail.recycle();
+ mStartAnimationTriggered = false;
return ActivityOptions.makeThumbnailScaleDownAnimation(mStatusBarView,
thumbnail, toTaskRect.left, toTaskRect.top, this);
}
@@ -449,9 +454,12 @@
@Override
public void onAnimationStarted() {
// Notify recents to start the enter animation
- Intent intent = new Intent(RecentsActivity.ACTION_START_ENTER_ANIMATION);
- intent.setPackage(mContext.getPackageName());
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
- mContext.sendBroadcast(intent);
+ if (!mStartAnimationTriggered) {
+ Intent intent = new Intent(RecentsActivity.ACTION_START_ENTER_ANIMATION);
+ intent.setPackage(mContext.getPackageName());
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ mContext.sendBroadcast(intent);
+ mStartAnimationTriggered = true;
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
index b039485..e62d989 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
@@ -92,6 +92,11 @@
public int taskBarExitAnimDuration;
public int taskBarDismissDozeDelaySeconds;
+ /** Lock to app */
+ public int taskViewLockToAppButtonHeight;
+ public int taskViewLockToAppShortAnimDuration;
+ public int taskViewLockToAppLongAnimDuration;
+
/** Nav bar scrim */
public int navBarScrimEnterDuration;
@@ -226,6 +231,14 @@
taskBarDismissDozeDelaySeconds =
res.getInteger(R.integer.recents_task_bar_dismiss_delay_seconds);
+ // Lock to app
+ taskViewLockToAppButtonHeight =
+ res.getDimensionPixelSize(R.dimen.recents_task_view_lock_to_app_button_height);
+ taskViewLockToAppShortAnimDuration =
+ res.getInteger(R.integer.recents_animate_lock_to_app_button_short_duration);
+ taskViewLockToAppLongAnimDuration =
+ res.getInteger(R.integer.recents_animate_lock_to_app_button_long_duration);
+
// Nav bar scrim
navBarScrimEnterDuration =
res.getInteger(R.integer.recents_nav_bar_scrim_enter_duration);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index 05c0f58..b8beda6f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -17,8 +17,10 @@
package com.android.systemui.recents.misc;
import android.app.ActivityManager;
+import android.app.ActivityManagerNative;
import android.app.ActivityOptions;
import android.app.AppGlobals;
+import android.app.IActivityManager;
import android.app.SearchManager;
import android.appwidget.AppWidgetHost;
import android.appwidget.AppWidgetManager;
@@ -66,6 +68,7 @@
final static String TAG = "SystemServicesProxy";
ActivityManager mAm;
+ IActivityManager mIam;
AppWidgetManager mAwm;
PackageManager mPm;
IPackageManager mIpm;
@@ -83,6 +86,7 @@
/** Private constructor */
public SystemServicesProxy(Context context) {
mAm = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+ mIam = ActivityManagerNative.getDefault();
mAwm = AppWidgetManager.getInstance(context);
mPm = context.getPackageManager();
mUm = (UserManager) context.getSystemService(Context.USER_SERVICE);
@@ -407,6 +411,17 @@
}
/**
+ * Locks the current task.
+ */
+ public void lockCurrentTask() {
+ if (mIam == null) return;
+
+ try {
+ mIam.startLockTaskModeOnCurrent();
+ } catch (RemoteException e) {}
+ }
+
+ /**
* Takes a screenshot of the current surface.
*/
public Bitmap takeScreenshot() {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
index 94474e9..86e8981 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
@@ -429,7 +429,8 @@
// Create a new task
Task task = new Task(t.persistentId, (t.id > -1), t.baseIntent, 0, activityLabel,
- activityIcon, activityColor, t.userId, t.firstActiveTime, t.lastActiveTime);
+ activityIcon, activityColor, t.userId, t.firstActiveTime, t.lastActiveTime,
+ (i == (taskCount - 1)));
// Preload the specified number of apps
if (i >= (taskCount - preloadCount)) {
@@ -523,7 +524,7 @@
if (info == null) continue;
stack.addTask(new Task(t.persistentId, true, t.baseIntent, 0, null, null, 0, 0,
- t.firstActiveTime, t.lastActiveTime));
+ t.firstActiveTime, t.lastActiveTime, (i == (taskCount - 1))));
}
stack.createSimulatedAffiliatedGroupings();
return stack;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
index 0667e4c..88e9f40 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
@@ -87,6 +87,7 @@
public int colorPrimaryGreyscale;
public Bitmap thumbnail;
public boolean isActive;
+ public boolean canLockToTask;
public int userId;
TaskCallbacks mCb;
@@ -97,7 +98,7 @@
public Task(int id, boolean isActive, Intent intent, int taskAffiliation, String activityTitle,
Drawable activityIcon, int colorPrimary, int userId,
- long firstActiveTime, long lastActiveTime) {
+ long firstActiveTime, long lastActiveTime, boolean canLockToTask) {
this.key = new TaskKey(id, intent, userId, firstActiveTime, lastActiveTime);
this.taskAffiliation = taskAffiliation;
this.activityLabel = activityTitle;
@@ -105,6 +106,7 @@
this.colorPrimary = colorPrimary;
this.colorPrimaryGreyscale = Utilities.colorToGreyscale(colorPrimary);
this.isActive = isActive;
+ this.canLockToTask = canLockToTask;
this.userId = userId;
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
index 1ed0edd..7dd15a6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
@@ -150,7 +150,7 @@
/* Notifies when a task has been added to the stack */
public void onStackTaskAdded(TaskStack stack, Task t);
/* Notifies when a task has been removed from the stack */
- public void onStackTaskRemoved(TaskStack stack, Task t);
+ public void onStackTaskRemoved(TaskStack stack, Task removedTask, Task newFrontMostTask);
/** Notifies when the stack was filtered */
public void onStackFiltered(TaskStack newStack, ArrayList<Task> curTasks, Task t);
/** Notifies when the stack was un-filtered */
@@ -203,9 +203,15 @@
if (group.getTaskCount() == 0) {
removeGroup(group);
}
+ // Update the lock-to-app state
+ Task newFrontMostTask = getFrontMostTask();
+ t.canLockToTask = false;
+ if (newFrontMostTask != null) {
+ newFrontMostTask.canLockToTask = true;
+ }
if (mCb != null) {
// Notify that a task has been removed
- mCb.onStackTaskRemoved(this, t);
+ mCb.onStackTaskRemoved(this, t, newFrontMostTask);
}
}
}
@@ -226,7 +232,7 @@
}
if (mCb != null) {
// Notify that a task has been removed
- mCb.onStackTaskRemoved(this, t);
+ mCb.onStackTaskRemoved(this, t, null);
}
}
mTaskList.set(tasks);
@@ -239,6 +245,7 @@
/** Gets the front task */
public Task getFrontMostTask() {
+ if (mTaskList.size() == 0) return null;
return mTaskList.getTasks().get(mTaskList.size() - 1);
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index 85afb32..99b012e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -34,9 +34,10 @@
import android.view.View;
import android.view.WindowInsets;
import android.widget.FrameLayout;
-import com.android.systemui.recents.misc.Console;
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.SystemServicesProxy;
import com.android.systemui.recents.model.RecentsPackageMonitor;
import com.android.systemui.recents.model.RecentsTaskLoader;
import com.android.systemui.recents.model.SpaceNode;
@@ -144,7 +145,7 @@
Console.log(Constants.Log.UI.Focus, "[RecentsView|launchFocusedTask]",
"Found focused Task");
}
- onTaskViewClicked(stackView, tv, stack, task);
+ onTaskViewClicked(stackView, tv, stack, task, false);
return true;
}
}
@@ -180,7 +181,7 @@
tv = stv;
}
}
- onTaskViewClicked(stackView, tv, stack, task);
+ onTaskViewClicked(stackView, tv, stack, task, false);
return true;
}
}
@@ -431,7 +432,7 @@
@Override
public void onTaskViewClicked(final TaskStackView stackView, final TaskView tv,
- final TaskStack stack, final Task task) {
+ final TaskStack stack, final Task task, final boolean lockToTask) {
// Notify any callbacks of the launching of a new task
if (mCb != null) {
mCb.onTaskViewClicked();
@@ -456,6 +457,8 @@
}
// Compute the thumbnail to scale up from
+ final SystemServicesProxy ssp =
+ RecentsTaskLoader.getInstance().getSystemServicesProxy();
ActivityOptions opts = null;
int thumbnailWidth = transform.rect.width();
int thumbnailHeight = transform.rect.height();
@@ -469,8 +472,26 @@
new Rect(0, 0, task.thumbnail.getWidth(), task.thumbnail.getHeight()),
new Rect(0, 0, thumbnailWidth, thumbnailHeight), null);
c.setBitmap(null);
+ ActivityOptions.OnAnimationStartedListener animStartedListener = null;
+ if (lockToTask) {
+ animStartedListener = new ActivityOptions.OnAnimationStartedListener() {
+ boolean mTriggered = false;
+ @Override
+ public void onAnimationStarted() {
+ if (!mTriggered) {
+ postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ ssp.lockCurrentTask();
+ }
+ }, 350);
+ mTriggered = true;
+ }
+ }
+ };
+ }
opts = ActivityOptions.makeThumbnailScaleUpAnimation(sourceView,
- b, offsetX, offsetY);
+ b, offsetX, offsetY, animStartedListener);
}
final ActivityOptions launchOpts = opts;
@@ -496,8 +517,12 @@
UserHandle taskUser = new UserHandle(task.userId);
if (launchOpts != null) {
getContext().startActivityAsUser(i, launchOpts.toBundle(), taskUser);
+
} else {
getContext().startActivityAsUser(i, taskUser);
+ if (lockToTask) {
+ ssp.lockCurrentTask();
+ }
}
} catch (ActivityNotFoundException anfe) {
Console.logError(getContext(), "Could not start Activity");
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 35cf8ab..599c590 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -51,11 +51,12 @@
/* The visual representation of a task stack view */
public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCallbacks,
TaskView.TaskViewCallbacks, ViewPool.ViewPoolConsumer<TaskView, Task>,
- View.OnClickListener, RecentsPackageMonitor.PackageCallbacks {
+ RecentsPackageMonitor.PackageCallbacks {
/** The TaskView callbacks */
interface TaskStackViewCallbacks {
- public void onTaskViewClicked(TaskStackView stackView, TaskView tv, TaskStack stack, Task t);
+ public void onTaskViewClicked(TaskStackView stackView, TaskView tv, TaskStack stack, Task t,
+ boolean lockToTask);
public void onTaskViewAppInfoClicked(Task t);
public void onTaskViewDismissed(Task t);
public void onAllTaskViewsDismissed();
@@ -734,7 +735,8 @@
for (int i = 0; i < childCount; i++) {
TaskView t = (TaskView) getChildAt(i);
t.measure(MeasureSpec.makeMeasureSpec(mStackAlgorithm.mTaskRect.width(), MeasureSpec.EXACTLY),
- MeasureSpec.makeMeasureSpec(mStackAlgorithm.mTaskRect.height(), MeasureSpec.EXACTLY));
+ MeasureSpec.makeMeasureSpec(mStackAlgorithm.mTaskRect.height() +
+ mConfig.taskViewLockToAppButtonHeight, MeasureSpec.EXACTLY));
}
setMeasuredDimension(width, height);
@@ -766,7 +768,7 @@
TaskView t = (TaskView) getChildAt(i);
t.layout(mStackAlgorithm.mTaskRect.left, mStackAlgorithm.mStackRectSansPeek.top,
mStackAlgorithm.mTaskRect.right, mStackAlgorithm.mStackRectSansPeek.top +
- mStackAlgorithm.mTaskRect.height());
+ mStackAlgorithm.mTaskRect.height() + mConfig.taskViewLockToAppButtonHeight);
}
if (mAwaitingFirstLayout) {
@@ -903,11 +905,6 @@
mUIDozeTrigger.poke();
}
- /** Disables handling touch on this task view. */
- void setTouchOnTaskView(TaskView tv, boolean enabled) {
- tv.setOnClickListener(enabled ? this : null);
- }
-
/**** TaskStackCallbacks Implementation ****/
@Override
@@ -919,25 +916,33 @@
}
@Override
- public void onStackTaskRemoved(TaskStack stack, Task t) {
+ public void onStackTaskRemoved(TaskStack stack, Task removedTask, Task newFrontMostTask) {
// Update the task offsets
mStackAlgorithm.updateTaskOffsets(mStack.getTasks());
// Remove the view associated with this task, we can't rely on updateTransforms
// to work here because the task is no longer in the list
- TaskView tv = getChildViewForTask(t);
+ TaskView tv = getChildViewForTask(removedTask);
if (tv != null) {
mViewPool.returnViewToPool(tv);
}
// Notify the callback that we've removed the task and it can clean up after it
- mCb.onTaskViewDismissed(t);
+ mCb.onTaskViewDismissed(removedTask);
// Update the min/max scroll and animate other task views into their new positions
updateMinMaxScroll(true);
int movement = (int) mStackAlgorithm.getTaskOverlapHeight();
requestSynchronizeStackViewsWithModel(Utilities.calculateTranslationAnimationDuration(movement));
+ // Update the new front most task
+ if (newFrontMostTask != null) {
+ TaskView frontTv = getChildViewForTask(newFrontMostTask);
+ if (frontTv != null) {
+ frontTv.onTaskBound(newFrontMostTask);
+ }
+ }
+
// If there are no remaining tasks, then either unfilter the current stack, or just close
// the activity if there are no filtered stacks
if (mStack.getTaskCount() == 0) {
@@ -1086,7 +1091,7 @@
addView(tv, insertIndex);
// Set the callbacks and listeners for this new view
- setTouchOnTaskView(tv, true);
+ tv.setTouchEnabled(true);
tv.setCallbacks(this);
} else {
attachViewToParent(tv, insertIndex, tv.getLayoutParams());
@@ -1129,18 +1134,7 @@
}
@Override
- public void onTaskViewDismissed(TaskView tv) {
- Task task = tv.getTask();
- // Remove the task from the view
- mStack.removeTask(task);
- }
-
- /**** View.OnClickListener Implementation ****/
-
- @Override
- public void onClick(View v) {
- TaskView tv = (TaskView) v;
- Task task = tv.getTask();
+ public void onTaskViewClicked(TaskView tv, Task task, boolean lockToTask) {
if (Console.Enabled) {
Console.log(Constants.Log.UI.ClickEvents, "[TaskStack|Clicked|Thumbnail]",
task + " cb: " + mCb);
@@ -1150,10 +1144,17 @@
mUIDozeTrigger.stopDozing();
if (mCb != null) {
- mCb.onTaskViewClicked(this, tv, mStack, task);
+ mCb.onTaskViewClicked(this, tv, mStack, task, lockToTask);
}
}
+ @Override
+ public void onTaskViewDismissed(TaskView tv) {
+ Task task = tv.getTask();
+ // Remove the task from the view
+ mStack.removeTask(task);
+ }
+
/**** RecentsPackageMonitor.PackageCallbacks Implementation ****/
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
index 789b4f7..e1e682b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
@@ -95,9 +95,11 @@
if (numTasks <= 1) {
// If there is only one task, then center the task in the stack rect (sans peek)
- mMinScroll = mMaxScroll = -(stackHeight - taskHeight) / 2;
+ mMinScroll = mMaxScroll = -(stackHeight -
+ (taskHeight + mConfig.taskViewLockToAppButtonHeight)) / 2;
} else {
- int maxScrollHeight = taskHeight + getStackScrollForTaskIndex(tasks.get(tasks.size() - 1));
+ int maxScrollHeight = getStackScrollForTaskIndex(tasks.get(tasks.size() - 1))
+ + taskHeight + mConfig.taskViewLockToAppButtonHeight;
mMinScroll = Math.min(stackHeight, maxScrollHeight) - stackHeight;
mMaxScroll = maxScrollHeight - stackHeight;
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
index e186e2e..15ace13 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
@@ -377,7 +377,9 @@
// Enable HW layers on that task
tv.enableHwLayers();
// Disallow touch events from this task view
- mSv.setTouchOnTaskView(tv, false);
+ tv.setTouchEnabled(false);
+ // Hide the footer
+ tv.animateFooterVisibility(false, mSv.mConfig.taskViewLockToAppShortAnimDuration, 0);
// Disallow parents from intercepting touch events
final ViewParent parent = mSv.getParent();
if (parent != null) {
@@ -413,7 +415,9 @@
// Re-enable clipping with the stack
tv.setClipViewInStack(true);
// Re-enable touch events from this task view
- mSv.setTouchOnTaskView(tv, true);
+ tv.setTouchEnabled(true);
+ // Restore the footer
+ tv.animateFooterVisibility(true, mSv.mConfig.taskViewLockToAppShortAnimDuration, 0);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index 33e3f58..125b018 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -35,6 +35,7 @@
import com.android.systemui.recents.Constants;
import com.android.systemui.recents.RecentsConfiguration;
import com.android.systemui.recents.model.Task;
+import com.android.systemui.recents.model.TaskStack;
/* A task view */
@@ -44,11 +45,16 @@
interface TaskViewCallbacks {
public void onTaskViewAppIconClicked(TaskView tv);
public void onTaskViewAppInfoClicked(TaskView tv);
+ public void onTaskViewClicked(TaskView tv, Task t, boolean lockToTask);
public void onTaskViewDismissed(TaskView tv);
}
RecentsConfiguration mConfig;
+ int mFooterHeight;
+ int mMaxFooterHeight;
+ ObjectAnimator mFooterAnimator;
+
int mDim;
int mMaxDim;
AccelerateInterpolator mDimInterpolator = new AccelerateInterpolator();
@@ -60,9 +66,11 @@
boolean mClipViewInStack;
Rect mTmpRect = new Rect();
Paint mLayerPaint = new Paint();
+ Outline mOutline = new Outline();
TaskThumbnailView mThumbnailView;
TaskBarView mBarView;
+ View mLockToAppButtonView;
TaskViewCallbacks mCb;
// Optimizations
@@ -102,9 +110,11 @@
public TaskView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
mConfig = RecentsConfiguration.getInstance();
+ mMaxFooterHeight = mConfig.taskViewLockToAppButtonHeight;
setWillNotDraw(false);
setClipToOutline(true);
setDim(getDim());
+ setFooterHeight(getFooterHeight());
}
@Override
@@ -117,6 +127,7 @@
// Bind the views
mBarView = (TaskBarView) findViewById(R.id.task_view_bar);
mThumbnailView = (TaskThumbnailView) findViewById(R.id.task_view_thumbnail);
+ mLockToAppButtonView = findViewById(R.id.lock_to_app);
if (mTaskDataLoaded) {
onTaskDataLoaded();
@@ -125,13 +136,33 @@
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ int width = MeasureSpec.getSize(widthMeasureSpec);
+ int height = MeasureSpec.getSize(heightMeasureSpec);
- // Update the outline
- Outline o = new Outline();
- o.setRoundRect(0, 0, getMeasuredWidth(), getMeasuredHeight(),
+ // Measure the bar view, thumbnail, and lock-to-app buttons
+ mBarView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(mConfig.taskBarHeight, MeasureSpec.EXACTLY));
+ mLockToAppButtonView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(mConfig.taskViewLockToAppButtonHeight,
+ MeasureSpec.EXACTLY));
+ // Measure the thumbnail height to be the same as the width
+ mThumbnailView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY));
+ setMeasuredDimension(width, height);
+ updateOutline();
+ }
+
+ /** Updates the outline to match whether the lock-to-app button is visible or not. */
+ void updateOutline() {
+ int height = getMeasuredHeight();
+ if (height == 0) return;
+
+ // Account for the current footer height
+ height = height - mMaxFooterHeight + mFooterHeight;
+
+ mOutline.setRoundRect(0, 0, getMeasuredWidth(), height,
mConfig.taskViewRoundedCornerRadiusPx);
- setOutline(o);
+ setOutline(mOutline);
}
/** Set callback */
@@ -289,6 +320,8 @@
// Animate the task bar of the first task view
mBarView.startEnterRecentsAnimation(0, mEnableThumbnailClip);
setVisibility(View.VISIBLE);
+ // Animate the footer into view
+ animateFooterVisibility(true, mConfig.taskBarEnterAnimDuration, 0);
// Decrement the post animation trigger
ctx.postAnimationTrigger.decrement();
}
@@ -335,6 +368,10 @@
});
anim.start();
ctx.postAnimationTrigger.increment();
+
+ // Animate the footer into view
+ animateFooterVisibility(true, mConfig.taskBarEnterAnimDuration,
+ mConfig.taskBarEnterAnimDelay);
} else {
mEnableThumbnailClip.run();
}
@@ -366,9 +403,16 @@
})
.start();
ctx.postAnimationTrigger.increment();
+
+ // Animate the footer into view
+ animateFooterVisibility(true, mConfig.taskViewEnterFromHomeDuration,
+ mConfig.taskBarEnterAnimDelay);
} else {
// Otherwise, just enable the thumbnail clip
mEnableThumbnailClip.run();
+
+ // Animate the footer into view
+ animateFooterVisibility(true, 0, 0);
}
}
@@ -457,12 +501,14 @@
void enableHwLayers() {
mThumbnailView.setLayerType(View.LAYER_TYPE_HARDWARE, mLayerPaint);
mBarView.enableHwLayers();
+ mLockToAppButtonView.setLayerType(View.LAYER_TYPE_HARDWARE, mLayerPaint);
}
/** Disable the hw layers on this task view */
void disableHwLayers() {
mThumbnailView.setLayerType(View.LAYER_TYPE_NONE, mLayerPaint);
mBarView.disableHwLayers();
+ mLockToAppButtonView.setLayerType(View.LAYER_TYPE_NONE, mLayerPaint);
}
/** Sets the stubbed state of this task view. */
@@ -499,6 +545,57 @@
}
}
+ /** Sets the footer height. */
+ public void setFooterHeight(int height) {
+ mFooterHeight = height;
+ updateOutline();
+ invalidate(0, getMeasuredHeight() - mMaxFooterHeight, getMeasuredWidth(),
+ getMeasuredHeight());
+ }
+
+ /** Gets the footer height. */
+ public int getFooterHeight() {
+ return mFooterHeight;
+ }
+
+ /** Animates the footer into and out of view. */
+ public void animateFooterVisibility(boolean visible, int duration, int delay) {
+ if (!mTask.canLockToTask) return;
+ if (mMaxFooterHeight <= 0) return;
+
+ if (mFooterAnimator != null) {
+ mFooterAnimator.removeAllListeners();
+ mFooterAnimator.cancel();
+ }
+ int height = visible ? mMaxFooterHeight : 0;
+ if (visible && mLockToAppButtonView.getVisibility() != View.VISIBLE) {
+ if (duration > 0) {
+ setFooterHeight(0);
+ } else {
+ setFooterHeight(mMaxFooterHeight);
+ }
+ mLockToAppButtonView.setVisibility(View.VISIBLE);
+ }
+ if (duration > 0) {
+ mFooterAnimator = ObjectAnimator.ofInt(this, "footerHeight", height);
+ mFooterAnimator.setDuration(duration);
+ mFooterAnimator.setInterpolator(mConfig.fastOutSlowInInterpolator);
+ if (!visible) {
+ mFooterAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mLockToAppButtonView.setVisibility(View.INVISIBLE);
+ }
+ });
+ }
+ mFooterAnimator.start();
+ } else {
+ if (!visible) {
+ mLockToAppButtonView.setVisibility(View.INVISIBLE);
+ }
+ }
+ }
+
/** Returns the current dim. */
public void setDim(int dim) {
mDim = dim;
@@ -584,6 +681,11 @@
public void onTaskBound(Task t) {
mTask = t;
mTask.setCallbacks(this);
+ if (getMeasuredWidth() == 0) {
+ animateFooterVisibility(t.canLockToTask, 0, 0);
+ } else {
+ animateFooterVisibility(t.canLockToTask, mConfig.taskViewLockToAppLongAnimDuration, 0);
+ }
}
@Override
@@ -597,6 +699,7 @@
mBarView.mApplicationIcon.setOnClickListener(this);
}
mBarView.mDismissButton.setOnClickListener(this);
+ mLockToAppButtonView.setOnClickListener(this);
if (Constants.DebugFlags.App.EnableDevAppInfoOnLongPress) {
if (mConfig.developerOptionsEnabled) {
mBarView.mApplicationIcon.setOnLongClickListener(this);
@@ -618,6 +721,7 @@
mBarView.mApplicationIcon.setOnClickListener(null);
}
mBarView.mDismissButton.setOnClickListener(null);
+ mLockToAppButtonView.setOnClickListener(null);
if (Constants.DebugFlags.App.EnableDevAppInfoOnLongPress) {
mBarView.mApplicationIcon.setOnLongClickListener(null);
}
@@ -625,6 +729,11 @@
mTaskDataLoaded = false;
}
+ /** Enables/disables handling touch on this task view. */
+ void setTouchEnabled(boolean enabled) {
+ setOnClickListener(enabled ? this : null);
+ }
+
@Override
public void onClick(final View v) {
// We purposely post the handler delayed to allow for the touch feedback to draw
@@ -642,6 +751,10 @@
mCb.onTaskViewDismissed(tv);
}
});
+ // Hide the footer
+ tv.animateFooterVisibility(false, mConfig.taskViewRemoveAnimDuration, 0);
+ } else if (v == tv || v == mLockToAppButtonView) {
+ mCb.onTaskViewClicked(tv, tv.getTask(), (v == mLockToAppButtonView));
}
}
}, 125);
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 682a38d..5f65a5e 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -24,12 +24,15 @@
import android.content.Context;
import android.content.pm.IPackageDeleteObserver;
import android.content.pm.IPackageInstaller;
+import android.content.pm.IPackageInstallerObserver;
import android.content.pm.IPackageInstallerSession;
-import android.content.pm.PackageInstallerParams;
+import android.content.pm.InstallSessionInfo;
+import android.content.pm.InstallSessionParams;
import android.os.Binder;
import android.os.FileUtils;
import android.os.HandlerThread;
import android.os.Process;
+import android.os.RemoteException;
import android.os.SELinux;
import android.os.UserHandle;
import android.os.UserManager;
@@ -47,6 +50,8 @@
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
public class PackageInstallerService extends IPackageInstaller.Stub {
private static final String TAG = "PackageInstaller";
@@ -138,7 +143,7 @@
}
@Override
- public int createSession(String installerPackageName, PackageInstallerParams params,
+ public int createSession(String installerPackageName, InstallSessionParams params,
int userId) {
final int callingUid = Binder.getCallingUid();
mPm.enforceCrossUserPermission(callingUid, userId, false, TAG);
@@ -217,29 +222,25 @@
}
@Override
- public int[] getSessions(String installerPackageName, int userId) {
- final int callingUid = Binder.getCallingUid();
- mPm.enforceCrossUserPermission(callingUid, userId, false, TAG);
- mAppOps.checkPackage(callingUid, installerPackageName);
+ public List<InstallSessionInfo> getSessions(int userId) {
+ mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, false, TAG);
- int[] matching = new int[0];
+ final List<InstallSessionInfo> result = new ArrayList<>();
synchronized (mSessions) {
for (int i = 0; i < mSessions.size(); i++) {
- final int key = mSessions.keyAt(i);
final PackageInstallerSession session = mSessions.valueAt(i);
- if (session.userId == userId
- && session.installerPackageName.equals(installerPackageName)) {
- matching = ArrayUtils.appendInt(matching, key);
+ if (session.userId == userId) {
+ result.add(session.generateInfo());
}
}
}
- return matching;
+ return result;
}
@Override
- public void uninstall(String basePackageName, int flags, IPackageDeleteObserver observer,
+ public void uninstall(String packageName, int flags, IPackageDeleteObserver observer,
int userId) {
- mPm.deletePackageAsUser(basePackageName, observer, userId, flags);
+ mPm.deletePackageAsUser(packageName, observer, userId, flags);
}
@Override
@@ -249,6 +250,16 @@
throw new UnsupportedOperationException();
}
+ @Override
+ public void registerObserver(IPackageInstallerObserver observer, int userId) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void unregisterObserver(IPackageInstallerObserver observer, int userId) {
+ throw new UnsupportedOperationException();
+ }
+
class Callback {
public void onProgressChanged(PackageInstallerSession session) {
// TODO: notify listeners
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 3448803..6a7a0b2 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -26,7 +26,8 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageInstallObserver2;
import android.content.pm.IPackageInstallerSession;
-import android.content.pm.PackageInstallerParams;
+import android.content.pm.InstallSessionInfo;
+import android.content.pm.InstallSessionParams;
import android.content.pm.PackageManager;
import android.content.pm.PackageParser;
import android.content.pm.PackageParser.ApkLite;
@@ -75,7 +76,7 @@
public final String installerPackageName;
/** UID not persisted */
public final int installerUid;
- public final PackageInstallerParams params;
+ public final InstallSessionParams params;
public final long createdMillis;
public final File sessionStageDir;
@@ -95,7 +96,8 @@
Slog.e(TAG, "Install failed: " + e);
destroy();
try {
- mRemoteObserver.packageInstalled(mPackageName, null, e.error);
+ mRemoteObserver.packageInstalled(mPackageName, null, e.error,
+ e.getMessage());
} catch (RemoteException ignored) {
}
}
@@ -123,7 +125,7 @@
public PackageInstallerSession(PackageInstallerService.Callback callback,
PackageManagerService pm, int sessionId, int userId, String installerPackageName,
- int installerUid, PackageInstallerParams params, long createdMillis, File sessionStageDir,
+ int installerUid, InstallSessionParams params, long createdMillis, File sessionStageDir,
Looper looper) {
mCallback = callback;
mPm = pm;
@@ -152,6 +154,21 @@
}
}
+ public InstallSessionInfo generateInfo() {
+ final InstallSessionInfo info = new InstallSessionInfo();
+
+ info.sessionId = sessionId;
+ info.installerPackageName = installerPackageName;
+ info.progress = mProgress;
+
+ info.fullInstall = params.fullInstall;
+ info.packageName = params.packageName;
+ info.icon = params.icon;
+ info.title = params.title;
+
+ return info;
+ }
+
@Override
public void updateProgress(int progress) {
mProgress = progress;
@@ -264,15 +281,15 @@
final IPackageInstallObserver2 remoteObserver = mRemoteObserver;
final IPackageInstallObserver2 localObserver = new IPackageInstallObserver2.Stub() {
@Override
- public void packageInstalled(String basePackageName, Bundle extras, int returnCode)
- throws RemoteException {
+ public void packageInstalled(String basePackageName, Bundle extras, int returnCode,
+ String msg) throws RemoteException {
destroy();
- remoteObserver.packageInstalled(basePackageName, extras, returnCode);
+ remoteObserver.packageInstalled(basePackageName, extras, returnCode, msg);
}
};
- mPm.installStage(mPackageName, this.sessionStageDir, localObserver, params, installerPackageName,
- installerUid, new UserHandle(userId));
+ mPm.installStage(mPackageName, this.sessionStageDir, localObserver, params,
+ installerPackageName, installerUid, new UserHandle(userId));
}
/**
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 1accfba..ca54679 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -91,12 +91,12 @@
import android.content.pm.IPackageManager;
import android.content.pm.IPackageMoveObserver;
import android.content.pm.IPackageStatsObserver;
+import android.content.pm.InstallSessionParams;
import android.content.pm.InstrumentationInfo;
import android.content.pm.ManifestDigest;
import android.content.pm.PackageCleanItem;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInfoLite;
-import android.content.pm.PackageInstallerParams;
import android.content.pm.PackageManager;
import android.content.pm.PackageParser.ActivityIntentInfo;
import android.content.pm.PackageParser.PackageLite;
@@ -1080,7 +1080,7 @@
if (args.observer != null) {
try {
Bundle extras = extrasForInstallResult(res);
- args.observer.packageInstalled(res.name, extras, res.returnCode);
+ args.observer.packageInstalled(res.name, extras, res.returnCode, null);
} catch (RemoteException e) {
Slog.i(TAG, "Observer no longer exists.");
}
@@ -7746,7 +7746,7 @@
if (isUserRestricted(UserHandle.getUserId(uid), UserManager.DISALLOW_INSTALL_APPS)) {
try {
if (observer != null) {
- observer.packageInstalled("", null, INSTALL_FAILED_USER_RESTRICTED);
+ observer.packageInstalled("", null, INSTALL_FAILED_USER_RESTRICTED, null);
}
} catch (RemoteException re) {
}
@@ -7779,7 +7779,7 @@
}
void installStage(String packageName, File stageDir, IPackageInstallObserver2 observer,
- PackageInstallerParams params, String installerPackageName, int installerUid,
+ InstallSessionParams params, String installerPackageName, int installerUid,
UserHandle user) {
final VerificationParams verifParams = new VerificationParams(null, params.originatingUri,
params.referrerUri, installerUid, null);
diff --git a/telephony/java/com/android/internal/telephony/IMms.aidl b/telephony/java/com/android/internal/telephony/IMms.aidl
index 4da90a5..db93a4f 100644
--- a/telephony/java/com/android/internal/telephony/IMms.aidl
+++ b/telephony/java/com/android/internal/telephony/IMms.aidl
@@ -17,6 +17,8 @@
package com.android.internal.telephony;
import android.app.PendingIntent;
+import android.content.ContentValues;
+import android.net.Uri;
/**
* Service interface to handle MMS API requests
@@ -25,25 +27,28 @@
/**
* Send an MMS message
*
+ * @param subId the SIM id
* @param callingPkg the package name of the calling app
* @param pdu the MMS message encoded in standard MMS PDU format
* @param locationUrl the optional location url for where this message should be sent to
* @param sentIntent if not NULL this <code>PendingIntent</code> is
* broadcast when the message is successfully sent, or failed
*/
- void sendMessage(String callingPkg, in byte[] pdu, String locationUrl,
+ void sendMessage(long subId, String callingPkg, in byte[] pdu, String locationUrl,
in PendingIntent sentIntent);
/**
* Download an MMS message using known location and transaction id
*
+ * @param subId the SIM id
* @param callingPkg the package name of the calling app
* @param locationUrl the location URL of the MMS message to be downloaded, usually obtained
* from the MMS WAP push notification
* @param downloadedIntent if not NULL this <code>PendingIntent</code> is
* broadcast when the message is downloaded, or the download is failed
*/
- void downloadMessage(String callingPkg, String locationUrl, in PendingIntent downloadedIntent);
+ void downloadMessage(long subId, String callingPkg, String locationUrl,
+ in PendingIntent downloadedIntent);
/**
* Update the status of a pending (send-by-IP) MMS message handled by the carrier app.
@@ -65,4 +70,149 @@
* will be downloaded via carrier network
*/
void updateMmsDownloadStatus(int messageRef, in byte[] pdu);
+
+ /**
+ * Get carrier-dependent configuration value as boolean. For example, if multipart SMS
+ * is supported.
+ *
+ * @param name the configuration name
+ * @param defaultValue the default value if fail to find the name
+ */
+ boolean getCarrierConfigBoolean(String name, boolean defaultValue);
+
+ /**
+ * Get carrier-dependent configuration value as int. For example, the MMS message size limit.
+ *
+ * @param name the configuration name
+ * @param defaultValue the default value if fail to find the name
+ */
+ int getCarrierConfigInt(String name, int defaultValue);
+
+ /**
+ * Get carrier-dependent configuration value as String. For example, extra HTTP headers for
+ * MMS request.
+ *
+ * @param name the configuration name
+ * @param defaultValue the default value if fail to find the name
+ */
+ String getCarrierConfigString(String name, String defaultValue);
+
+ /**
+ * Set carrier-dependent configuration value as boolean. For example, if multipart SMS
+ * is supported.
+ *
+ * @param name the configuration name
+ * @param value the configuration value
+ */
+ void setCarrierConfigBoolean(String callingPkg, String name, boolean value);
+
+ /**
+ * Set carrier-dependent configuration value as int. For example, the MMS message size limit.
+ *
+ * @param name the configuration name
+ * @param value the configuration value
+ */
+ void setCarrierConfigInt(String callingPkg, String name, int value);
+
+ /**
+ * Set carrier-dependent configuration value as String. For example, extra HTTP headers for
+ * MMS request.
+ *
+ * @param name the configuration name
+ * @param value the configuration value
+ */
+ void setCarrierConfigString(String callingPkg, String name, String value);
+
+ /**
+ * Import a text message into system's SMS store
+ *
+ * @param callingPkg the calling app's package name
+ * @param address the destination address of the message
+ * @param type the type of the message
+ * @param text the message text
+ * @param timestampMillis the message timestamp in milliseconds
+ * @param seen if the message is seen
+ * @param read if the message is read
+ * @return the message URI, null if failed
+ */
+ Uri importTextMessage(String callingPkg, String address, int type, String text,
+ long timestampMillis, boolean seen, boolean read);
+
+ /**
+ * Import a multimedia message into system's MMS store
+ *
+ * @param callingPkg the package name of the calling app
+ * @param pdu the PDU of the message to import
+ * @param messageId the optional message id
+ * @param timestampSecs the message timestamp in seconds
+ * @param seen if the message is seen
+ * @param read if the message is read
+ * @return the message URI, null if failed
+ */
+ Uri importMultimediaMessage(String callingPkg, in byte[] pdu, String messageId,
+ long timestampSecs, boolean seen, boolean read);
+
+ /**
+ * Delete a system stored SMS or MMS message
+ *
+ * @param callingPkg the package name of the calling app
+ * @param messageUri the URI of the stored message
+ * @return true if deletion is successful, false otherwise
+ */
+ boolean deleteStoredMessage(String callingPkg, in Uri messageUri);
+
+ /**
+ * Delete a system stored SMS or MMS thread
+ *
+ * @param callingPkg the package name of the calling app
+ * @param conversationId the ID of the message conversation
+ * @return true if deletion is successful, false otherwise
+ */
+ boolean deleteStoredConversation(String callingPkg, long conversationId);
+
+ /**
+ * Update the status properties of a system stored SMS or MMS message, e.g.
+ * the read status of a message, etc.
+ *
+ * @param callingPkg the package name of the calling app
+ * @param messageUri the URI of the stored message
+ * @param statusValues a list of status properties in key-value pairs to update
+ * @return true if deletion is successful, false otherwise
+ */
+ boolean updateStoredMessageStatus(String callingPkg, in Uri messageUri,
+ in ContentValues statusValues);
+
+ /**
+ * Add a text message draft to system SMS store
+ *
+ * @param callingPkg the package name of the calling app
+ * @param address the destination address of message
+ * @param text the body of the message to send
+ * @return the URI of the stored draft message
+ */
+ Uri addTextMessageDraft(String callingPkg, String address, String text);
+
+ /**
+ * Add a multimedia message draft to system MMS store
+ *
+ * @param callingPkg the package name of the calling app
+ * @param pdu the PDU data of the draft MMS
+ * @return the URI of the stored draft message
+ */
+ Uri addMultimediaMessageDraft(String callingPkg, in byte[] pdu);
+
+ /**
+ * Send a system stored MMS message
+ *
+ * This is used for sending a previously sent, but failed-to-send, message or
+ * for sending a text message that has been stored as a draft.
+ *
+ * @param subId the SIM id
+ * @param callingPkg the package name of the calling app
+ * @param messageUri the URI of the stored message
+ * @param sentIntent if not NULL this <code>PendingIntent</code> is
+ * broadcast when the message is successfully sent, or failed
+ */
+ void sendStoredMessage(long subId, String callingPkg, in Uri messageUri,
+ in PendingIntent sentIntent);
}
diff --git a/telephony/java/com/android/internal/telephony/ISms.aidl b/telephony/java/com/android/internal/telephony/ISms.aidl
index 53429b6..abbdc4a 100644
--- a/telephony/java/com/android/internal/telephony/ISms.aidl
+++ b/telephony/java/com/android/internal/telephony/ISms.aidl
@@ -17,6 +17,7 @@
package com.android.internal.telephony;
import android.app.PendingIntent;
+import android.net.Uri;
import com.android.internal.telephony.SmsRawData;
/** Interface for applications to access the ICC phone book.
@@ -466,7 +467,6 @@
*/
long getPreferredSmsSubscription();
-
/**
* Gets SMS format supported on IMS. SMS over IMS format is
* either 3GPP or 3GPP2.
@@ -491,12 +491,76 @@
*/
String getImsSmsFormatUsingSubId(long subId);
-
-
-
/*
* Get SMS prompt property, enabled or not
* @return true if enabled, false otherwise
*/
boolean isSMSPromptEnabled();
+
+ /**
+ * Send a system stored text message.
+ *
+ * This is used for sending a previously sent, but failed-to-send, message or
+ * for sending a text message that has been stored as a draft.
+ *
+ * @param subId the SIM id.
+ * @param callingPkg the package name of the calling app
+ * @param messageUri the URI of the stored message
+ * @param scAddress is the service center address or null to use the current default SMSC
+ * @param sentIntent if not NULL this <code>PendingIntent</code> is
+ * broadcast when the message is successfully sent, or failed.
+ * The result code will be <code>Activity.RESULT_OK</code> for success,
+ * or one of these errors:<br>
+ * <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
+ * <code>RESULT_ERROR_RADIO_OFF</code><br>
+ * <code>RESULT_ERROR_NULL_PDU</code><br>
+ * For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
+ * the extra "errorCode" containing a radio technology specific value,
+ * generally only useful for troubleshooting.<br>
+ * The per-application based SMS control checks sentIntent. If sentIntent
+ * is NULL the caller will be checked against all unknown applications,
+ * which cause smaller number of SMS to be sent in checking period.
+ * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
+ * broadcast when the message is delivered to the recipient. The
+ * raw pdu of the status report is in the extended data ("pdu").
+ */
+ void sendStoredText(long subId, String callingPkg, in Uri messageUri, String scAddress,
+ in PendingIntent sentIntent, in PendingIntent deliveryIntent);
+
+ /**
+ * Send a system stored multi-part text message.
+ *
+ * This is used for sending a previously sent, but failed-to-send, message or
+ * for sending a text message that has been stored as a draft.
+ * The provided <code>PendingIntent</code> lists should match the part number of the
+ * divided text of the stored message by using <code>divideMessage</code>
+ *
+ * @param subId the SIM id.
+ * @param callingPkg the package name of the calling app
+ * @param messageUri the URI of the stored message
+ * @param scAddress is the service center address or null to use
+ * the current default SMSC
+ * @param sentIntents if not null, an <code>ArrayList</code> of
+ * <code>PendingIntent</code>s (one for each message part) that is
+ * broadcast when the corresponding message part has been sent.
+ * The result code will be <code>Activity.RESULT_OK</code> for success,
+ * or one of these errors:<br>
+ * <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
+ * <code>RESULT_ERROR_RADIO_OFF</code><br>
+ * <code>RESULT_ERROR_NULL_PDU</code><br>
+ * For <code>RESULT_ERROR_GENERIC_FAILURE</code> each sentIntent may include
+ * the extra "errorCode" containing a radio technology specific value,
+ * generally only useful for troubleshooting.<br>
+ * The per-application based SMS control checks sentIntent. If sentIntent
+ * is NULL the caller will be checked against all unknown applications,
+ * which cause smaller number of SMS to be sent in checking period.
+ * @param deliveryIntents if not null, an <code>ArrayList</code> of
+ * <code>PendingIntent</code>s (one for each message part) that is
+ * broadcast when the corresponding message part has been delivered
+ * to the recipient. The raw pdu of the status report is in the
+ * extended data ("pdu").
+ */
+ void sendStoredMultipartText(long subId, String callingPkg, in Uri messageUri,
+ String scAddress, in List<PendingIntent> sentIntents,
+ in List<PendingIntent> deliveryIntents);
}