Merge "Fix 7.1 audio playback from AudioTrack" into lmp-dev
diff --git a/api/current.txt b/api/current.txt
index 2a3c086..5778c7e 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -8526,6 +8526,15 @@
field public int reqTouchScreen;
}
+ public final class FeatureGroupInfo implements android.os.Parcelable {
+ ctor public FeatureGroupInfo();
+ ctor public FeatureGroupInfo(android.content.pm.FeatureGroupInfo);
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator CREATOR;
+ field public android.content.pm.FeatureInfo[] features;
+ }
+
public class FeatureInfo implements android.os.Parcelable {
ctor public FeatureInfo();
ctor public FeatureInfo(android.content.pm.FeatureInfo);
@@ -8540,36 +8549,6 @@
field public int reqGlEsVersion;
}
- public class InstallSessionInfo implements android.os.Parcelable {
- method public int describeContents();
- method public android.graphics.Bitmap getAppIcon();
- method public java.lang.CharSequence getAppLabel();
- method public java.lang.String getAppPackageName();
- method public android.content.Intent getDetailsIntent();
- method public java.lang.String getInstallerPackageName();
- method public float getProgress();
- method public int getSessionId();
- method public boolean isOpen();
- method public void writeToParcel(android.os.Parcel, int);
- field public static final android.os.Parcelable.Creator CREATOR;
- }
-
- public class InstallSessionParams implements android.os.Parcelable {
- ctor public InstallSessionParams(int);
- method public int describeContents();
- method public void setAppIcon(android.graphics.Bitmap);
- method public void setAppLabel(java.lang.CharSequence);
- method public void setAppPackageName(java.lang.String);
- method public void setInstallLocation(int);
- method public void setOriginatingUri(android.net.Uri);
- method public void setReferrerUri(android.net.Uri);
- method public void setSize(long);
- method public void writeToParcel(android.os.Parcel, int);
- field public static final android.os.Parcelable.Creator CREATOR;
- field public static final int MODE_FULL_INSTALL = 1; // 0x1
- field public static final int MODE_INHERIT_EXISTING = 2; // 0x2
- }
-
public class InstrumentationInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable {
ctor public InstrumentationInfo();
ctor public InstrumentationInfo(android.content.pm.InstrumentationInfo);
@@ -8647,6 +8626,7 @@
field public android.content.pm.ActivityInfo[] activities;
field public android.content.pm.ApplicationInfo applicationInfo;
field public android.content.pm.ConfigurationInfo[] configPreferences;
+ field public android.content.pm.FeatureGroupInfo[] featureGroups;
field public long firstInstallTime;
field public int[] gids;
field public int installLocation;
@@ -8671,37 +8651,35 @@
public class PackageInstaller {
method public void addSessionCallback(android.content.pm.PackageInstaller.SessionCallback);
method public void addSessionCallback(android.content.pm.PackageInstaller.SessionCallback, android.os.Handler);
- method public int createSession(android.content.pm.InstallSessionParams) throws java.io.IOException;
- method public java.util.List<android.content.pm.InstallSessionInfo> getAllSessions();
- method public java.util.List<android.content.pm.InstallSessionInfo> getMySessions();
- method public android.content.pm.InstallSessionInfo getSessionInfo(int);
+ method public int createSession(android.content.pm.PackageInstaller.SessionParams) throws java.io.IOException;
+ method public java.util.List<android.content.pm.PackageInstaller.SessionInfo> getAllSessions();
+ method public java.util.List<android.content.pm.PackageInstaller.SessionInfo> getMySessions();
+ method public android.content.pm.PackageInstaller.SessionInfo getSessionInfo(int);
method public android.content.pm.PackageInstaller.Session openSession(int);
method public void removeSessionCallback(android.content.pm.PackageInstaller.SessionCallback);
- method public void uninstall(java.lang.String, android.content.pm.PackageInstaller.UninstallCallback);
+ method public void uninstall(java.lang.String, android.content.IntentSender);
field public static final java.lang.String ACTION_SESSION_DETAILS = "android.content.pm.action.SESSION_DETAILS";
+ field public static final java.lang.String EXTRA_PACKAGE_NAMES = "android.content.pm.extra.PACKAGE_NAMES";
field public static final java.lang.String EXTRA_SESSION_ID = "android.content.pm.extra.SESSION_ID";
- }
-
- public static abstract class PackageInstaller.CommitCallback {
- ctor public PackageInstaller.CommitCallback();
- method public abstract void onFailure(int, java.lang.String, android.os.Bundle);
- method public abstract void onSuccess();
- method public abstract void onUserActionRequired(android.content.Intent);
- field public static final java.lang.String EXTRA_PACKAGE_NAME = "android.content.pm.extra.PACKAGE_NAME";
- field public static final int FAILURE_ABORTED = 5; // 0x5
- field public static final int FAILURE_CONFLICT = 2; // 0x2
- field public static final int FAILURE_INCOMPATIBLE = 4; // 0x4
- field public static final int FAILURE_INVALID = 1; // 0x1
- field public static final int FAILURE_STORAGE = 3; // 0x3
- field public static final int FAILURE_UNKNOWN = 0; // 0x0
+ field public static final java.lang.String EXTRA_STATUS = "android.content.pm.extra.STATUS";
+ field public static final java.lang.String EXTRA_STATUS_MESSAGE = "android.content.pm.extra.STATUS_MESSAGE";
+ field public static final int STATUS_FAILURE = 1; // 0x1
+ field public static final int STATUS_FAILURE_ABORTED = 2; // 0x2
+ field public static final int STATUS_FAILURE_BLOCKED = 1; // 0x1
+ field public static final int STATUS_FAILURE_CONFLICT = 4; // 0x4
+ field public static final int STATUS_FAILURE_INCOMPATIBLE = 6; // 0x6
+ field public static final int STATUS_FAILURE_INVALID = 3; // 0x3
+ field public static final int STATUS_FAILURE_STORAGE = 5; // 0x5
+ field public static final int STATUS_SUCCESS = 0; // 0x0
+ field public static final int STATUS_USER_ACTION_REQUIRED = -1; // 0xffffffff
}
public static class PackageInstaller.Session implements java.io.Closeable {
method public void abandon();
method public void close();
- method public void commit(android.content.pm.PackageInstaller.CommitCallback);
+ method public void commit(android.content.IntentSender);
method public void fsync(java.io.OutputStream) throws java.io.IOException;
- method public java.lang.String[] list();
+ method public java.lang.String[] getNames();
method public java.io.InputStream openRead(java.lang.String) throws java.io.IOException;
method public java.io.OutputStream openWrite(java.lang.String, long, long) throws java.io.IOException;
method public void setProgress(float);
@@ -8716,14 +8694,34 @@
method public abstract void onProgressChanged(int, float);
}
- public static abstract class PackageInstaller.UninstallCallback {
- ctor public PackageInstaller.UninstallCallback();
- method public abstract void onFailure(int, java.lang.String, android.os.Bundle);
- method public abstract void onSuccess();
- method public abstract void onUserActionRequired(android.content.Intent);
- field public static final int FAILURE_ABORTED = 2; // 0x2
- field public static final int FAILURE_BLOCKED = 1; // 0x1
- field public static final int FAILURE_UNKNOWN = 0; // 0x0
+ public static class PackageInstaller.SessionInfo implements android.os.Parcelable {
+ method public int describeContents();
+ method public android.graphics.Bitmap getAppIcon();
+ method public java.lang.CharSequence getAppLabel();
+ method public java.lang.String getAppPackageName();
+ method public android.content.Intent getDetailsIntent();
+ method public java.lang.String getInstallerPackageName();
+ method public float getProgress();
+ method public int getSessionId();
+ method public boolean isOpen();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator CREATOR;
+ }
+
+ public static class PackageInstaller.SessionParams implements android.os.Parcelable {
+ ctor public PackageInstaller.SessionParams(int);
+ method public int describeContents();
+ method public void setAppIcon(android.graphics.Bitmap);
+ method public void setAppLabel(java.lang.CharSequence);
+ method public void setAppPackageName(java.lang.String);
+ method public void setInstallLocation(int);
+ method public void setOriginatingUri(android.net.Uri);
+ method public void setReferrerUri(android.net.Uri);
+ method public void setSize(long);
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator CREATOR;
+ field public static final int MODE_FULL_INSTALL = 1; // 0x1
+ field public static final int MODE_INHERIT_EXISTING = 2; // 0x2
}
public class PackageItemInfo {
@@ -11654,7 +11652,7 @@
public class AnimatedStateListDrawable extends android.graphics.drawable.StateListDrawable {
ctor public AnimatedStateListDrawable();
method public void addState(int[], android.graphics.drawable.Drawable, int);
- method public void addTransition(int, int, android.graphics.drawable.Drawable, boolean);
+ method public void addTransition(int, int, T, boolean);
}
public class AnimatedVectorDrawable extends android.graphics.drawable.Drawable implements android.graphics.drawable.Animatable {
@@ -23026,9 +23024,9 @@
method public boolean isUserRunning(android.os.UserHandle);
method public boolean isUserRunningOrStopping(android.os.UserHandle);
method public boolean setRestrictionsChallenge(java.lang.String);
- method public void setUserRestriction(java.lang.String, boolean);
- method public void setUserRestrictions(android.os.Bundle);
- method public void setUserRestrictions(android.os.Bundle, android.os.UserHandle);
+ method public deprecated void setUserRestriction(java.lang.String, boolean);
+ method public deprecated void setUserRestrictions(android.os.Bundle);
+ method public deprecated void setUserRestrictions(android.os.Bundle, android.os.UserHandle);
field public static final java.lang.String DISALLOW_ADD_USER = "no_add_user";
field public static final java.lang.String DISALLOW_ADJUST_VOLUME = "no_adjust_volume";
field public static final java.lang.String DISALLOW_APPS_CONTROL = "no_control_apps";
diff --git a/cmds/media/src/com/android/commands/media/Media.java b/cmds/media/src/com/android/commands/media/Media.java
index b13b009..b37f896 100644
--- a/cmds/media/src/com/android/commands/media/Media.java
+++ b/cmds/media/src/com/android/commands/media/Media.java
@@ -225,7 +225,7 @@
void printUsageMessage() {
try {
System.out.println("V2Monitoring session " + mController.getTag()
- + "... available commands:");
+ + "... available commands: play, pause, next, previous");
} catch (RemoteException e) {
System.out.println("Error trying to monitor session!");
}
@@ -257,6 +257,14 @@
addNewline = false;
} else if ("q".equals(line) || "quit".equals(line)) {
break;
+ } else if ("play".equals(line)) {
+ mController.play();
+ } else if ("pause".equals(line)) {
+ mController.pause();
+ } else if ("next".equals(line)) {
+ mController.next();
+ } else if ("previous".equals(line)) {
+ mController.previous();
} else {
System.out.println("Invalid command: " + line);
}
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index 15152e5..46d8ade 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -19,21 +19,22 @@
import android.app.ActivityManager;
import android.app.ActivityManagerNative;
import android.app.IActivityManager;
-import android.app.PackageDeleteObserver;
import android.app.PackageInstallObserver;
import android.content.ComponentName;
+import android.content.IIntentReceiver;
+import android.content.IIntentSender;
import android.content.Intent;
+import android.content.IntentSender;
import android.content.pm.ApplicationInfo;
import android.content.pm.FeatureInfo;
import android.content.pm.IPackageDataObserver;
import android.content.pm.IPackageInstaller;
import android.content.pm.IPackageManager;
-import android.content.pm.InstallSessionInfo;
-import android.content.pm.InstallSessionParams;
import android.content.pm.InstrumentationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
-import android.content.pm.PackageInstaller.CommitCallback;
+import android.content.pm.PackageInstaller.SessionInfo;
+import android.content.pm.PackageInstaller.SessionParams;
import android.content.pm.PackageItemInfo;
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
@@ -74,6 +75,8 @@
import java.util.Comparator;
import java.util.List;
import java.util.WeakHashMap;
+import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.TimeUnit;
public final class Pm {
private static final String TAG = "Pm";
@@ -776,36 +779,6 @@
}
}
- class LocalCommitCallback extends CommitCallback {
- 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 onUserActionRequired(Intent intent) {
- setResult(false, "Unexepected user action required!");
- }
-
- @Override
- public void onSuccess() {
- setResult(true, null);
- }
-
- @Override
- public void onFailure(int failureReason, String msg, Bundle extras) {
- setResult(false, msg);
- }
- }
-
/**
* Converts a failure code into a string by using reflection to find a matching constant
* in PackageManager.
@@ -1007,8 +980,7 @@
private void runInstallCreate() throws RemoteException {
String installerPackageName = null;
- final InstallSessionParams params = new InstallSessionParams(
- InstallSessionParams.MODE_FULL_INSTALL);
+ final SessionParams params = new SessionParams(SessionParams.MODE_FULL_INSTALL);
params.installFlags = PackageManager.INSTALL_ALL_USERS;
String opt;
@@ -1031,7 +1003,7 @@
} else if (opt.equals("-d")) {
params.installFlags |= PackageManager.INSTALL_ALLOW_DOWNGRADE;
} else if (opt.equals("-p")) {
- params.mode = InstallSessionParams.MODE_INHERIT_EXISTING;
+ params.mode = SessionParams.MODE_INHERIT_EXISTING;
} else if (opt.equals("-S")) {
params.setSize(Long.parseLong(nextOptionData()));
} else if (opt.equals("--abi")) {
@@ -1073,7 +1045,7 @@
}
}
- final InstallSessionInfo info = mInstaller.getSessionInfo(sessionId);
+ final SessionInfo info = mInstaller.getSessionInfo(sessionId);
PackageInstaller.Session session = null;
InputStream in = null;
@@ -1117,22 +1089,20 @@
try {
session = new PackageInstaller.Session(mInstaller.openSession(sessionId));
- final LocalCommitCallback callback = new LocalCommitCallback();
- session.commit(callback);
+ final LocalIntentReceiver receiver = new LocalIntentReceiver();
+ session.commit(receiver.getIntentSender());
- synchronized (callback) {
- while (!callback.finished) {
- try {
- callback.wait();
- } catch (InterruptedException e) {
- }
- }
- if (!callback.success) {
- throw new IllegalStateException("Failure [" + callback.msg + "]");
- }
+ final Intent result = receiver.getResult();
+ final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
+ PackageInstaller.STATUS_FAILURE);
+ if (status == PackageInstaller.STATUS_SUCCESS) {
+ System.out.println("Success");
+ } else {
+ Log.e(TAG, "Failure details: " + result.getExtras());
+ System.out.println("Failure ["
+ + result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) + "]");
+ return;
}
-
- System.out.println("Success");
} finally {
IoUtils.closeQuietly(session);
}
@@ -1274,28 +1244,14 @@
}
}
- class LocalPackageDeleteObserver extends PackageDeleteObserver {
- boolean finished;
- boolean result;
-
- @Override
- public void onPackageDeleted(String name, int returnCode, String msg) {
- synchronized (this) {
- finished = true;
- result = returnCode == PackageManager.DELETE_SUCCEEDED;
- notifyAll();
- }
- }
- }
-
- private void runUninstall() {
- int unInstallFlags = 0;
+ private void runUninstall() throws RemoteException {
+ int flags = 0;
int userId = UserHandle.USER_ALL;
String opt;
while ((opt=nextOption()) != null) {
if (opt.equals("-k")) {
- unInstallFlags |= PackageManager.DELETE_KEEP_DATA;
+ flags |= PackageManager.DELETE_KEEP_DATA;
} else if (opt.equals("--user")) {
String param = nextArg();
if (isNumber(param)) {
@@ -1320,7 +1276,7 @@
if (userId == UserHandle.USER_ALL) {
userId = UserHandle.USER_OWNER;
- unInstallFlags |= PackageManager.DELETE_ALL_USERS;
+ flags |= PackageManager.DELETE_ALL_USERS;
} else {
PackageInfo info;
try {
@@ -1340,38 +1296,25 @@
// user set flag so it disables rather than reverting to system
// version of the app.
if (isSystem) {
- unInstallFlags |= PackageManager.DELETE_SYSTEM_APP;
+ flags |= PackageManager.DELETE_SYSTEM_APP;
}
}
- boolean result = deletePackage(pkg, unInstallFlags, userId);
- if (result) {
+ final LocalIntentReceiver receiver = new LocalIntentReceiver();
+ mInstaller.uninstall(pkg, flags, receiver.getIntentSender(), userId);
+
+ final Intent result = receiver.getResult();
+ final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
+ PackageInstaller.STATUS_FAILURE);
+ if (status == PackageInstaller.STATUS_SUCCESS) {
System.out.println("Success");
} else {
- System.out.println("Failure");
+ Log.e(TAG, "Failure details: " + result.getExtras());
+ System.out.println("Failure ["
+ + result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) + "]");
}
}
- private boolean deletePackage(String packageName, int flags, int userId) {
- LocalPackageDeleteObserver obs = new LocalPackageDeleteObserver();
- try {
- mInstaller.uninstall(packageName, flags, obs.getBinder(), userId);
-
- synchronized (obs) {
- while (!obs.finished) {
- try {
- obs.wait();
- } catch (InterruptedException e) {
- }
- }
- }
- } catch (RemoteException e) {
- System.err.println(e.toString());
- System.err.println(PM_NOT_RUNNING_ERR);
- }
- return obs.result;
- }
-
static class ClearDataObserver extends IPackageDataObserver.Stub {
boolean finished;
boolean result;
@@ -1384,7 +1327,6 @@
notifyAll();
}
}
-
}
private void runClear() {
@@ -1717,6 +1659,35 @@
throw new IllegalArgumentException("ABI " + abi + " not supported on this device");
}
+ private static class LocalIntentReceiver {
+ private final SynchronousQueue<Intent> mResult = new SynchronousQueue<>();
+
+ private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
+ @Override
+ public int send(int code, Intent intent, String resolvedType,
+ IIntentReceiver finishedReceiver, String requiredPermission) {
+ try {
+ mResult.offer(intent, 5, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ return 0;
+ }
+ };
+
+ public IntentSender getIntentSender() {
+ return new IntentSender((IIntentSender) mLocalSender);
+ }
+
+ public Intent getResult() {
+ try {
+ return mResult.poll(30, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
private String nextOption() {
if (mNextArg >= mArgs.length) {
return null;
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index fed7ae3..d9ea671 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -1811,6 +1811,10 @@
synchronized (this) {
getSystemContext().installSystemApplicationInfo(info, classLoader);
+ // The code package for "android" in the system server needs
+ // to be the system context's package.
+ mPackages.put("android", new WeakReference<LoadedApk>(getSystemContext().mPackageInfo));
+
// give ourselves a default profiler
mProfiler = new Profiler();
}
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index a935dc0..b2812e3 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -1568,7 +1568,7 @@
synchronized (mLock) {
if (mInstaller == null) {
try {
- mInstaller = new PackageInstaller(this, mPM.getPackageInstaller(),
+ mInstaller = new PackageInstaller(mContext, this, mPM.getPackageInstaller(),
mContext.getPackageName(), mContext.getUserId());
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index fcfc1c49..e0c7816 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -40,6 +40,7 @@
import android.os.Trace;
import android.os.UserHandle;
import android.util.AndroidRuntimeException;
+import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import android.view.DisplayAdjustments;
@@ -643,8 +644,17 @@
}
private void rewriteRValues(ClassLoader cl, String packageName, int id) {
+ final Class<?> rClazz;
try {
- final Class<?> rClazz = cl.loadClass(packageName + ".R");
+ rClazz = cl.loadClass(packageName + ".R");
+ } catch (ClassNotFoundException e) {
+ // This is not necessarily an error, as some packages do not ship with resources
+ // (or they do not need rewriting).
+ Log.i(TAG, "Could not find R class for package '" + packageName + "'");
+ return;
+ }
+
+ try {
Class<?>[] declaredClasses = rClazz.getDeclaredClasses();
for (Class<?> clazz : declaredClasses) {
try {
diff --git a/core/java/android/app/backup/BackupManager.java b/core/java/android/app/backup/BackupManager.java
index e9297b9..1bb4eba 100644
--- a/core/java/android/app/backup/BackupManager.java
+++ b/core/java/android/app/backup/BackupManager.java
@@ -229,6 +229,28 @@
}
/**
+ * Enable/disable data restore at application install time. When enabled, app
+ * installation will include an attempt to fetch the app's historical data from
+ * the archival restore dataset (if any). When disabled, no such attempt will
+ * be made.
+ *
+ * <p>Callers must hold the android.permission.BACKUP permission to use this method.
+ *
+ * @hide
+ */
+ @SystemApi
+ public void setAutoRestore(boolean isEnabled) {
+ checkServiceBinder();
+ if (sService != null) {
+ try {
+ sService.setAutoRestore(isEnabled);
+ } catch (RemoteException e) {
+ Log.e(TAG, "setAutoRestore() couldn't connect");
+ }
+ }
+ }
+
+ /**
* Identify the currently selected transport. Callers must hold the
* android.permission.BACKUP permission to use this method.
* @return The name of the currently active backup transport. In case of
diff --git a/core/java/android/appwidget/AppWidgetProviderInfo.java b/core/java/android/appwidget/AppWidgetProviderInfo.java
index a767246..0b40e35 100644
--- a/core/java/android/appwidget/AppWidgetProviderInfo.java
+++ b/core/java/android/appwidget/AppWidgetProviderInfo.java
@@ -271,13 +271,13 @@
* @return The provider icon.
*/
public final Drawable loadIcon(@NonNull Context context, int density) {
- return loadDrawable(context, density, providerInfo.getIconResource());
+ return loadDrawable(context, density, providerInfo.getIconResource(), true);
}
/**
* Loads a preview of what the AppWidget will look like after it's configured.
- * If not supplied, the AppWidget's icon will be used. A client can optionally
- * provide a desired deinsity such as {@link android.util.DisplayMetrics#DENSITY_LOW}
+ * A client can optionally provide a desired density such as
+ * {@link android.util.DisplayMetrics#DENSITY_LOW}
* {@link android.util.DisplayMetrics#DENSITY_MEDIUM}, etc. If no density is
* provided, the density of the current display will be used.
* <p>
@@ -288,10 +288,10 @@
* @param context Context for accessing resources.
* @param density The optional desired density as per
* {@link android.util.DisplayMetrics#densityDpi}.
- * @return The widget preview image.
+ * @return The widget preview image or {@null} if preview image is not available.
*/
public final Drawable loadPreviewImage(@NonNull Context context, int density) {
- return loadDrawable(context, density, previewImage);
+ return loadDrawable(context, density, previewImage, false);
}
/**
@@ -361,7 +361,8 @@
return 0;
}
- private Drawable loadDrawable(Context context, int density, int resourceId) {
+ private Drawable loadDrawable(Context context, int density, int resourceId,
+ boolean loadDefaultIcon) {
try {
Resources resources = context.getPackageManager().getResourcesForApplication(
providerInfo.applicationInfo);
@@ -374,7 +375,7 @@
} catch (PackageManager.NameNotFoundException | Resources.NotFoundException e) {
/* ignore */
}
- return providerInfo.loadIcon(context.getPackageManager());
+ return loadDefaultIcon ? providerInfo.loadIcon(context.getPackageManager()) : null;
}
/**
diff --git a/core/java/android/content/pm/FeatureGroupInfo.java b/core/java/android/content/pm/FeatureGroupInfo.java
new file mode 100644
index 0000000..79a6eea
--- /dev/null
+++ b/core/java/android/content/pm/FeatureGroupInfo.java
@@ -0,0 +1,65 @@
+/**
+ * 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.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A set of features that can be requested by an application. This corresponds
+ * to information collected from the
+ * AndroidManifest.xml's {@code <feature-group>} tag.
+ */
+public final class FeatureGroupInfo implements Parcelable {
+
+ /**
+ * The list of features that are required by this group.
+ *
+ * @see FeatureInfo#FLAG_REQUIRED
+ */
+ public FeatureInfo[] features;
+
+ public FeatureGroupInfo() {
+ }
+
+ public FeatureGroupInfo(FeatureGroupInfo other) {
+ features = other.features;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeTypedArray(features, flags);
+ }
+
+ public static final Creator<FeatureGroupInfo> CREATOR = new Creator<FeatureGroupInfo>() {
+ @Override
+ public FeatureGroupInfo createFromParcel(Parcel source) {
+ FeatureGroupInfo group = new FeatureGroupInfo();
+ group.features = source.createTypedArray(FeatureInfo.CREATOR);
+ return group;
+ }
+
+ @Override
+ public FeatureGroupInfo[] newArray(int size) {
+ return new FeatureGroupInfo[size];
+ }
+ };
+}
diff --git a/core/java/android/content/pm/FeatureInfo.java b/core/java/android/content/pm/FeatureInfo.java
index d919fc3..79fa327 100644
--- a/core/java/android/content/pm/FeatureInfo.java
+++ b/core/java/android/content/pm/FeatureInfo.java
@@ -22,7 +22,7 @@
/**
* A single feature that can be requested by an application. This corresponds
* to information collected from the
- * AndroidManifest.xml's <uses-feature> tag.
+ * AndroidManifest.xml's {@code <uses-feature>} tag.
*/
public class FeatureInfo implements Parcelable {
/**
diff --git a/core/java/android/content/pm/IPackageInstaller.aidl b/core/java/android/content/pm/IPackageInstaller.aidl
index 5223476..97be8f0 100644
--- a/core/java/android/content/pm/IPackageInstaller.aidl
+++ b/core/java/android/content/pm/IPackageInstaller.aidl
@@ -19,23 +19,22 @@
import android.content.pm.IPackageDeleteObserver2;
import android.content.pm.IPackageInstallerCallback;
import android.content.pm.IPackageInstallerSession;
-import android.content.pm.InstallSessionInfo;
-import android.content.pm.InstallSessionParams;
+import android.content.pm.PackageInstaller;
+import android.content.IntentSender;
/** {@hide} */
interface IPackageInstaller {
- int createSession(in InstallSessionParams params, String installerPackageName, int userId);
+ int createSession(in PackageInstaller.SessionParams params, String installerPackageName, int userId);
IPackageInstallerSession openSession(int sessionId);
- InstallSessionInfo getSessionInfo(int sessionId);
- List<InstallSessionInfo> getAllSessions(int userId);
- List<InstallSessionInfo> getMySessions(String installerPackageName, int userId);
+ PackageInstaller.SessionInfo getSessionInfo(int sessionId);
+ List<PackageInstaller.SessionInfo> getAllSessions(int userId);
+ List<PackageInstaller.SessionInfo> getMySessions(String installerPackageName, int userId);
void registerCallback(IPackageInstallerCallback callback, int userId);
void unregisterCallback(IPackageInstallerCallback callback);
- void uninstall(String packageName, int flags, in IPackageDeleteObserver2 observer, int userId);
- void uninstallSplit(String packageName, String splitName, int flags, in IPackageDeleteObserver2 observer, int userId);
+ void uninstall(String packageName, int flags, in IntentSender statusReceiver, int userId);
void setPermissionsResult(int sessionId, boolean accepted);
}
diff --git a/core/java/android/content/pm/IPackageInstallerSession.aidl b/core/java/android/content/pm/IPackageInstallerSession.aidl
index af0323f..aee3ba7 100644
--- a/core/java/android/content/pm/IPackageInstallerSession.aidl
+++ b/core/java/android/content/pm/IPackageInstallerSession.aidl
@@ -17,6 +17,7 @@
package android.content.pm;
import android.content.pm.IPackageInstallObserver2;
+import android.content.IntentSender;
import android.os.ParcelFileDescriptor;
/** {@hide} */
@@ -24,11 +25,11 @@
void setClientProgress(float progress);
void addClientProgress(float progress);
- String[] list();
+ String[] getNames();
ParcelFileDescriptor openWrite(String name, long offsetBytes, long lengthBytes);
ParcelFileDescriptor openRead(String name);
void close();
- void commit(in IPackageInstallObserver2 observer);
+ void commit(in IntentSender statusReceiver);
void abandon();
}
diff --git a/core/java/android/content/pm/InstallSessionInfo.aidl b/core/java/android/content/pm/InstallSessionInfo.aidl
deleted file mode 100644
index 3d21bbd..0000000
--- a/core/java/android/content/pm/InstallSessionInfo.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * 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
deleted file mode 100644
index 161bcde..0000000
--- a/core/java/android/content/pm/InstallSessionInfo.java
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * 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.annotation.Nullable;
-import android.content.Intent;
-import android.graphics.Bitmap;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-/**
- * Details for an active install session.
- */
-public class InstallSessionInfo implements Parcelable {
-
- /** {@hide} */
- public int sessionId;
- /** {@hide} */
- public String installerPackageName;
- /** {@hide} */
- public String resolvedBaseCodePath;
- /** {@hide} */
- public float progress;
- /** {@hide} */
- public boolean sealed;
- /** {@hide} */
- public boolean open;
-
- /** {@hide} */
- public int mode;
- /** {@hide} */
- public long sizeBytes;
- /** {@hide} */
- public String appPackageName;
- /** {@hide} */
- public Bitmap appIcon;
- /** {@hide} */
- public CharSequence appLabel;
-
- /** {@hide} */
- public InstallSessionInfo() {
- }
-
- /** {@hide} */
- public InstallSessionInfo(Parcel source) {
- sessionId = source.readInt();
- installerPackageName = source.readString();
- resolvedBaseCodePath = source.readString();
- progress = source.readFloat();
- sealed = source.readInt() != 0;
- open = source.readInt() != 0;
-
- mode = source.readInt();
- sizeBytes = source.readLong();
- appPackageName = source.readString();
- appIcon = source.readParcelable(null);
- appLabel = source.readString();
- }
-
- /**
- * Return the ID for this session.
- */
- public int getSessionId() {
- return sessionId;
- }
-
- /**
- * Return the package name of the app that owns this session.
- */
- public @Nullable String getInstallerPackageName() {
- return installerPackageName;
- }
-
- /**
- * Return current overall progress of this session, between 0 and 1.
- * <p>
- * Note that this progress may not directly correspond to the value reported
- * by {@link PackageInstaller.Session#setProgress(float)}, as the system may
- * carve out a portion of the overall progress to represent its own internal
- * installation work.
- */
- public float getProgress() {
- return progress;
- }
-
- /**
- * Return if this session is currently open.
- */
- public boolean isOpen() {
- return open;
- }
-
- /**
- * Return the package name this session is working with. May be {@code null}
- * if unknown.
- */
- public @Nullable String getAppPackageName() {
- return appPackageName;
- }
-
- /**
- * Return an icon representing the app being installed. May be {@code null}
- * if unavailable.
- */
- public @Nullable Bitmap getAppIcon() {
- return appIcon;
- }
-
- /**
- * Return a label representing the app being installed. May be {@code null}
- * if unavailable.
- */
- public @Nullable CharSequence getAppLabel() {
- return appLabel;
- }
-
- /**
- * Return an Intent that can be started to view details about this install
- * session. This may surface actions such as pause, resume, or cancel.
- * <p>
- * In some cases, a matching Activity may not exist, so ensure you safeguard
- * against this.
- *
- * @see PackageInstaller#ACTION_SESSION_DETAILS
- */
- public @Nullable Intent getDetailsIntent() {
- final Intent intent = new Intent(PackageInstaller.ACTION_SESSION_DETAILS);
- intent.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId);
- intent.setPackage(installerPackageName);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- return intent;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(sessionId);
- dest.writeString(installerPackageName);
- dest.writeString(resolvedBaseCodePath);
- dest.writeFloat(progress);
- dest.writeInt(sealed ? 1 : 0);
- dest.writeInt(open ? 1 : 0);
-
- dest.writeInt(mode);
- dest.writeLong(sizeBytes);
- dest.writeString(appPackageName);
- dest.writeParcelable(appIcon, flags);
- dest.writeString(appLabel != null ? appLabel.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
deleted file mode 100644
index 81b7574..0000000
--- a/core/java/android/content/pm/InstallSessionParams.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * 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/InstallSessionParams.java b/core/java/android/content/pm/InstallSessionParams.java
deleted file mode 100644
index 1716e39..0000000
--- a/core/java/android/content/pm/InstallSessionParams.java
+++ /dev/null
@@ -1,227 +0,0 @@
-/*
- * 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.annotation.Nullable;
-import android.app.ActivityManager;
-import android.content.Intent;
-import android.graphics.Bitmap;
-import android.net.Uri;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import com.android.internal.util.IndentingPrintWriter;
-
-/**
- * Parameters for creating a new {@link PackageInstaller.Session}.
- */
-public class InstallSessionParams implements Parcelable {
-
- /** {@hide} */
- public static final int MODE_INVALID = -1;
-
- /**
- * Mode for an install session whose staged APKs should fully replace any
- * existing APKs for the target app.
- */
- public static final int MODE_FULL_INSTALL = 1;
-
- /**
- * Mode for an install session that should inherit any existing APKs for the
- * target app, unless they have been explicitly overridden (based on split
- * name) by the session. For example, this can be used to add one or more
- * split APKs to an existing installation.
- * <p>
- * If there are no existing APKs for the target app, this behaves like
- * {@link #MODE_FULL_INSTALL}.
- */
- public static final int MODE_INHERIT_EXISTING = 2;
-
- /** {@hide} */
- public int mode = MODE_INVALID;
- /** {@hide} */
- public int installFlags;
- /** {@hide} */
- public int installLocation = PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY;
- /** {@hide} */
- public long sizeBytes = -1;
- /** {@hide} */
- public String appPackageName;
- /** {@hide} */
- public Bitmap appIcon;
- /** {@hide} */
- public String appLabel;
- /** {@hide} */
- public Uri originatingUri;
- /** {@hide} */
- public Uri referrerUri;
- /** {@hide} */
- public String abiOverride;
-
- /**
- * Construct parameters for a new package install session.
- *
- * @param mode one of {@link #MODE_FULL_INSTALL} or
- * {@link #MODE_INHERIT_EXISTING} describing how the session
- * should interact with an existing app.
- */
- public InstallSessionParams(int mode) {
- this.mode = mode;
- }
-
- /** {@hide} */
- public InstallSessionParams(Parcel source) {
- mode = source.readInt();
- installFlags = source.readInt();
- installLocation = source.readInt();
- sizeBytes = source.readLong();
- appPackageName = source.readString();
- appIcon = source.readParcelable(null);
- appLabel = source.readString();
- originatingUri = source.readParcelable(null);
- referrerUri = source.readParcelable(null);
- abiOverride = source.readString();
- }
-
- /**
- * Provide value of {@link PackageInfo#installLocation}, which may be used
- * to determine where the app will be staged. Defaults to
- * {@link PackageInfo#INSTALL_LOCATION_INTERNAL_ONLY}.
- */
- public void setInstallLocation(int installLocation) {
- this.installLocation = installLocation;
- }
-
- /**
- * @deprecated use {@link PackageInstaller.Session#openRead(String)} to
- * calculate message digest instead.
- * @hide
- */
- @Deprecated
- public void setSignatures(@Nullable Signature[] signatures) {
- throw new UnsupportedOperationException();
- }
-
- /**
- * Optionally indicate the total size (in bytes) of all APKs that will be
- * delivered in this session. The system may use this to ensure enough disk
- * space exists before proceeding, or to estimate container size for
- * installations living on external storage.
- *
- * @see PackageInfo#INSTALL_LOCATION_AUTO
- * @see PackageInfo#INSTALL_LOCATION_PREFER_EXTERNAL
- */
- public void setSize(long sizeBytes) {
- this.sizeBytes = sizeBytes;
- }
-
- /**
- * Optionally set the package name of the app being installed. It's strongly
- * recommended that you provide this value when known, so that observers can
- * communicate installing apps to users.
- * <p>
- * If the APKs staged in the session aren't consistent with this package
- * name, the install will fail. Regardless of this value, all APKs in the
- * app must have the same package name.
- */
- public void setAppPackageName(@Nullable String appPackageName) {
- this.appPackageName = appPackageName;
- }
-
- /**
- * Optionally set an icon representing the app being installed. This should
- * be roughly {@link ActivityManager#getLauncherLargeIconSize()} in both
- * dimensions.
- */
- public void setAppIcon(@Nullable Bitmap appIcon) {
- this.appIcon = appIcon;
- }
-
- /**
- * Optionally set a label representing the app being installed.
- */
- public void setAppLabel(@Nullable CharSequence appLabel) {
- this.appLabel = (appLabel != null) ? appLabel.toString() : null;
- }
-
- /**
- * Optionally set the URI where this package was downloaded from. Used for
- * verification purposes.
- *
- * @see Intent#EXTRA_ORIGINATING_URI
- */
- public void setOriginatingUri(@Nullable Uri originatingUri) {
- this.originatingUri = originatingUri;
- }
-
- /**
- * Optionally set the URI that referred you to install this package. Used
- * for verification purposes.
- *
- * @see Intent#EXTRA_REFERRER
- */
- public void setReferrerUri(@Nullable Uri referrerUri) {
- this.referrerUri = referrerUri;
- }
-
- /** {@hide} */
- public void dump(IndentingPrintWriter pw) {
- pw.printPair("mode", mode);
- pw.printHexPair("installFlags", installFlags);
- pw.printPair("installLocation", installLocation);
- pw.printPair("sizeBytes", sizeBytes);
- pw.printPair("appPackageName", appPackageName);
- pw.printPair("appIcon", (appIcon != null));
- pw.printPair("appLabel", appLabel);
- pw.printPair("originatingUri", originatingUri);
- pw.printPair("referrerUri", referrerUri);
- pw.printPair("abiOverride", abiOverride);
- pw.println();
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(mode);
- dest.writeInt(installFlags);
- dest.writeInt(installLocation);
- dest.writeLong(sizeBytes);
- dest.writeString(appPackageName);
- dest.writeParcelable(appIcon, flags);
- dest.writeString(appLabel);
- dest.writeParcelable(originatingUri, flags);
- dest.writeParcelable(referrerUri, flags);
- dest.writeString(abiOverride);
- }
-
- public static final Parcelable.Creator<InstallSessionParams>
- CREATOR = new Parcelable.Creator<InstallSessionParams>() {
- @Override
- public InstallSessionParams createFromParcel(Parcel p) {
- return new InstallSessionParams(p);
- }
-
- @Override
- public InstallSessionParams[] newArray(int size) {
- return new InstallSessionParams[size];
- }
- };
-}
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java
index 49ffef2..a0e3c4a 100644
--- a/core/java/android/content/pm/PackageInfo.java
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -180,7 +180,7 @@
* {@link android.R.styleable#AndroidManifestUsesConfiguration
* <uses-configuration>} tags included under <manifest>,
* or null if there were none. This is only filled in if the flag
- * {@link PackageManager#GET_CONFIGURATIONS} was set.
+ * {@link PackageManager#GET_CONFIGURATIONS} was set.
*/
public ConfigurationInfo[] configPreferences;
@@ -192,6 +192,16 @@
public FeatureInfo[] reqFeatures;
/**
+ * Groups of features that this application has requested.
+ * Each group contains a set of features that are required.
+ * A device must match the features listed in {@link #reqFeatures} and one
+ * or more FeatureGroups in order to have satisfied the feature requirement.
+ *
+ * @see FeatureInfo#FLAG_REQUIRED
+ */
+ public FeatureGroupInfo[] featureGroups;
+
+ /**
* Constant corresponding to <code>auto</code> in
* the {@link android.R.attr#installLocation} attribute.
* @hide
@@ -300,6 +310,7 @@
dest.writeTypedArray(signatures, parcelableFlags);
dest.writeTypedArray(configPreferences, parcelableFlags);
dest.writeTypedArray(reqFeatures, parcelableFlags);
+ dest.writeTypedArray(featureGroups, parcelableFlags);
dest.writeInt(installLocation);
dest.writeInt(requiredForAllUsers ? 1 : 0);
dest.writeInt(requiredForProfile);
@@ -344,6 +355,7 @@
signatures = source.createTypedArray(Signature.CREATOR);
configPreferences = source.createTypedArray(ConfigurationInfo.CREATOR);
reqFeatures = source.createTypedArray(FeatureInfo.CREATOR);
+ featureGroups = source.createTypedArray(FeatureGroupInfo.CREATOR);
installLocation = source.readInt();
requiredForAllUsers = source.readInt() != 0;
requiredForProfile = source.readInt();
diff --git a/core/java/android/content/pm/PackageInstallerParams.aidl b/core/java/android/content/pm/PackageInstaller.aidl
similarity index 88%
rename from core/java/android/content/pm/PackageInstallerParams.aidl
rename to core/java/android/content/pm/PackageInstaller.aidl
index b3dde21..270f870 100644
--- a/core/java/android/content/pm/PackageInstallerParams.aidl
+++ b/core/java/android/content/pm/PackageInstaller.aidl
@@ -16,4 +16,5 @@
package android.content.pm;
-parcelable PackageInstallerParams;
+parcelable PackageInstaller.SessionParams;
+parcelable PackageInstaller.SessionInfo;
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index d70e22c..aa4ea45 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -20,17 +20,24 @@
import android.annotation.Nullable;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
-import android.app.PackageDeleteObserver;
-import android.app.PackageInstallObserver;
+import android.app.ActivityManager;
+import android.content.Context;
import android.content.Intent;
-import android.os.Bundle;
+import android.content.IntentSender;
+import android.graphics.Bitmap;
+import android.net.Uri;
import android.os.FileBridge;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import android.os.Parcel;
import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
import android.os.RemoteException;
import android.util.ExceptionUtils;
+import android.util.Log;
+
+import com.android.internal.util.IndentingPrintWriter;
import java.io.Closeable;
import java.io.IOException;
@@ -67,13 +74,15 @@
* </ul>
*/
public class PackageInstaller {
+ private static final String TAG = "PackageInstaller";
+
/**
* Activity Action: Show details about a particular install session. This
* may surface actions such as pause, resume, or cancel.
* <p>
* This should always be scoped to the installer package that owns the
- * session. Clients should use {@link InstallSessionInfo#getDetailsIntent()}
- * to build this intent correctly.
+ * session. Clients should use {@link SessionInfo#getDetailsIntent()} to
+ * build this intent correctly.
* <p>
* In some cases, a matching Activity may not exist, so ensure you safeguard
* against this.
@@ -92,9 +101,110 @@
*/
public static final String EXTRA_SESSION_ID = "android.content.pm.extra.SESSION_ID";
+ public static final String EXTRA_STATUS = "android.content.pm.extra.STATUS";
+ public static final String EXTRA_STATUS_MESSAGE = "android.content.pm.extra.STATUS_MESSAGE";
+
+ /**
+ * List of package names that are relevant to a status.
+ *
+ * @see Intent#getStringArrayExtra(String)
+ */
+ public static final String EXTRA_PACKAGE_NAMES = "android.content.pm.extra.PACKAGE_NAMES";
+
+ /** {@hide} */
+ public static final String EXTRA_LEGACY_STATUS = "android.content.pm.extra.LEGACY_STATUS";
+ /** {@hide} */
+ public static final String EXTRA_LEGACY_BUNDLE = "android.content.pm.extra.LEGACY_BUNDLE";
/** {@hide} */
public static final String EXTRA_CALLBACK = "android.content.pm.extra.CALLBACK";
+ /**
+ * User action is currently required to proceed. You can launch the intent
+ * activity described by {@link Intent#EXTRA_INTENT} to involve the user and
+ * continue.
+ * <p>
+ * You may choose to immediately launch the intent if the user is actively
+ * using your app. Otherwise, you should use a notification to guide the
+ * user back into your app before launching.
+ *
+ * @see Intent#getParcelableExtra(String)
+ */
+ public static final int STATUS_USER_ACTION_REQUIRED = -1;
+
+ /**
+ * The operation succeeded.
+ */
+ public static final int STATUS_SUCCESS = 0;
+
+ /**
+ * The operation failed in a generic way. The system will always try to
+ * provide a more specific failure reason, but in some rare cases this may
+ * be delivered.
+ *
+ * @see #EXTRA_STATUS_MESSAGE
+ */
+ public static final int STATUS_FAILURE = 1;
+
+ /**
+ * The operation failed because it was blocked. For example, a device policy
+ * may be blocking the operation, a package verifier may have blocked the
+ * operation, or the app may be required for core system operation.
+ *
+ * @see #EXTRA_STATUS_MESSAGE
+ */
+ public static final int STATUS_FAILURE_BLOCKED = 1;
+
+ /**
+ * The operation failed because it was actively aborted. For example, the
+ * user actively declined requested permissions, or the session was
+ * abandoned.
+ *
+ * @see #EXTRA_STATUS_MESSAGE
+ */
+ public static final int STATUS_FAILURE_ABORTED = 2;
+
+ /**
+ * The operation failed because one or more of the APKs was invalid. For
+ * example, they might be malformed, corrupt, incorrectly signed,
+ * mismatched, etc.
+ *
+ * @see #EXTRA_STATUS_MESSAGE
+ */
+ public static final int STATUS_FAILURE_INVALID = 3;
+
+ /**
+ * The operation failed because it 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.
+ * <p>
+ * The result may also contain {@link #EXTRA_PACKAGE_NAMES} with the
+ * specific packages identified as the cause of the conflict.
+ *
+ * @see #EXTRA_STATUS_MESSAGE
+ */
+ public static final int STATUS_FAILURE_CONFLICT = 4;
+
+ /**
+ * The operation failed because of storage issues. For example, the device
+ * may be running low on space, or external media may be unavailable. The
+ * user may be able to help free space or insert different external media.
+ *
+ * @see #EXTRA_STATUS_MESSAGE
+ */
+ public static final int STATUS_FAILURE_STORAGE = 5;
+
+ /**
+ * The operation failed because it is fundamentally incompatible with this
+ * device. For example, the app may require a hardware feature that doesn't
+ * exist, it may be missing native code for the ABIs supported by the
+ * device, or it requires a newer SDK version, etc.
+ *
+ * @see #EXTRA_STATUS_MESSAGE
+ */
+ public static final int STATUS_FAILURE_INCOMPATIBLE = 6;
+
+ private final Context mContext;
private final PackageManager mPm;
private final IPackageInstaller mInstaller;
private final int mUserId;
@@ -103,8 +213,9 @@
private final ArrayList<SessionCallbackDelegate> mDelegates = new ArrayList<>();
/** {@hide} */
- public PackageInstaller(PackageManager pm, IPackageInstaller installer,
+ public PackageInstaller(Context context, PackageManager pm, IPackageInstaller installer,
String installerPackageName, int userId) {
+ mContext = context;
mPm = pm;
mInstaller = installer;
mInstallerPackageName = installerPackageName;
@@ -126,7 +237,7 @@
* This ID remains consistent across device reboots until the
* session is finalized. IDs are not reused during a given boot.
*/
- public int createSession(@NonNull InstallSessionParams params) throws IOException {
+ public int createSession(@NonNull SessionParams params) throws IOException {
try {
return mInstaller.createSession(params, mInstallerPackageName, mUserId);
} catch (RuntimeException e) {
@@ -153,7 +264,7 @@
* Return details for a specific session. To succeed, the caller must either
* own this session, or be the current home app.
*/
- public @Nullable InstallSessionInfo getSessionInfo(int sessionId) {
+ public @Nullable SessionInfo getSessionInfo(int sessionId) {
try {
return mInstaller.getSessionInfo(sessionId);
} catch (RemoteException e) {
@@ -165,7 +276,7 @@
* Return list of all active install sessions, regardless of the installer.
* To succeed, the caller must be the current home app.
*/
- public @NonNull List<InstallSessionInfo> getAllSessions() {
+ public @NonNull List<SessionInfo> getAllSessions() {
try {
return mInstaller.getAllSessions(mUserId);
} catch (RemoteException e) {
@@ -176,7 +287,7 @@
/**
* Return list of all install sessions owned by the calling app.
*/
- public @NonNull List<InstallSessionInfo> getMySessions() {
+ public @NonNull List<SessionInfo> getMySessions() {
try {
return mInstaller.getMySessions(mInstallerPackageName, mUserId);
} catch (RemoteException e) {
@@ -189,25 +300,9 @@
* method is only available to the current "installer of record" for the
* package.
*/
- public void uninstall(@NonNull String packageName, @NonNull UninstallCallback callback) {
+ public void uninstall(@NonNull String packageName, @NonNull IntentSender statusReceiver) {
try {
- mInstaller.uninstall(packageName, 0,
- new UninstallCallbackDelegate(callback).getBinder(), mUserId);
- } catch (RemoteException e) {
- throw e.rethrowAsRuntimeException();
- }
- }
-
- /**
- * Uninstall only a specific split from the given package.
- *
- * @hide
- */
- public void uninstall(@NonNull String packageName, @NonNull String splitName,
- @NonNull UninstallCallback callback) {
- try {
- mInstaller.uninstallSplit(packageName, splitName, 0,
- new UninstallCallbackDelegate(callback).getBinder(), mUserId);
+ mInstaller.uninstall(packageName, 0, statusReceiver, mUserId);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
@@ -353,6 +448,14 @@
* calling thread.
*/
public void addSessionCallback(@NonNull SessionCallback callback, @NonNull Handler handler) {
+ // TODO: remove this temporary guard once we have new prebuilts
+ final ApplicationInfo info = mContext.getApplicationInfo();
+ if ("com.google.android.googlequicksearchbox".equals(info.packageName)
+ && info.versionCode <= 300400070) {
+ Log.d(TAG, "Ignoring callback request from old prebuilt");
+ return;
+ }
+
synchronized (mDelegates) {
final SessionCallbackDelegate delegate = new SessionCallbackDelegate(callback,
handler.getLooper());
@@ -436,7 +539,7 @@
* You can write data into the returned stream, optionally call
* {@link #fsync(OutputStream)} as needed to ensure bytes have been
* persisted to disk, and then close when finished. All streams must be
- * closed before calling {@link #commit(CommitCallback)}.
+ * closed before calling {@link #commit(IntentSender)}.
*
* @param name arbitrary, unique name of your choosing to identify the
* APK being written. You can open a file again for
@@ -476,14 +579,14 @@
}
/**
- * List all APK names contained in this session.
+ * Return all APK names contained in this session.
* <p>
* This returns all names which have been previously written through
* {@link #openWrite(String, long, long)} as part of this session.
*/
- public @NonNull String[] list() {
+ public @NonNull String[] getNames() {
try {
- return mSession.list();
+ return mSession.getNames();
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
@@ -518,9 +621,9 @@
* on the session. If the device reboots before the session has been
* finalized, you may commit the session again.
*/
- public void commit(@NonNull CommitCallback callback) {
+ public void commit(@NonNull IntentSender statusReceiver) {
try {
- mSession.commit(new CommitCallbackDelegate(callback).getBinder());
+ mSession.commit(statusReceiver);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
@@ -553,169 +656,350 @@
}
/**
- * Events for a specific uninstall request.
+ * Parameters for creating a new {@link PackageInstaller.Session}.
*/
- public static abstract class UninstallCallback {
- /**
- * Generic unknown failure. The system will always try to provide a more
- * specific failure reason, but in some rare cases this may be
- * delivered.
- */
- public static final int FAILURE_UNKNOWN = 0;
+ public static class SessionParams implements Parcelable {
+
+ /** {@hide} */
+ public static final int MODE_INVALID = -1;
/**
- * This uninstall was blocked. The package may be required for core
- * system operation, or the user may be restricted. Attempting to
- * uninstall again will have the same result.
+ * Mode for an install session whose staged APKs should fully replace any
+ * existing APKs for the target app.
*/
- public static final int FAILURE_BLOCKED = 1;
+ public static final int MODE_FULL_INSTALL = 1;
/**
- * This uninstall was actively aborted. For example, the user declined
- * to uninstall. You may try to uninstall again.
- */
- public static final int FAILURE_ABORTED = 2;
-
- /**
- * User action is required to proceed. You can start the given intent
- * activity to involve the user and continue.
+ * Mode for an install session that should inherit any existing APKs for the
+ * target app, unless they have been explicitly overridden (based on split
+ * name) by the session. For example, this can be used to add one or more
+ * split APKs to an existing installation.
* <p>
- * You may choose to immediately launch the intent if the user is
- * actively using your app. However, you should use a notification to
- * guide the user back into your app if not currently active.
+ * If there are no existing APKs for the target app, this behaves like
+ * {@link #MODE_FULL_INSTALL}.
*/
- public abstract void onUserActionRequired(Intent intent);
+ public static final int MODE_INHERIT_EXISTING = 2;
- public abstract void onSuccess();
- public abstract void onFailure(int failureReason, String msg, Bundle extras);
- }
+ /** {@hide} */
+ public int mode = MODE_INVALID;
+ /** {@hide} */
+ public int installFlags;
+ /** {@hide} */
+ public int installLocation = PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY;
+ /** {@hide} */
+ public long sizeBytes = -1;
+ /** {@hide} */
+ public String appPackageName;
+ /** {@hide} */
+ public Bitmap appIcon;
+ /** {@hide} */
+ public String appLabel;
+ /** {@hide} */
+ public Uri originatingUri;
+ /** {@hide} */
+ public Uri referrerUri;
+ /** {@hide} */
+ public String abiOverride;
- /** {@hide} */
- private static class UninstallCallbackDelegate extends PackageDeleteObserver {
- private final UninstallCallback target;
+ /**
+ * Construct parameters for a new package install session.
+ *
+ * @param mode one of {@link #MODE_FULL_INSTALL} or
+ * {@link #MODE_INHERIT_EXISTING} describing how the session
+ * should interact with an existing app.
+ */
+ public SessionParams(int mode) {
+ this.mode = mode;
+ }
- public UninstallCallbackDelegate(UninstallCallback target) {
- this.target = target;
+ /** {@hide} */
+ public SessionParams(Parcel source) {
+ mode = source.readInt();
+ installFlags = source.readInt();
+ installLocation = source.readInt();
+ sizeBytes = source.readLong();
+ appPackageName = source.readString();
+ appIcon = source.readParcelable(null);
+ appLabel = source.readString();
+ originatingUri = source.readParcelable(null);
+ referrerUri = source.readParcelable(null);
+ abiOverride = source.readString();
+ }
+
+ /**
+ * Provide value of {@link PackageInfo#installLocation}, which may be used
+ * to determine where the app will be staged. Defaults to
+ * {@link PackageInfo#INSTALL_LOCATION_INTERNAL_ONLY}.
+ */
+ public void setInstallLocation(int installLocation) {
+ this.installLocation = installLocation;
+ }
+
+ /**
+ * Optionally indicate the total size (in bytes) of all APKs that will be
+ * delivered in this session. The system may use this to ensure enough disk
+ * space exists before proceeding, or to estimate container size for
+ * installations living on external storage.
+ *
+ * @see PackageInfo#INSTALL_LOCATION_AUTO
+ * @see PackageInfo#INSTALL_LOCATION_PREFER_EXTERNAL
+ */
+ public void setSize(long sizeBytes) {
+ this.sizeBytes = sizeBytes;
+ }
+
+ /**
+ * Optionally set the package name of the app being installed. It's strongly
+ * recommended that you provide this value when known, so that observers can
+ * communicate installing apps to users.
+ * <p>
+ * If the APKs staged in the session aren't consistent with this package
+ * name, the install will fail. Regardless of this value, all APKs in the
+ * app must have the same package name.
+ */
+ public void setAppPackageName(@Nullable String appPackageName) {
+ this.appPackageName = appPackageName;
+ }
+
+ /**
+ * Optionally set an icon representing the app being installed. This should
+ * be roughly {@link ActivityManager#getLauncherLargeIconSize()} in both
+ * dimensions.
+ */
+ public void setAppIcon(@Nullable Bitmap appIcon) {
+ this.appIcon = appIcon;
+ }
+
+ /**
+ * Optionally set a label representing the app being installed.
+ */
+ public void setAppLabel(@Nullable CharSequence appLabel) {
+ this.appLabel = (appLabel != null) ? appLabel.toString() : null;
+ }
+
+ /**
+ * Optionally set the URI where this package was downloaded from. Used for
+ * verification purposes.
+ *
+ * @see Intent#EXTRA_ORIGINATING_URI
+ */
+ public void setOriginatingUri(@Nullable Uri originatingUri) {
+ this.originatingUri = originatingUri;
+ }
+
+ /**
+ * Optionally set the URI that referred you to install this package. Used
+ * for verification purposes.
+ *
+ * @see Intent#EXTRA_REFERRER
+ */
+ public void setReferrerUri(@Nullable Uri referrerUri) {
+ this.referrerUri = referrerUri;
+ }
+
+ /** {@hide} */
+ public void dump(IndentingPrintWriter pw) {
+ pw.printPair("mode", mode);
+ pw.printHexPair("installFlags", installFlags);
+ pw.printPair("installLocation", installLocation);
+ pw.printPair("sizeBytes", sizeBytes);
+ pw.printPair("appPackageName", appPackageName);
+ pw.printPair("appIcon", (appIcon != null));
+ pw.printPair("appLabel", appLabel);
+ pw.printPair("originatingUri", originatingUri);
+ pw.printPair("referrerUri", referrerUri);
+ pw.printPair("abiOverride", abiOverride);
+ pw.println();
}
@Override
- public void onUserActionRequired(Intent intent) {
- target.onUserActionRequired(intent);
+ public int describeContents() {
+ return 0;
}
@Override
- public void onPackageDeleted(String basePackageName, int returnCode, String msg) {
- if (returnCode == PackageManager.DELETE_SUCCEEDED) {
- target.onSuccess();
- } else {
- final int failureReason = PackageManager.deleteStatusToFailureReason(returnCode);
- msg = PackageManager.deleteStatusToString(returnCode) + ": " + msg;
- target.onFailure(failureReason, msg, null);
- }
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mode);
+ dest.writeInt(installFlags);
+ dest.writeInt(installLocation);
+ dest.writeLong(sizeBytes);
+ dest.writeString(appPackageName);
+ dest.writeParcelable(appIcon, flags);
+ dest.writeString(appLabel);
+ dest.writeParcelable(originatingUri, flags);
+ dest.writeParcelable(referrerUri, flags);
+ dest.writeString(abiOverride);
}
+
+ public static final Parcelable.Creator<SessionParams>
+ CREATOR = new Parcelable.Creator<SessionParams>() {
+ @Override
+ public SessionParams createFromParcel(Parcel p) {
+ return new SessionParams(p);
+ }
+
+ @Override
+ public SessionParams[] newArray(int size) {
+ return new SessionParams[size];
+ }
+ };
}
/**
- * Final result of a session commit request.
+ * Details for an active install session.
*/
- public static abstract class CommitCallback {
- /**
- * Generic unknown failure. The system will always try to provide a more
- * specific failure reason, but in some rare cases this may be
- * delivered.
- */
- public static final int FAILURE_UNKNOWN = 0;
+ public static class SessionInfo implements Parcelable {
+
+ /** {@hide} */
+ public int sessionId;
+ /** {@hide} */
+ public String installerPackageName;
+ /** {@hide} */
+ public String resolvedBaseCodePath;
+ /** {@hide} */
+ public float progress;
+ /** {@hide} */
+ public boolean sealed;
+ /** {@hide} */
+ public boolean open;
+
+ /** {@hide} */
+ public int mode;
+ /** {@hide} */
+ public long sizeBytes;
+ /** {@hide} */
+ public String appPackageName;
+ /** {@hide} */
+ public Bitmap appIcon;
+ /** {@hide} */
+ public CharSequence appLabel;
+
+ /** {@hide} */
+ public SessionInfo() {
+ }
+
+ /** {@hide} */
+ public SessionInfo(Parcel source) {
+ sessionId = source.readInt();
+ installerPackageName = source.readString();
+ resolvedBaseCodePath = source.readString();
+ progress = source.readFloat();
+ sealed = source.readInt() != 0;
+ open = source.readInt() != 0;
+
+ mode = source.readInt();
+ sizeBytes = source.readLong();
+ appPackageName = source.readString();
+ appIcon = source.readParcelable(null);
+ appLabel = source.readString();
+ }
/**
- * 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.
+ * Return the ID for this session.
*/
- public static final int FAILURE_INVALID = 1;
+ public int getSessionId() {
+ return sessionId;
+ }
/**
- * 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.
+ * Return the package name of the app that owns this session.
+ */
+ public @Nullable String getInstallerPackageName() {
+ return installerPackageName;
+ }
+
+ /**
+ * Return current overall progress of this session, between 0 and 1.
* <p>
- * The extras bundle may contain {@link #EXTRA_PACKAGE_NAME} with the
- * specific packages identified as the cause of the conflict.
+ * Note that this progress may not directly correspond to the value reported
+ * by {@link PackageInstaller.Session#setProgress(float)}, as the system may
+ * carve out a portion of the overall progress to represent its own internal
+ * installation work.
*/
- public static final int FAILURE_CONFLICT = 2;
+ public float getProgress() {
+ return progress;
+ }
/**
- * 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.
+ * Return if this session is currently open.
*/
- public static final int FAILURE_STORAGE = 3;
+ public boolean isOpen() {
+ return open;
+ }
/**
- * 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.
+ * Return the package name this session is working with. May be {@code null}
+ * if unknown.
*/
- public static final int FAILURE_INCOMPATIBLE = 4;
+ public @Nullable String getAppPackageName() {
+ return appPackageName;
+ }
/**
- * This install session failed because it was actively aborted. For
- * example, the user declined requested permissions, or a verifier
- * rejected the session.
+ * Return an icon representing the app being installed. May be {@code null}
+ * if unavailable.
+ */
+ public @Nullable Bitmap getAppIcon() {
+ return appIcon;
+ }
+
+ /**
+ * Return a label representing the app being installed. May be {@code null}
+ * if unavailable.
+ */
+ public @Nullable CharSequence getAppLabel() {
+ return appLabel;
+ }
+
+ /**
+ * Return an Intent that can be started to view details about this install
+ * session. This may surface actions such as pause, resume, or cancel.
+ * <p>
+ * In some cases, a matching Activity may not exist, so ensure you safeguard
+ * against this.
*
- * @see PackageManager#VERIFICATION_REJECT
+ * @see PackageInstaller#ACTION_SESSION_DETAILS
*/
- public static final int FAILURE_ABORTED = 5;
-
- public static final String EXTRA_PACKAGE_NAME = "android.content.pm.extra.PACKAGE_NAME";
-
- /**
- * User action is required to proceed. You can start the given intent
- * activity to involve the user and continue.
- * <p>
- * You may choose to immediately launch the intent if the user is
- * actively using your app. However, you should use a notification to
- * guide the user back into your app if not currently active.
- */
- public abstract void onUserActionRequired(Intent intent);
-
- public abstract void onSuccess();
- public abstract void onFailure(int failureReason, String msg, Bundle extras);
- }
-
- /** {@hide} */
- private static class CommitCallbackDelegate extends PackageInstallObserver {
- private final CommitCallback target;
-
- public CommitCallbackDelegate(CommitCallback target) {
- this.target = target;
+ public @Nullable Intent getDetailsIntent() {
+ final Intent intent = new Intent(PackageInstaller.ACTION_SESSION_DETAILS);
+ intent.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId);
+ intent.setPackage(installerPackageName);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ return intent;
}
@Override
- public void onUserActionRequired(Intent intent) {
- target.onUserActionRequired(intent);
+ public int describeContents() {
+ return 0;
}
@Override
- public void onPackageInstalled(String basePackageName, int returnCode, String msg,
- Bundle extras) {
- if (returnCode == PackageManager.INSTALL_SUCCEEDED) {
- target.onSuccess();
- } else {
- final int failureReason = PackageManager.installStatusToFailureReason(returnCode);
- msg = PackageManager.installStatusToString(returnCode) + ": " + msg;
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(sessionId);
+ dest.writeString(installerPackageName);
+ dest.writeString(resolvedBaseCodePath);
+ dest.writeFloat(progress);
+ dest.writeInt(sealed ? 1 : 0);
+ dest.writeInt(open ? 1 : 0);
- if (extras != null) {
- extras.putString(CommitCallback.EXTRA_PACKAGE_NAME,
- extras.getString(PackageManager.EXTRA_FAILURE_EXISTING_PACKAGE));
- }
-
- target.onFailure(failureReason, msg, extras);
- }
+ dest.writeInt(mode);
+ dest.writeLong(sizeBytes);
+ dest.writeString(appPackageName);
+ dest.writeParcelable(appIcon, flags);
+ dest.writeString(appLabel != null ? appLabel.toString() : null);
}
+
+ public static final Parcelable.Creator<SessionInfo>
+ CREATOR = new Parcelable.Creator<SessionInfo>() {
+ @Override
+ public SessionInfo createFromParcel(Parcel p) {
+ return new SessionInfo(p);
+ }
+
+ @Override
+ public SessionInfo[] newArray(int size) {
+ return new SessionInfo[size];
+ }
+ };
}
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index b957a15..56b7164 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -28,8 +28,6 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
-import android.content.pm.PackageInstaller.CommitCallback;
-import android.content.pm.PackageInstaller.UninstallCallback;
import android.content.pm.PackageParser.PackageParserException;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
@@ -179,9 +177,9 @@
/**
* {@link PackageInfo} flag: return information about
* hardware preferences in
- * {@link PackageInfo#configPreferences PackageInfo.configPreferences} and
- * requested features in {@link PackageInfo#reqFeatures
- * PackageInfo.reqFeatures}.
+ * {@link PackageInfo#configPreferences PackageInfo.configPreferences},
+ * and requested features in {@link PackageInfo#reqFeatures} and
+ * {@link PackageInfo#featureGroups}.
*/
public static final int GET_CONFIGURATIONS = 0x00004000;
@@ -3797,6 +3795,16 @@
public abstract boolean isPackageAvailable(String packageName);
/** {@hide} */
+ public static String installStatusToString(int status, String msg) {
+ final String str = installStatusToString(status);
+ if (msg != null) {
+ return str + ": " + msg;
+ } else {
+ return str;
+ }
+ }
+
+ /** {@hide} */
public static String installStatusToString(int status) {
switch (status) {
case INSTALL_SUCCEEDED: return "INSTALL_SUCCEEDED";
@@ -3845,49 +3853,60 @@
}
/** {@hide} */
- public static int installStatusToFailureReason(int status) {
+ public static int installStatusToPublicStatus(int status) {
switch (status) {
- case INSTALL_FAILED_ALREADY_EXISTS: return CommitCallback.FAILURE_CONFLICT;
- case INSTALL_FAILED_INVALID_APK: return CommitCallback.FAILURE_INVALID;
- case INSTALL_FAILED_INVALID_URI: return CommitCallback.FAILURE_INVALID;
- case INSTALL_FAILED_INSUFFICIENT_STORAGE: return CommitCallback.FAILURE_STORAGE;
- case INSTALL_FAILED_DUPLICATE_PACKAGE: return CommitCallback.FAILURE_CONFLICT;
- case INSTALL_FAILED_NO_SHARED_USER: return CommitCallback.FAILURE_CONFLICT;
- case INSTALL_FAILED_UPDATE_INCOMPATIBLE: return CommitCallback.FAILURE_CONFLICT;
- case INSTALL_FAILED_SHARED_USER_INCOMPATIBLE: return CommitCallback.FAILURE_CONFLICT;
- case INSTALL_FAILED_MISSING_SHARED_LIBRARY: return CommitCallback.FAILURE_INCOMPATIBLE;
- case INSTALL_FAILED_REPLACE_COULDNT_DELETE: return CommitCallback.FAILURE_CONFLICT;
- case INSTALL_FAILED_DEXOPT: return CommitCallback.FAILURE_INVALID;
- case INSTALL_FAILED_OLDER_SDK: return CommitCallback.FAILURE_INCOMPATIBLE;
- case INSTALL_FAILED_CONFLICTING_PROVIDER: return CommitCallback.FAILURE_CONFLICT;
- case INSTALL_FAILED_NEWER_SDK: return CommitCallback.FAILURE_INCOMPATIBLE;
- case INSTALL_FAILED_TEST_ONLY: return CommitCallback.FAILURE_INVALID;
- case INSTALL_FAILED_CPU_ABI_INCOMPATIBLE: return CommitCallback.FAILURE_INCOMPATIBLE;
- case INSTALL_FAILED_MISSING_FEATURE: return CommitCallback.FAILURE_INCOMPATIBLE;
- case INSTALL_FAILED_CONTAINER_ERROR: return CommitCallback.FAILURE_STORAGE;
- case INSTALL_FAILED_INVALID_INSTALL_LOCATION: return CommitCallback.FAILURE_STORAGE;
- case INSTALL_FAILED_MEDIA_UNAVAILABLE: return CommitCallback.FAILURE_STORAGE;
- case INSTALL_FAILED_VERIFICATION_TIMEOUT: return CommitCallback.FAILURE_ABORTED;
- case INSTALL_FAILED_VERIFICATION_FAILURE: return CommitCallback.FAILURE_ABORTED;
- case INSTALL_FAILED_PACKAGE_CHANGED: return CommitCallback.FAILURE_INVALID;
- case INSTALL_FAILED_UID_CHANGED: return CommitCallback.FAILURE_INVALID;
- case INSTALL_FAILED_VERSION_DOWNGRADE: return CommitCallback.FAILURE_INVALID;
- case INSTALL_PARSE_FAILED_NOT_APK: return CommitCallback.FAILURE_INVALID;
- case INSTALL_PARSE_FAILED_BAD_MANIFEST: return CommitCallback.FAILURE_INVALID;
- case INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION: return CommitCallback.FAILURE_INVALID;
- case INSTALL_PARSE_FAILED_NO_CERTIFICATES: return CommitCallback.FAILURE_INVALID;
- case INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES: return CommitCallback.FAILURE_INVALID;
- case INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING: return CommitCallback.FAILURE_INVALID;
- case INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME: return CommitCallback.FAILURE_INVALID;
- case INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID: return CommitCallback.FAILURE_INVALID;
- case INSTALL_PARSE_FAILED_MANIFEST_MALFORMED: return CommitCallback.FAILURE_INVALID;
- case INSTALL_PARSE_FAILED_MANIFEST_EMPTY: return CommitCallback.FAILURE_INVALID;
- case INSTALL_FAILED_INTERNAL_ERROR: return CommitCallback.FAILURE_UNKNOWN;
- case INSTALL_FAILED_USER_RESTRICTED: return CommitCallback.FAILURE_INCOMPATIBLE;
- case INSTALL_FAILED_DUPLICATE_PERMISSION: return CommitCallback.FAILURE_CONFLICT;
- case INSTALL_FAILED_NO_MATCHING_ABIS: return CommitCallback.FAILURE_INCOMPATIBLE;
- case INSTALL_FAILED_ABORTED: return CommitCallback.FAILURE_ABORTED;
- default: return CommitCallback.FAILURE_UNKNOWN;
+ case INSTALL_SUCCEEDED: return PackageInstaller.STATUS_SUCCESS;
+ case INSTALL_FAILED_ALREADY_EXISTS: return PackageInstaller.STATUS_FAILURE_CONFLICT;
+ case INSTALL_FAILED_INVALID_APK: return PackageInstaller.STATUS_FAILURE_INVALID;
+ case INSTALL_FAILED_INVALID_URI: return PackageInstaller.STATUS_FAILURE_INVALID;
+ case INSTALL_FAILED_INSUFFICIENT_STORAGE: return PackageInstaller.STATUS_FAILURE_STORAGE;
+ case INSTALL_FAILED_DUPLICATE_PACKAGE: return PackageInstaller.STATUS_FAILURE_CONFLICT;
+ case INSTALL_FAILED_NO_SHARED_USER: return PackageInstaller.STATUS_FAILURE_CONFLICT;
+ case INSTALL_FAILED_UPDATE_INCOMPATIBLE: return PackageInstaller.STATUS_FAILURE_CONFLICT;
+ case INSTALL_FAILED_SHARED_USER_INCOMPATIBLE: return PackageInstaller.STATUS_FAILURE_CONFLICT;
+ case INSTALL_FAILED_MISSING_SHARED_LIBRARY: return PackageInstaller.STATUS_FAILURE_INCOMPATIBLE;
+ case INSTALL_FAILED_REPLACE_COULDNT_DELETE: return PackageInstaller.STATUS_FAILURE_CONFLICT;
+ case INSTALL_FAILED_DEXOPT: return PackageInstaller.STATUS_FAILURE_INVALID;
+ case INSTALL_FAILED_OLDER_SDK: return PackageInstaller.STATUS_FAILURE_INCOMPATIBLE;
+ case INSTALL_FAILED_CONFLICTING_PROVIDER: return PackageInstaller.STATUS_FAILURE_CONFLICT;
+ case INSTALL_FAILED_NEWER_SDK: return PackageInstaller.STATUS_FAILURE_INCOMPATIBLE;
+ case INSTALL_FAILED_TEST_ONLY: return PackageInstaller.STATUS_FAILURE_INVALID;
+ case INSTALL_FAILED_CPU_ABI_INCOMPATIBLE: return PackageInstaller.STATUS_FAILURE_INCOMPATIBLE;
+ case INSTALL_FAILED_MISSING_FEATURE: return PackageInstaller.STATUS_FAILURE_INCOMPATIBLE;
+ case INSTALL_FAILED_CONTAINER_ERROR: return PackageInstaller.STATUS_FAILURE_STORAGE;
+ case INSTALL_FAILED_INVALID_INSTALL_LOCATION: return PackageInstaller.STATUS_FAILURE_STORAGE;
+ case INSTALL_FAILED_MEDIA_UNAVAILABLE: return PackageInstaller.STATUS_FAILURE_STORAGE;
+ case INSTALL_FAILED_VERIFICATION_TIMEOUT: return PackageInstaller.STATUS_FAILURE_ABORTED;
+ case INSTALL_FAILED_VERIFICATION_FAILURE: return PackageInstaller.STATUS_FAILURE_ABORTED;
+ case INSTALL_FAILED_PACKAGE_CHANGED: return PackageInstaller.STATUS_FAILURE_INVALID;
+ case INSTALL_FAILED_UID_CHANGED: return PackageInstaller.STATUS_FAILURE_INVALID;
+ case INSTALL_FAILED_VERSION_DOWNGRADE: return PackageInstaller.STATUS_FAILURE_INVALID;
+ case INSTALL_PARSE_FAILED_NOT_APK: return PackageInstaller.STATUS_FAILURE_INVALID;
+ case INSTALL_PARSE_FAILED_BAD_MANIFEST: return PackageInstaller.STATUS_FAILURE_INVALID;
+ case INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION: return PackageInstaller.STATUS_FAILURE_INVALID;
+ case INSTALL_PARSE_FAILED_NO_CERTIFICATES: return PackageInstaller.STATUS_FAILURE_INVALID;
+ case INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES: return PackageInstaller.STATUS_FAILURE_INVALID;
+ case INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING: return PackageInstaller.STATUS_FAILURE_INVALID;
+ case INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME: return PackageInstaller.STATUS_FAILURE_INVALID;
+ case INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID: return PackageInstaller.STATUS_FAILURE_INVALID;
+ case INSTALL_PARSE_FAILED_MANIFEST_MALFORMED: return PackageInstaller.STATUS_FAILURE_INVALID;
+ case INSTALL_PARSE_FAILED_MANIFEST_EMPTY: return PackageInstaller.STATUS_FAILURE_INVALID;
+ case INSTALL_FAILED_INTERNAL_ERROR: return PackageInstaller.STATUS_FAILURE;
+ case INSTALL_FAILED_USER_RESTRICTED: return PackageInstaller.STATUS_FAILURE_INCOMPATIBLE;
+ case INSTALL_FAILED_DUPLICATE_PERMISSION: return PackageInstaller.STATUS_FAILURE_CONFLICT;
+ case INSTALL_FAILED_NO_MATCHING_ABIS: return PackageInstaller.STATUS_FAILURE_INCOMPATIBLE;
+ case INSTALL_FAILED_ABORTED: return PackageInstaller.STATUS_FAILURE_ABORTED;
+ default: return PackageInstaller.STATUS_FAILURE;
+ }
+ }
+
+ /** {@hide} */
+ public static String deleteStatusToString(int status, String msg) {
+ final String str = deleteStatusToString(status);
+ if (msg != null) {
+ return str + ": " + msg;
+ } else {
+ return str;
}
}
@@ -3905,14 +3924,15 @@
}
/** {@hide} */
- public static int deleteStatusToFailureReason(int status) {
+ public static int deleteStatusToPublicStatus(int status) {
switch (status) {
- case DELETE_FAILED_INTERNAL_ERROR: return UninstallCallback.FAILURE_UNKNOWN;
- case DELETE_FAILED_DEVICE_POLICY_MANAGER: return UninstallCallback.FAILURE_BLOCKED;
- case DELETE_FAILED_USER_RESTRICTED: return UninstallCallback.FAILURE_BLOCKED;
- case DELETE_FAILED_OWNER_BLOCKED: return UninstallCallback.FAILURE_BLOCKED;
- case DELETE_FAILED_ABORTED: return UninstallCallback.FAILURE_ABORTED;
- default: return UninstallCallback.FAILURE_UNKNOWN;
+ case DELETE_SUCCEEDED: return PackageInstaller.STATUS_SUCCESS;
+ case DELETE_FAILED_INTERNAL_ERROR: return PackageInstaller.STATUS_FAILURE;
+ case DELETE_FAILED_DEVICE_POLICY_MANAGER: return PackageInstaller.STATUS_FAILURE_BLOCKED;
+ case DELETE_FAILED_USER_RESTRICTED: return PackageInstaller.STATUS_FAILURE_BLOCKED;
+ case DELETE_FAILED_OWNER_BLOCKED: return PackageInstaller.STATUS_FAILURE_BLOCKED;
+ case DELETE_FAILED_ABORTED: return PackageInstaller.STATUS_FAILURE_ABORTED;
+ default: return PackageInstaller.STATUS_FAILURE;
}
}
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 44bf35d..cddefb5 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -434,6 +434,11 @@
pi.reqFeatures = new FeatureInfo[N];
p.reqFeatures.toArray(pi.reqFeatures);
}
+ N = p.featureGroups != null ? p.featureGroups.size() : 0;
+ if (N > 0) {
+ pi.featureGroups = new FeatureGroupInfo[N];
+ p.featureGroups.toArray(pi.featureGroups);
+ }
}
if ((flags&PackageManager.GET_ACTIVITIES) != 0) {
int N = p.activities.size();
@@ -1502,24 +1507,7 @@
XmlUtils.skipCurrentTag(parser);
} else if (tagName.equals("uses-feature")) {
- FeatureInfo fi = new FeatureInfo();
- sa = res.obtainAttributes(attrs,
- com.android.internal.R.styleable.AndroidManifestUsesFeature);
- // Note: don't allow this value to be a reference to a resource
- // that may change.
- fi.name = sa.getNonResourceString(
- com.android.internal.R.styleable.AndroidManifestUsesFeature_name);
- if (fi.name == null) {
- fi.reqGlEsVersion = sa.getInt(
- com.android.internal.R.styleable.AndroidManifestUsesFeature_glEsVersion,
- FeatureInfo.GL_ES_VERSION_UNDEFINED);
- }
- if (sa.getBoolean(
- com.android.internal.R.styleable.AndroidManifestUsesFeature_required,
- true)) {
- fi.flags |= FeatureInfo.FLAG_REQUIRED;
- }
- sa.recycle();
+ FeatureInfo fi = parseUsesFeature(res, attrs);
pkg.reqFeatures = ArrayUtils.add(pkg.reqFeatures, fi);
if (fi.name == null) {
@@ -1531,9 +1519,35 @@
XmlUtils.skipCurrentTag(parser);
} else if (tagName.equals("feature-group")) {
- // Skip this for now until we know what to do with it.
+ FeatureGroupInfo group = new FeatureGroupInfo();
+ ArrayList<FeatureInfo> features = null;
+ final int innerDepth = parser.getDepth();
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
- XmlUtils.skipCurrentTag(parser);
+ final String innerTagName = parser.getName();
+ if (innerTagName.equals("uses-feature")) {
+ FeatureInfo featureInfo = parseUsesFeature(res, attrs);
+ // FeatureGroups are stricter and mandate that
+ // any <uses-feature> declared are mandatory.
+ featureInfo.flags |= FeatureInfo.FLAG_REQUIRED;
+ features = ArrayUtils.add(features, featureInfo);
+ } else {
+ Slog.w(TAG, "Unknown element under <feature-group>: " + innerTagName +
+ " at " + mArchiveSourcePath + " " +
+ parser.getPositionDescription());
+ }
+ XmlUtils.skipCurrentTag(parser);
+ }
+
+ if (features != null) {
+ group.features = new FeatureInfo[features.size()];
+ group.features = features.toArray(group.features);
+ }
+ pkg.featureGroups = ArrayUtils.add(pkg.featureGroups, group);
} else if (tagName.equals("uses-sdk")) {
if (SDK_VERSION > 0) {
@@ -1851,6 +1865,28 @@
return pkg;
}
+ private FeatureInfo parseUsesFeature(Resources res, AttributeSet attrs)
+ throws XmlPullParserException, IOException {
+ FeatureInfo fi = new FeatureInfo();
+ TypedArray sa = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.AndroidManifestUsesFeature);
+ // Note: don't allow this value to be a reference to a resource
+ // that may change.
+ fi.name = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestUsesFeature_name);
+ if (fi.name == null) {
+ fi.reqGlEsVersion = sa.getInt(
+ com.android.internal.R.styleable.AndroidManifestUsesFeature_glEsVersion,
+ FeatureInfo.GL_ES_VERSION_UNDEFINED);
+ }
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestUsesFeature_required, true)) {
+ fi.flags |= FeatureInfo.FLAG_REQUIRED;
+ }
+ sa.recycle();
+ return fi;
+ }
+
private boolean parseUsesPermission(Package pkg, Resources res, XmlResourceParser parser,
AttributeSet attrs, String[] outError)
throws XmlPullParserException, IOException {
@@ -4225,6 +4261,9 @@
// Applications requested features
public ArrayList<FeatureInfo> reqFeatures = null;
+ // Applications requested feature groups
+ public ArrayList<FeatureGroupInfo> featureGroups = null;
+
public int installLocation;
/* An app that's required for all users and cannot be uninstalled for a user */
diff --git a/core/java/android/content/pm/VerificationParams.java b/core/java/android/content/pm/VerificationParams.java
index bf1f77f..e5119b6 100644
--- a/core/java/android/content/pm/VerificationParams.java
+++ b/core/java/android/content/pm/VerificationParams.java
@@ -24,7 +24,7 @@
/**
* Represents verification parameters used to verify packages to be installed.
*
- * @deprecated callers should migrate to {@link PackageInstallerParams}.
+ * @deprecated callers should migrate to {@link PackageInstaller}.
* @hide
*/
@Deprecated
diff --git a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
index cbf4a3d..1cf7797 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
@@ -510,4 +510,6 @@
/*out*/int[/*2*/] dimens);
private static native int nativeSetNextTimestamp(Surface surface, long timestamp);
+
+ static native int nativeGetJpegFooterSize();
}
diff --git a/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java b/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java
index 0337c96..fbe26e5 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java
@@ -33,6 +33,7 @@
import android.hardware.camera2.utils.ArrayUtils;
import android.hardware.camera2.utils.ListUtils;
import android.hardware.camera2.utils.ParamsUtils;
+import android.hardware.camera2.utils.SizeAreaComparator;
import android.util.Log;
import android.util.Range;
import android.util.Size;
@@ -40,6 +41,8 @@
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
import java.util.List;
import static com.android.internal.util.Preconditions.*;
@@ -187,8 +190,10 @@
* flash.*
*/
mapFlash(m, p);
-
- // TODO: map other fields
+ /*
+ * jpeg.*
+ */
+ mapJpeg(m, p);
/*
* scaler.*
@@ -595,6 +600,16 @@
m.set(FLASH_INFO_AVAILABLE, flashAvailable);
}
+ private static void mapJpeg(CameraMetadataNative m, Camera.Parameters p) {
+ List<Camera.Size> thumbnailSizes = p.getSupportedJpegThumbnailSizes();
+
+ if (thumbnailSizes != null) {
+ Size[] sizes = convertSizeListToArray(thumbnailSizes);
+ Arrays.sort(sizes, new SizeAreaComparator());
+ m.set(JPEG_AVAILABLE_THUMBNAIL_SIZES, sizes);
+ }
+ }
+
private static void mapRequest(CameraMetadataNative m, Parameters p) {
/*
* request.availableCapabilities
diff --git a/core/java/android/hardware/camera2/legacy/LegacyRequestMapper.java b/core/java/android/hardware/camera2/legacy/LegacyRequestMapper.java
index 35646fe..4c4ad0d2 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyRequestMapper.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyRequestMapper.java
@@ -24,6 +24,7 @@
import android.hardware.camera2.params.MeteringRectangle;
import android.hardware.camera2.utils.ListUtils;
import android.hardware.camera2.utils.ParamsUtils;
+import android.location.Location;
import android.util.Log;
import android.util.Range;
import android.util.Size;
@@ -44,6 +45,8 @@
private static final String TAG = "LegacyRequestMapper";
private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+ private static final byte DEFAULT_JPEG_QUALITY = 85;
+
/**
* Set the legacy parameters using the {@link LegacyRequest legacy request}.
*
@@ -350,6 +353,70 @@
+ testPatternMode + "; only OFF is supported");
}
}
+
+ /*
+ * jpeg.*
+ */
+
+ // jpeg.gpsLocation
+ {
+ Location location = request.get(JPEG_GPS_LOCATION);
+ if (location != null) {
+ if (checkForCompleteGpsData(location)) {
+ params.setGpsAltitude(location.getAltitude());
+ params.setGpsLatitude(location.getLatitude());
+ params.setGpsLongitude(location.getLongitude());
+ params.setGpsProcessingMethod(location.getProvider().toUpperCase());
+ params.setGpsTimestamp(location.getTime());
+ } else {
+ Log.w(TAG, "Incomplete GPS parameters provided in location " + location);
+ }
+ } else {
+ params.removeGpsData();
+ }
+ }
+
+ // jpeg.orientation
+ {
+ int orientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
+ params.setRotation(ParamsUtils.getOrDefault(request, JPEG_ORIENTATION, orientation));
+ }
+
+ // jpeg.quality
+ {
+ params.setJpegQuality(0xFF & ParamsUtils.getOrDefault(request, JPEG_QUALITY,
+ DEFAULT_JPEG_QUALITY));
+ }
+
+ // jpeg.thumbnailQuality
+ {
+ params.setJpegQuality(0xFF & ParamsUtils.getOrDefault(request, JPEG_THUMBNAIL_QUALITY,
+ DEFAULT_JPEG_QUALITY));
+ }
+
+ // jpeg.thumbnailSize
+ {
+ List<Camera.Size> sizes = params.getSupportedJpegThumbnailSizes();
+
+ if (sizes != null && sizes.size() > 0) {
+ Size s = request.get(JPEG_THUMBNAIL_SIZE);
+ boolean invalidSize = (s == null) ? false : !ParameterUtils.containsSize(sizes,
+ s.getWidth(), s.getHeight());
+ if (invalidSize) {
+ Log.w(TAG, "Invalid JPEG thumbnail size set " + s + ", skipping thumbnail...");
+ }
+ if (s == null || invalidSize) {
+ // (0,0) = "no thumbnail" in Camera API 1
+ params.setJpegThumbnailSize(/*width*/0, /*height*/0);
+ } else {
+ params.setJpegThumbnailSize(s.getWidth(), s.getHeight());
+ }
+ }
+ }
+ }
+
+ private static boolean checkForCompleteGpsData(Location location) {
+ return location != null && location.getProvider() != null && location.getTime() != 0;
}
static int filterSupportedCaptureIntent(int captureIntent) {
diff --git a/core/java/android/hardware/camera2/legacy/LegacyResultMapper.java b/core/java/android/hardware/camera2/legacy/LegacyResultMapper.java
index a2f9b4c..090a822 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyResultMapper.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyResultMapper.java
@@ -28,6 +28,7 @@
import android.hardware.camera2.params.MeteringRectangle;
import android.hardware.camera2.utils.ListUtils;
import android.hardware.camera2.utils.ParamsUtils;
+import android.location.Location;
import android.util.Log;
import android.util.Size;
@@ -250,6 +251,29 @@
result.set(SENSOR_TEST_PATTERN_MODE, SENSOR_TEST_PATTERN_MODE_OFF);
}
+ /*
+ * jpeg
+ */
+ // jpeg.gpsLocation
+ result.set(JPEG_GPS_LOCATION, request.get(CaptureRequest.JPEG_GPS_LOCATION));
+
+ // jpeg.orientation
+ result.set(JPEG_ORIENTATION, request.get(CaptureRequest.JPEG_ORIENTATION));
+
+ // jpeg.quality
+ result.set(JPEG_QUALITY, (byte) params.getJpegQuality());
+
+ // jpeg.thumbnailQuality
+ result.set(JPEG_THUMBNAIL_QUALITY, (byte) params.getJpegThumbnailQuality());
+
+ // jpeg.thumbnailSize
+ Camera.Size s = params.getJpegThumbnailSize();
+ if (s != null) {
+ result.set(JPEG_THUMBNAIL_SIZE, ParameterUtils.convertSize(s));
+ } else {
+ Log.w(TAG, "Null thumbnail size received from parameters.");
+ }
+
// TODO: Remaining result metadata tags conversions.
return result;
}
diff --git a/core/java/android/hardware/camera2/legacy/ParameterUtils.java b/core/java/android/hardware/camera2/legacy/ParameterUtils.java
index 385f844..98adcea 100644
--- a/core/java/android/hardware/camera2/legacy/ParameterUtils.java
+++ b/core/java/android/hardware/camera2/legacy/ParameterUtils.java
@@ -253,6 +253,33 @@
}
/**
+ * Convert a camera API1 list of sizes into an array of sizes
+ */
+ public static Size[] convertSizeListToArray(List<Camera.Size> sizeList) {
+ checkNotNull(sizeList, "sizeList must not be null");
+
+ Size[] array = new Size[sizeList.size()];
+ int ctr = 0;
+ for (Camera.Size s : sizeList) {
+ array[ctr++] = new Size(s.width, s.height);
+ }
+ return array;
+ }
+
+ /**
+ * Check if the camera API1 list of sizes contains a size with the given dimens.
+ */
+ public static boolean containsSize(List<Camera.Size> sizeList, int width, int height) {
+ checkNotNull(sizeList, "sizeList must not be null");
+ for (Camera.Size s : sizeList) {
+ if (s.height == height && s.width == width) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
* Returns the largest supported picture size, as compared by its area.
*/
public static Size getLargestSupportedJpegSizeByArea(Camera.Parameters params) {
diff --git a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
index 5c66753..eb8debb 100644
--- a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
+++ b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
@@ -190,7 +190,8 @@
try {
if (RequestHolder.jpegType(s)) {
Log.i(TAG, "Producing jpeg buffer...");
- LegacyCameraDevice.setSurfaceDimens(s, data.length, /*height*/1);
+ LegacyCameraDevice.setSurfaceDimens(s, data.length +
+ LegacyCameraDevice.nativeGetJpegFooterSize(), /*height*/1);
LegacyCameraDevice.setNextTimestamp(s, timestamp);
LegacyCameraDevice.produceFrame(s, data, data.length, /*height*/1,
CameraMetadataNative.NATIVE_JPEG_FORMAT);
@@ -665,10 +666,6 @@
}
mReceivedJpeg.close();
doJpegCapturePrepare(holder);
- if (!mReceivedJpeg.block(JPEG_FRAME_TIMEOUT)) {
- // TODO: report error to CameraDevice
- Log.e(TAG, "Hit timeout for jpeg callback!");
- }
}
/*
diff --git a/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java b/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java
index 3f24b2c..b1b0f9b 100644
--- a/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java
+++ b/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java
@@ -16,6 +16,7 @@
package android.hardware.camera2.legacy;
import android.graphics.ImageFormat;
+import android.graphics.RectF;
import android.graphics.SurfaceTexture;
import android.os.Environment;
import android.opengl.EGL14;
@@ -194,6 +195,9 @@
checkGlError("onDrawFrame start");
st.getTransformMatrix(mSTMatrix);
+ Matrix.setIdentityM(mMVPMatrix, /*smOffset*/0);
+
+ // Find intermediate buffer dimensions
Size dimens;
try {
dimens = LegacyCameraDevice.getTextureSize(st);
@@ -201,9 +205,6 @@
// Should never hit this.
throw new IllegalStateException("Surface abandoned, skipping drawFrame...", e);
}
-
- Matrix.setIdentityM(mMVPMatrix, /*smOffset*/0);
-
float texWidth = dimens.getWidth();
float texHeight = dimens.getHeight();
@@ -211,32 +212,30 @@
throw new IllegalStateException("Illegal intermediate texture with dimension of 0");
}
- // Find largest scaling factor from the intermediate texture dimension to the
- // output surface dimension. Scaling the intermediate texture by this allows
- // us to letterbox/pillerbox the output surface into the intermediate texture.
- float widthRatio = width / texWidth;
- float heightRatio = height / texHeight;
- float actual = (widthRatio < heightRatio) ? heightRatio : widthRatio;
+ // Letterbox or pillerbox output dimensions into intermediate dimensions.
+ RectF intermediate = new RectF(/*left*/0, /*top*/0, /*right*/texWidth, /*bottom*/texHeight);
+ RectF output = new RectF(/*left*/0, /*top*/0, /*right*/width, /*bottom*/height);
+ android.graphics.Matrix boxingXform = new android.graphics.Matrix();
+ boxingXform.setRectToRect(output, intermediate, android.graphics.Matrix.ScaleToFit.CENTER);
+ boxingXform.mapRect(output);
+
+ // Find scaling factor from pillerboxed/letterboxed output dimensions to intermediate
+ // buffer dimensions.
+ float scaleX = intermediate.width() / output.width();
+ float scaleY = intermediate.height() / output.height();
+
+ // Scale opposite dimension in clip coordinates so output is letterboxed/pillerboxed into
+ // the intermediate dimensions (rather than vice-versa).
+ Matrix.scaleM(mMVPMatrix, /*offset*/0, /*x*/scaleY, /*y*/scaleX, /*z*/1);
if (DEBUG) {
- Log.d(TAG, "Scaling factor " + actual + " used for " + width + "x" + height +
- " surface, intermediate buffer size is " + texWidth + "x" + texHeight);
+ Log.d(TAG, "Scaling factors (S_x = " + scaleX + ",S_y = " + scaleY + ") used for " +
+ width + "x" + height + " surface, intermediate buffer size is " + texWidth +
+ "x" + texHeight);
}
- // Set the viewport height and width to be the scaled intermediate texture dimensions.
- int viewportW = (int) (actual * texWidth);
- int viewportH = (int) (actual * texHeight);
-
- // Set the offset of the viewport so that the output surface is centered in the viewport.
- float dx = (width - viewportW) / 2f;
- float dy = (height - viewportH) / 2f;
-
- if (DEBUG) {
- Log.d(TAG, "Translation " + dx + "," + dy + " used for " + width + "x" + height +
- " surface");
- }
-
- GLES20.glViewport((int) dx, (int) dy, viewportW, viewportH);
+ // Set viewport to be output buffer dimensions
+ GLES20.glViewport(0, 0, width, height);
if (DEBUG) {
GLES20.glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
@@ -251,7 +250,7 @@
mTriangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET);
GLES20.glVertexAttribPointer(maPositionHandle, VERTEX_POS_SIZE, GLES20.GL_FLOAT,
- /*normalized*/ false,TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices);
+ /*normalized*/ false, TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices);
checkGlError("glVertexAttribPointer maPosition");
GLES20.glEnableVertexAttribArray(maPositionHandle);
checkGlError("glEnableVertexAttribArray maPositionHandle");
@@ -654,6 +653,8 @@
if (LegacyCameraDevice.containsSurfaceId(holder.surface, targetSurfaceIds)) {
makeCurrent(holder.eglSurface);
try {
+ LegacyCameraDevice.setSurfaceDimens(holder.surface, holder.width,
+ holder.height);
LegacyCameraDevice.setNextTimestamp(holder.surface, captureHolder.second);
drawFrame(mSurfaceTexture, holder.width, holder.height);
swapBuffers(holder.eglSurface);
diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java
index 6160bc2..dcb2892 100644
--- a/core/java/android/net/LinkProperties.java
+++ b/core/java/android/net/LinkProperties.java
@@ -56,6 +56,10 @@
private ProxyInfo mHttpProxy;
private int mMtu;
+ private static final int MIN_MTU = 68;
+ private static final int MIN_MTU_V6 = 1280;
+ private static final int MAX_MTU = 10000;
+
// Stores the properties of links that are "stacked" above this link.
// Indexed by interface name to allow modification and to prevent duplicates being added.
private Hashtable<String, LinkProperties> mStackedLinks =
@@ -996,4 +1000,17 @@
return new LinkProperties[size];
}
};
+
+ /**
+ * Check the valid MTU range based on IPv4 or IPv6.
+ * @hide
+ */
+ public static boolean isValidMtu(int mtu, boolean ipv6) {
+ if (ipv6) {
+ if ((mtu >= MIN_MTU_V6 && mtu <= MAX_MTU)) return true;
+ } else {
+ if ((mtu >= MIN_MTU && mtu <= MAX_MTU)) return true;
+ }
+ return false;
+ }
}
diff --git a/core/java/android/nfc/INfcAdapter.aidl b/core/java/android/nfc/INfcAdapter.aidl
index ee4d45e..5815fa6 100644
--- a/core/java/android/nfc/INfcAdapter.aidl
+++ b/core/java/android/nfc/INfcAdapter.aidl
@@ -59,5 +59,5 @@
void registerLockscreenDispatch(INfcLockscreenDispatch lockscreenDispatch, in int[] techList);
void addNfcUnlockHandler(INfcUnlockHandler unlockHandler, in int[] techList);
- void removeNfcUnlockHandler(IBinder b);
+ void removeNfcUnlockHandler(INfcUnlockHandler unlockHandler);
}
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index dde2cf1..6bd5a32 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -311,7 +311,7 @@
final NfcActivityManager mNfcActivityManager;
final Context mContext;
- final HashMap<NfcUnlockHandler, IBinder> mNfcUnlockHandlers;
+ final HashMap<NfcUnlockHandler, INfcUnlockHandler> mNfcUnlockHandlers;
final Object mLock;
/**
@@ -542,7 +542,7 @@
NfcAdapter(Context context) {
mContext = context;
mNfcActivityManager = new NfcActivityManager(this);
- mNfcUnlockHandlers = new HashMap<NfcUnlockHandler, IBinder>();
+ mNfcUnlockHandlers = new HashMap<NfcUnlockHandler, INfcUnlockHandler>();
mLock = new Object();
}
@@ -1498,27 +1498,37 @@
* <p /> The parameter {@code tagTechnologies} determines which Tag technologies will be polled for
* at the lockscreen. Polling for less tag technologies reduces latency, and so it is
* strongly recommended to only provide the Tag technologies that the handler is expected to
- * receive.
+ * receive. There must be at least one tag technology provided, otherwise the unlock handler
+ * is ignored.
*
* @hide
*/
@SystemApi
public boolean addNfcUnlockHandler(final NfcUnlockHandler unlockHandler,
String[] tagTechnologies) {
- try {
- INfcUnlockHandler.Stub iHandler = new INfcUnlockHandler.Stub() {
- @Override
- public boolean onUnlockAttempted(Tag tag) throws RemoteException {
- return unlockHandler.onUnlockAttempted(tag);
- }
- };
+ // If there are no tag technologies, don't bother adding unlock handler
+ if (tagTechnologies.length == 0) {
+ return false;
+ }
+ try {
synchronized (mLock) {
if (mNfcUnlockHandlers.containsKey(unlockHandler)) {
- return true;
+ // update the tag technologies
+ sService.removeNfcUnlockHandler(mNfcUnlockHandlers.get(unlockHandler));
+ mNfcUnlockHandlers.remove(unlockHandler);
}
- sService.addNfcUnlockHandler(iHandler, Tag.getTechCodesFromStrings(tagTechnologies));
- mNfcUnlockHandlers.put(unlockHandler, iHandler.asBinder());
+
+ INfcUnlockHandler.Stub iHandler = new INfcUnlockHandler.Stub() {
+ @Override
+ public boolean onUnlockAttempted(Tag tag) throws RemoteException {
+ return unlockHandler.onUnlockAttempted(tag);
+ }
+ };
+
+ sService.addNfcUnlockHandler(iHandler,
+ Tag.getTechCodesFromStrings(tagTechnologies));
+ mNfcUnlockHandlers.put(unlockHandler, iHandler);
}
} catch (RemoteException e) {
attemptDeadServiceRecovery(e);
@@ -1542,8 +1552,7 @@
try {
synchronized (mLock) {
if (mNfcUnlockHandlers.containsKey(unlockHandler)) {
- sService.removeNfcUnlockHandler(mNfcUnlockHandlers.get(unlockHandler));
- mNfcUnlockHandlers.remove(unlockHandler);
+ sService.removeNfcUnlockHandler(mNfcUnlockHandlers.remove(unlockHandler));
}
return true;
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index f9e7b78..9d1a7bc 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -499,7 +499,12 @@
* Sets all the user-wide restrictions for this user.
* Requires the MANAGE_USERS permission.
* @param restrictions the Bundle containing all the restrictions.
+ * @deprecated use {@link android.app.admin.DevicePolicyManager#addUserRestriction(
+ * android.content.ComponentName, String)} or
+ * {@link android.app.admin.DevicePolicyManager#clearUserRestriction(
+ * android.content.ComponentName, String)} instead.
*/
+ @Deprecated
public void setUserRestrictions(Bundle restrictions) {
setUserRestrictions(restrictions, Process.myUserHandle());
}
@@ -509,7 +514,12 @@
* Requires the MANAGE_USERS permission.
* @param restrictions the Bundle containing all the restrictions.
* @param userHandle the UserHandle of the user for whom to set the restrictions.
+ * @deprecated use {@link android.app.admin.DevicePolicyManager#addUserRestriction(
+ * android.content.ComponentName, String)} or
+ * {@link android.app.admin.DevicePolicyManager#clearUserRestriction(
+ * android.content.ComponentName, String)} instead.
*/
+ @Deprecated
public void setUserRestrictions(Bundle restrictions, UserHandle userHandle) {
try {
mService.setUserRestrictions(restrictions, userHandle.getIdentifier());
@@ -523,7 +533,12 @@
* Requires the MANAGE_USERS permission.
* @param key the key of the restriction
* @param value the value for the restriction
+ * @deprecated use {@link android.app.admin.DevicePolicyManager#addUserRestriction(
+ * android.content.ComponentName, String)} or
+ * {@link android.app.admin.DevicePolicyManager#clearUserRestriction(
+ * android.content.ComponentName, String)} instead.
*/
+ @Deprecated
public void setUserRestriction(String key, boolean value) {
Bundle bundle = getUserRestrictions();
bundle.putBoolean(key, value);
@@ -537,7 +552,12 @@
* @param key the key of the restriction
* @param value the value for the restriction
* @param userHandle the user whose restriction is to be changed.
+ * @deprecated use {@link android.app.admin.DevicePolicyManager#addUserRestriction(
+ * android.content.ComponentName, String)} or
+ * {@link android.app.admin.DevicePolicyManager#clearUserRestriction(
+ * android.content.ComponentName, String)} instead.
*/
+ @Deprecated
public void setUserRestriction(String key, boolean value, UserHandle userHandle) {
Bundle bundle = getUserRestrictions(userHandle);
bundle.putBoolean(key, value);
@@ -718,7 +738,7 @@
/**
* Returns list of the profiles of userHandle including
* userHandle itself.
- * Note that it this returns both enabled and not enabled profiles. See
+ * Note that this returns both enabled and not enabled profiles. See
* {@link #getUserProfiles()} if you need only the enabled ones.
*
* Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 8886559..e075d8b 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -2667,6 +2667,16 @@
};
/**
+ * These entries are considered common between the personal and the managed profile,
+ * since the managed profile doesn't get to change them.
+ * @hide
+ */
+ public static final String[] CLONE_TO_MANAGED_PROFILE = {
+ DATE_FORMAT,
+ TIME_12_24
+ };
+
+ /**
* When to use Wi-Fi calling
*
* @see android.telephony.TelephonyManager.WifiCallingChoices
@@ -4797,6 +4807,26 @@
};
/**
+ * These entries are considered common between the personal and the managed profile,
+ * since the managed profile doesn't get to change them.
+ * @hide
+ */
+ public static final String[] CLONE_TO_MANAGED_PROFILE = {
+ ACCESSIBILITY_ENABLED,
+ ALLOW_MOCK_LOCATION,
+ ALLOWED_GEOLOCATION_ORIGINS,
+ DEFAULT_INPUT_METHOD,
+ ENABLED_ACCESSIBILITY_SERVICES,
+ ENABLED_INPUT_METHODS,
+ LOCATION_MODE,
+ LOCATION_PROVIDERS_ALLOWED,
+ LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS,
+ SELECTED_INPUT_METHOD_SUBTYPE,
+ SELECTED_SPELL_CHECKER,
+ SELECTED_SPELL_CHECKER_SUBTYPE
+ };
+
+ /**
* Helper method for determining if a location provider is enabled.
*
* @param cr the content resolver to use
@@ -6440,6 +6470,14 @@
public static final String GUEST_USER_ENABLED = "guest_user_enabled";
/**
+ * Whether the NetworkScoringService has been first initialized.
+ * <p>
+ * Type: int (0 for false, 1 for true)
+ * @hide
+ */
+ public static final String NETWORK_SCORING_PROVISIONED = "network_scoring_provisioned";
+
+ /**
* Settings to backup. This is here so that it's in the same place as the settings
* keys and easy to update.
*
diff --git a/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp b/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp
index 697cdc6..980ead0 100644
--- a/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp
+++ b/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp
@@ -541,6 +541,15 @@
ALOGE("%s: Error while setting surface dimens %s (%d).", __FUNCTION__, strerror(-err), err);
return err;
}
+
+ // WAR - Set user dimensions also to avoid incorrect scaling after TextureView orientation
+ // change.
+ err = native_window_set_buffers_user_dimensions(anw.get(), width, height);
+ if (err != NO_ERROR) {
+ ALOGE("%s: Error while setting surface user dimens %s (%d).", __FUNCTION__, strerror(-err),
+ err);
+ return err;
+ }
return NO_ERROR;
}
@@ -624,6 +633,11 @@
return NO_ERROR;
}
+static jint LegacyCameraDevice_nativeGetJpegFooterSize(JNIEnv* env, jobject thiz) {
+ ALOGV("nativeGetJpegFooterSize");
+ return static_cast<jint>(sizeof(struct camera3_jpeg_blob));
+}
+
} // extern "C"
static JNINativeMethod gCameraDeviceMethods[] = {
@@ -657,6 +671,9 @@
{ "nativeSetNextTimestamp",
"(Landroid/view/Surface;J)I",
(void *)LegacyCameraDevice_nativeSetNextTimestamp },
+ { "nativeGetJpegFooterSize",
+ "()I",
+ (void *)LegacyCameraDevice_nativeGetJpegFooterSize },
};
// Get all the required offsets in java class and register native functions
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 931d1c6..aee3090 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1257,6 +1257,14 @@
android:description="@string/permdesc_bind_call_service"
android:label="@string/permlab_bind_call_service" />
+ <!-- @SystemApi Allows an application to bind to ConnectionService implementations.
+ @hide -->
+ <permission android:name="android.permission.BIND_CONNECTION_SERVICE"
+ android:permissionGroup="android.permission-group.PHONE_CALLS"
+ android:protectionLevel="system|signature"
+ android:description="@string/permdesc_bind_connection_service"
+ android:label="@string/permlab_bind_connection_service" />
+
<!-- ================================== -->
<!-- Permissions for sdcard interaction -->
<!-- ================================== -->
diff --git a/core/res/res/animator/leanback_setup_fragment_close_enter.xml b/core/res/res/animator/leanback_setup_fragment_close_enter.xml
new file mode 100644
index 0000000..1626dd3
--- /dev/null
+++ b/core/res/res/animator/leanback_setup_fragment_close_enter.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2014, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+ <objectAnimator
+ android:propertyName="alpha"
+ android:valueType="floatType"
+ android:valueFrom="@dimen/leanback_setup_alpha_backward_in_content_start"
+ android:valueTo="@dimen/leanback_setup_alpha_backward_in_content_end"
+ android:duration="@integer/leanback_setup_alpha_backward_in_content_duration"
+ android:startOffset="@integer/leanback_setup_alpha_backward_in_content_delay"/>
+</set>
diff --git a/core/res/res/animator/leanback_setup_fragment_close_exit.xml b/core/res/res/animator/leanback_setup_fragment_close_exit.xml
new file mode 100644
index 0000000..a827df4
--- /dev/null
+++ b/core/res/res/animator/leanback_setup_fragment_close_exit.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2014, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+ <objectAnimator
+ android:propertyName="alpha"
+ android:valueType="floatType"
+ android:valueFrom="@dimen/leanback_setup_alpha_backward_out_content_start"
+ android:valueTo="@dimen/leanback_setup_alpha_backward_out_content_end"
+ android:duration="@integer/leanback_setup_alpha_backward_out_content_duration"
+ android:startOffset="@integer/leanback_setup_alpha_backward_out_content_delay"/>
+ <objectAnimator
+ android:propertyName="x"
+ android:valueType="floatType"
+ android:valueFrom="@dimen/leanback_setup_translation_backward_out_content_start"
+ android:valueTo="@dimen/leanback_setup_translation_backward_out_content_end"
+ android:duration="@integer/leanback_setup_translation_backward_out_content_duration"
+ android:startOffset="@integer/leanback_setup_translation_backward_out_content_delay"/>
+</set>
diff --git a/core/res/res/animator/leanback_setup_fragment_open_enter.xml b/core/res/res/animator/leanback_setup_fragment_open_enter.xml
new file mode 100644
index 0000000..34b9a57
--- /dev/null
+++ b/core/res/res/animator/leanback_setup_fragment_open_enter.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2014, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<set xmlns:android="http://schemas.android.com/apk/res/android" >
+ <objectAnimator
+ android:propertyName="alpha"
+ android:valueType="floatType"
+ android:valueFrom="@dimen/leanback_setup_alpha_forward_in_content_start"
+ android:valueTo="@dimen/leanback_setup_alpha_forward_in_content_end"
+ android:duration="@integer/leanback_setup_alpha_forward_in_content_duration"
+ android:startOffset="@integer/leanback_setup_alpha_forward_in_content_delay"/>
+ <objectAnimator
+ android:propertyName="x"
+ android:valueType="floatType"
+ android:valueFrom="@dimen/leanback_setup_translation_forward_in_content_start"
+ android:valueTo="@dimen/leanback_setup_translation_forward_in_content_end"
+ android:duration="@integer/leanback_setup_translation_forward_in_content_duration"
+ android:startOffset="@integer/leanback_setup_translation_forward_in_content_delay" />
+</set>
diff --git a/core/res/res/animator/leanback_setup_fragment_open_exit.xml b/core/res/res/animator/leanback_setup_fragment_open_exit.xml
new file mode 100644
index 0000000..5622db4
--- /dev/null
+++ b/core/res/res/animator/leanback_setup_fragment_open_exit.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2014, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+ <objectAnimator
+ android:propertyName="alpha"
+ android:valueType="floatType"
+ android:valueFrom="@dimen/leanback_setup_alpha_forward_out_content_start"
+ android:valueTo="@dimen/leanback_setup_alpha_forward_out_content_end"
+ android:duration="@integer/leanback_setup_alpha_forward_out_content_duration"
+ android:startOffset="@integer/leanback_setup_alpha_forward_out_content_delay"/>
+</set>
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index c268d97..7d4c37e 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1250,7 +1250,9 @@
application.
<p>This appears as a child tag of the root
- {@link #AndroidManifest manifest} tag. -->
+ {@link #AndroidManifest manifest} tag.
+
+ @deprecated Use <code>feature-group</code> instead.-->
<declare-styleable name="AndroidManifestUsesConfiguration" parent="AndroidManifest">
<!-- The type of touch screen used by an application. -->
<attr name="reqTouchScreen" />
diff --git a/core/res/res/values/dimens_leanback.xml b/core/res/res/values/dimens_leanback.xml
index fb5f8f0..c824a2a 100644
--- a/core/res/res/values/dimens_leanback.xml
+++ b/core/res/res/values/dimens_leanback.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
+<?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");
@@ -13,7 +13,74 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<resources>
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Default alpha value for disabled elements. -->
<item name="disabled_alpha_leanback_formwizard" format="float" type="dimen">0.2</item>
+ <!-- The duration of most animations related to screen content transitions -->
+ <integer name="leanback_setup_base_animation_duration">500</integer>
+ <item name="leanback_setup_alpha_animiation_max_opacity" format="float" type="dimen">1.0</item>
+ <item name="leanback_setup_alpha_animiation_min_opacity" format="float" type="dimen">0.0</item>
+ <!-- Where stable, on-screen content rests -->
+ <dimen name="leanback_setup_translation_content_resting_point">0dp</dimen>
+ <integer name="leanback_setup_translation_content_resting_point_v4">0</integer>
+ <!-- The screen position at which content enters/exits. If you're over the edge of the cliff, we can't see you. -->
+ <dimen name="leanback_setup_translation_content_cliff">100dp</dimen>
+ <integer name="leanback_setup_translation_content_cliff_v4">200</integer>
+
+ <!-- Opacity animation for activity background -->
+ <!-- The opacity of the background of the new activity background when the alpha animation starts-->
+ <item name="leanback_setup_alpha_activity_in_bkg_start" format="float" type="dimen">@dimen/leanback_setup_alpha_animiation_min_opacity</item>
+ <!-- The opacity of the background of the new activity background when the alpha animation ends-->
+ <item name="leanback_setup_alpha_activity_in_bkg_end" format="float" type="dimen">@dimen/leanback_setup_alpha_animiation_max_opacity</item>
+ <integer name="leanback_setup_alpha_activity_in_bkg_delay">0</integer>
+ <integer name="leanback_setup_alpha_activity_in_bkg_duration">@integer/leanback_setup_base_animation_duration</integer>
+
+ <item name="leanback_setup_alpha_activity_out_bkg_start" format="float" type="dimen">@dimen/leanback_setup_alpha_animiation_max_opacity</item>
+ <!-- The opacity of the background of the new activity background when the alpha animation ends-->
+ <item name="leanback_setup_alpha_activity_out_bkg_end" format="float" type="dimen">@dimen/leanback_setup_alpha_animiation_min_opacity</item>
+ <integer name="leanback_setup_alpha_activity_out_bkg_delay">0</integer>
+ <integer name="leanback_setup_alpha_activity_out_bkg_duration">@integer/leanback_setup_base_animation_duration</integer>
+
+ <!-- Content forward animation configuration values -->
+ <!-- Parameter for alpha animation of new content coming on to the screen when we're moving "forward" -->
+ <!-- Initial opacity of the new content that is coming on to the screen -->
+ <item name="leanback_setup_alpha_forward_in_content_start" format="float" type="dimen">@dimen/leanback_setup_alpha_animiation_min_opacity</item>
+ <item name="leanback_setup_alpha_forward_in_content_end" format="float" type="dimen">@dimen/leanback_setup_alpha_animiation_max_opacity</item>
+ <integer name="leanback_setup_alpha_forward_in_content_delay">0</integer>
+ <integer name="leanback_setup_alpha_forward_in_content_duration">@integer/leanback_setup_base_animation_duration</integer>
+
+ <item name="leanback_setup_alpha_forward_out_content_start" format="float" type="dimen">@dimen/leanback_setup_alpha_animiation_max_opacity</item>
+ <item name="leanback_setup_alpha_forward_out_content_end" format="float" type="dimen">@dimen/leanback_setup_alpha_animiation_min_opacity</item>
+ <integer name="leanback_setup_alpha_forward_out_content_delay">0</integer>
+ <integer name="leanback_setup_alpha_forward_out_content_duration">@integer/leanback_setup_base_animation_duration</integer>
+
+ <!-- Position animation of incoming content during a "forward" transition -->
+ <dimen name="leanback_setup_translation_forward_in_content_start">@dimen/leanback_setup_translation_content_cliff</dimen>
+ <dimen name="leanback_setup_translation_forward_in_content_start_v4">@integer/leanback_setup_translation_content_cliff_v4</dimen>
+ <dimen name="leanback_setup_translation_forward_in_content_end">@dimen/leanback_setup_translation_content_resting_point</dimen>
+ <dimen name="leanback_setup_translation_forward_in_content_end_v4">@integer/leanback_setup_translation_content_resting_point_v4</dimen>
+ <integer name="leanback_setup_translation_forward_in_content_delay">0</integer>
+ <integer name="leanback_setup_translation_forward_in_content_duration">@integer/leanback_setup_base_animation_duration</integer>
+
+ <!-- Content backward animation configuration values -->
+ <!-- Alpha animation values for the content that will be displayed after the transition is complete, this is the content coming in. -->
+ <item name="leanback_setup_alpha_backward_in_content_start" format="float" type="dimen">@dimen/leanback_setup_alpha_animiation_min_opacity</item>
+ <item name="leanback_setup_alpha_backward_in_content_end" format="float" type="dimen">@dimen/leanback_setup_alpha_animiation_max_opacity</item>
+ <integer name="leanback_setup_alpha_backward_in_content_delay">0</integer>
+ <integer name="leanback_setup_alpha_backward_in_content_duration">@integer/leanback_setup_base_animation_duration</integer>
+
+ <!-- Alpha animiation values for the content that is displayed when the transition starts, this is the content going away. -->
+ <item name="leanback_setup_alpha_backward_out_content_start" format="float" type="dimen">@dimen/leanback_setup_alpha_animiation_max_opacity</item>
+ <item name="leanback_setup_alpha_backward_out_content_end" format="float" type="dimen">@dimen/leanback_setup_alpha_animiation_min_opacity</item>
+ <integer name="leanback_setup_alpha_backward_out_content_delay">0</integer>
+ <integer name="leanback_setup_alpha_backward_out_content_duration">@integer/leanback_setup_base_animation_duration</integer>
+
+ <!-- Position animation for content that is displayed when the transition starts, this is the content going away. -->
+ <dimen name="leanback_setup_translation_backward_out_content_start">@dimen/leanback_setup_translation_content_resting_point</dimen>
+ <dimen name="leanback_setup_translation_backward_out_content_start_v4">@integer/leanback_setup_translation_content_resting_point_v4</dimen>
+ <dimen name="leanback_setup_translation_backward_out_content_end">@dimen/leanback_setup_translation_content_cliff</dimen>
+ <dimen name="leanback_setup_translation_backward_out_content_end_v4">@integer/leanback_setup_translation_content_cliff_v4</dimen>
+ <integer name="leanback_setup_translation_backward_out_content_delay">0</integer>
+ <integer name="leanback_setup_translation_backward_out_content_duration">@integer/leanback_setup_base_animation_duration</integer>
</resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 8065a9c..6262f13 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2117,6 +2117,11 @@
<string name="permdesc_bind_call_service">Allows the app to control when and how the user sees the in-call screen.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_bind_connection_service">interact with telephony services</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_bind_connection_service">Allows the app to interact with telephony services to make/receive calls.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_readNetworkUsageHistory">read historical network usage</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_readNetworkUsageHistory">Allows the app to read historical network usage for specific networks and apps.</string>
diff --git a/core/res/res/values/styles_leanback.xml b/core/res/res/values/styles_leanback.xml
index 72735f7..da83c36 100644
--- a/core/res/res/values/styles_leanback.xml
+++ b/core/res/res/values/styles_leanback.xml
@@ -65,4 +65,10 @@
<item name="fontFamily">sans-serif-condensed</item>
</style>
+ <style name="WindowAnimationStyle.Leanback.Setup" parent="@style/Animation.Material.Activity">
+ <item name="android:fragmentOpenEnterAnimation">@animator/leanback_setup_fragment_open_enter</item>
+ <item name="android:fragmentOpenExitAnimation">@animator/leanback_setup_fragment_open_exit</item>
+ <item name="android:fragmentCloseEnterAnimation">@animator/leanback_setup_fragment_close_enter</item>
+ <item name="android:fragmentCloseExitAnimation">@animator/leanback_setup_fragment_close_exit</item>
+ </style>
</resources>
diff --git a/core/res/res/values/themes_leanback.xml b/core/res/res/values/themes_leanback.xml
index 1cda843..0a2c0a4 100644
--- a/core/res/res/values/themes_leanback.xml
+++ b/core/res/res/values/themes_leanback.xml
@@ -58,5 +58,6 @@
<item name="textAppearanceListItem">@style/TextAppearance.Leanback.FormWizard.ListItem</item>
<item name="textAppearance">@style/TextAppearance.Leanback.FormWizard</item>
<item name="textColorPrimary">@color/primary_text_leanback_formwizard_dark</item>
+ <item name="windowAnimationStyle">@style/WindowAnimationStyle.Leanback.Setup</item>
</style>
</resources>
diff --git a/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java b/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java
index fc38e8a..14aa570 100644
--- a/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java
@@ -119,11 +119,11 @@
*
* @param fromId Unique identifier of the starting keyframe
* @param toId Unique identifier of the ending keyframe
- * @param transition An animatable drawable to use as a transition, may not be null
+ * @param transition An {@link Animatable} drawable to use as a transition, may not be null
* @param reversible Whether the transition can be reversed
*/
- public void addTransition(int fromId, int toId, @NonNull Drawable transition,
- boolean reversible) {
+ public <T extends Drawable & Animatable> void addTransition(int fromId, int toId,
+ @NonNull T transition, boolean reversible) {
if (transition == null) {
throw new IllegalArgumentException("Transition drawable must not be null");
}
diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java
index 766e681..9ac6927 100644
--- a/graphics/java/android/graphics/drawable/VectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/VectorDrawable.java
@@ -49,7 +49,7 @@
import java.util.Stack;
/**
- * This lets you create a drawable based on an XML vector graphic It can be
+ * This lets you create a drawable based on an XML vector graphic. It can be
* defined in an XML file with the <code><vector></code> element.
* <p/>
* The vector drawable has the following elements:
diff --git a/graphics/java/android/graphics/drawable/shapes/Shape.java b/graphics/java/android/graphics/drawable/shapes/Shape.java
index 589fbaa..eab8666 100644
--- a/graphics/java/android/graphics/drawable/shapes/Shape.java
+++ b/graphics/java/android/graphics/drawable/shapes/Shape.java
@@ -16,6 +16,7 @@
package android.graphics.drawable.shapes;
+import android.annotation.NonNull;
import android.graphics.Canvas;
import android.graphics.Outline;
import android.graphics.Paint;
@@ -93,11 +94,12 @@
protected void onResize(float width, float height) {}
/**
- * Compute the Outline of the shape.
+ * Compute the Outline of the shape and return it in the supplied Outline
+ * parameter. The default implementation does nothing and {@code outline} is not changed.
*
- * The default implementation does not supply an outline.
+ * @param outline The Outline to be populated with the result. Should not be null.
*/
- public void getOutline(Outline outline) {}
+ public void getOutline(@NonNull Outline outline) {}
@Override
public Shape clone() throws CloneNotSupportedException {
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 1316cb8..2a4dec0 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -2860,16 +2860,17 @@
struct ResTable::Package
{
Package(ResTable* _owner, const Header* _header, const ResTable_package* _package)
- : owner(_owner), header(_header), typeIdOffset(0) {
- if (_package != NULL && dtohs(_package->header.headerSize) == sizeof(_package)) {
+ : owner(_owner), header(_header), package(_package), typeIdOffset(0) {
+ if (dtohs(package->header.headerSize) == sizeof(package)) {
// The package structure is the same size as the definition.
// This means it contains the typeIdOffset field.
- typeIdOffset = _package->typeIdOffset;
+ typeIdOffset = package->typeIdOffset;
}
}
const ResTable* const owner;
const Header* const header;
+ const ResTable_package* const package;
ResStringPool typeStrings;
ResStringPool keyStrings;
@@ -3368,10 +3369,6 @@
header->header = (const ResTable_header*) resHeader;
mHeaders.add(header);
-
- PackageGroup* pg = new PackageGroup(this, String16(), 0);
- pg->packages.add(new Package(this, header, NULL));
- mPackageGroups.add(pg);
return (mError=NO_ERROR);
}
@@ -5940,7 +5937,7 @@
*outSize += 2 * sizeof(uint16_t);
// overlay packages are assumed to contain only one package group
- const String16 overlayPackage(overlay.mPackageGroups[0]->name);
+ const String16 overlayPackage(overlay.mPackageGroups[0]->packages[0]->package->name);
for (size_t typeIndex = 0; typeIndex < pg->types.size(); ++typeIndex) {
const TypeList& typeList = pg->types[typeIndex];
@@ -6223,6 +6220,13 @@
(int)pgIndex, pg->id, (int)pg->packages.size(),
String8(pg->name).string());
+ size_t pkgCount = pg->packages.size();
+ for (size_t pkgIndex=0; pkgIndex<pkgCount; pkgIndex++) {
+ const Package* pkg = pg->packages[pkgIndex];
+ printf(" Package %d id=%d name=%s\n", (int)pkgIndex,
+ pkg->package->id, String8(String16(pkg->package->name)).string());
+ }
+
for (size_t typeIndex=0; typeIndex < pg->types.size(); typeIndex++) {
const TypeList& typeList = pg->types[typeIndex];
if (typeList.isEmpty()) {
diff --git a/libs/androidfw/tests/ResTable_test.cpp b/libs/androidfw/tests/ResTable_test.cpp
index 68c228e..89d271d0 100644
--- a/libs/androidfw/tests/ResTable_test.cpp
+++ b/libs/androidfw/tests/ResTable_test.cpp
@@ -196,17 +196,16 @@
}
TEST(ResTableTest, emptyTableHasSensibleDefaults) {
- const int32_t expectedCookie = 1;
+ const int32_t assetCookie = 1;
ResTable table;
- ASSERT_EQ(NO_ERROR, table.addEmpty(expectedCookie));
+ ASSERT_EQ(NO_ERROR, table.addEmpty(assetCookie));
+ // Adding an empty table gives us one table!
ASSERT_EQ(uint32_t(1), table.getTableCount());
- ASSERT_EQ(uint32_t(1), table.getBasePackageCount());
- ASSERT_EQ(expectedCookie, table.getTableCookie(0));
- const DynamicRefTable* dynamicRefTable = table.getDynamicRefTableForCookie(expectedCookie);
- ASSERT_TRUE(dynamicRefTable != NULL);
+ // Adding an empty table doesn't mean we get packages.
+ ASSERT_EQ(uint32_t(0), table.getBasePackageCount());
Res_value val;
ASSERT_LT(table.getResource(base::R::integer::number1, &val, MAY_NOT_BE_BAG), 0);
diff --git a/libs/hwui/font/Font.cpp b/libs/hwui/font/Font.cpp
index 2ea6c8c..ba878bac 100644
--- a/libs/hwui/font/Font.cpp
+++ b/libs/hwui/font/Font.cpp
@@ -140,12 +140,12 @@
void Font::measureCachedGlyph(CachedGlyphInfo *glyph, int x, int y,
uint8_t* bitmap, uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, const float* pos) {
- int nPenX = x + glyph->mBitmapLeft;
- int nPenY = y + glyph->mBitmapTop;
-
int width = (int) glyph->mBitmapWidth;
int height = (int) glyph->mBitmapHeight;
+ int nPenX = x + glyph->mBitmapLeft;
+ int nPenY = y + glyph->mBitmapTop;
+
if (bounds->bottom > nPenY) {
bounds->bottom = nPenY;
}
@@ -162,12 +162,12 @@
void Font::drawCachedGlyph(CachedGlyphInfo* glyph, int x, int y,
uint8_t* bitmap, uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, const float* pos) {
- float nPenX = x + glyph->mBitmapLeft;
- float nPenY = y + glyph->mBitmapTop + glyph->mBitmapHeight;
-
float width = (float) glyph->mBitmapWidth;
float height = (float) glyph->mBitmapHeight;
+ float nPenX = x + glyph->mBitmapLeft;
+ float nPenY = y + glyph->mBitmapTop + height;
+
float u1 = glyph->mBitmapMinU;
float u2 = glyph->mBitmapMaxU;
float v1 = glyph->mBitmapMinV;
@@ -181,10 +181,13 @@
void Font::drawCachedGlyphTransformed(CachedGlyphInfo* glyph, int x, int y,
uint8_t* bitmap, uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, const float* pos) {
+ float width = (float) glyph->mBitmapWidth;
+ float height = (float) glyph->mBitmapHeight;
+
SkPoint p[4];
- p[0].iset(glyph->mBitmapLeft, glyph->mBitmapTop + glyph->mBitmapHeight);
- p[1].iset(glyph->mBitmapLeft + glyph->mBitmapWidth, glyph->mBitmapTop + glyph->mBitmapHeight);
- p[2].iset(glyph->mBitmapLeft + glyph->mBitmapWidth, glyph->mBitmapTop);
+ p[0].iset(glyph->mBitmapLeft, glyph->mBitmapTop + height);
+ p[1].iset(glyph->mBitmapLeft + width, glyph->mBitmapTop + height);
+ p[2].iset(glyph->mBitmapLeft + width, glyph->mBitmapTop);
p[3].iset(glyph->mBitmapLeft, glyph->mBitmapTop);
mDescription.mInverseLookupTransform.mapPoints(p, 4);
diff --git a/media/java/android/media/session/MediaSessionLegacyHelper.java b/media/java/android/media/session/MediaSessionLegacyHelper.java
index a182982..aa196a9 100644
--- a/media/java/android/media/session/MediaSessionLegacyHelper.java
+++ b/media/java/android/media/session/MediaSessionLegacyHelper.java
@@ -290,7 +290,6 @@
if (DEBUG) {
Log.d(TAG, "addMediaButtonListener already added " + pi);
}
- return;
}
holder.mMediaButtonListener = new MediaButtonListener(pi, context);
// TODO determine if handling transport performer commands should also
@@ -468,7 +467,11 @@
mSessions.remove(mPi);
} else if (mCb == null) {
mCb = new SessionCallback();
- mSession.setCallback(mCb);
+ Handler handler = null;
+ if (Looper.myLooper() == null) {
+ handler = new Handler(Looper.getMainLooper());
+ }
+ mSession.setCallback(mCb, handler);
}
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 5dc7d26..87c015c 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -20,6 +20,7 @@
import java.security.SecureRandom;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
@@ -94,6 +95,9 @@
// Each defined user has their own settings
protected final SparseArray<DatabaseHelper> mOpenHelpers = new SparseArray<DatabaseHelper>();
+ // Keep the list of managed profiles synced here
+ private List<UserInfo> mManagedProfiles = null;
+
// Over this size we don't reject loading or saving settings but
// we do consider them broken/malicious and don't keep them in
// memory at least:
@@ -119,6 +123,9 @@
private static final String DROPBOX_TAG_USERLOG = "restricted_profile_ssaid";
+ static final HashSet<String> sSecureCloneToManagedKeys;
+ static final HashSet<String> sSystemCloneToManagedKeys;
+
static {
// Keys (name column) from the 'secure' table that are now in the owner user's 'global'
// table, shared across all users
@@ -142,6 +149,15 @@
UserManager.ENSURE_VERIFY_APPS);
sRestrictedKeys.put(Settings.Global.PREFERRED_NETWORK_MODE,
UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS);
+
+ sSecureCloneToManagedKeys = new HashSet<String>();
+ for (int i = 0; i < Settings.Secure.CLONE_TO_MANAGED_PROFILE.length; i++) {
+ sSecureCloneToManagedKeys.add(Settings.Secure.CLONE_TO_MANAGED_PROFILE[i]);
+ }
+ sSystemCloneToManagedKeys = new HashSet<String>();
+ for (int i = 0; i < Settings.System.CLONE_TO_MANAGED_PROFILE.length; i++) {
+ sSystemCloneToManagedKeys.add(Settings.System.CLONE_TO_MANAGED_PROFILE[i]);
+ }
}
private boolean settingMovedToGlobal(final String name) {
@@ -362,18 +378,22 @@
IntentFilter userFilter = new IntentFilter();
userFilter.addAction(Intent.ACTION_USER_REMOVED);
+ userFilter.addAction(Intent.ACTION_USER_ADDED);
getContext().registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
+ final int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
+ UserHandle.USER_OWNER);
if (intent.getAction().equals(Intent.ACTION_USER_REMOVED)) {
- final int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
- UserHandle.USER_OWNER);
- if (userHandle != UserHandle.USER_OWNER) {
- onUserRemoved(userHandle);
- }
+ onUserRemoved(userHandle);
+ } else if (intent.getAction().equals(Intent.ACTION_USER_ADDED)) {
+ onProfilesChanged();
}
}
}, userFilter);
+
+ onProfilesChanged();
+
return true;
}
@@ -391,6 +411,32 @@
sSystemCaches.delete(userHandle);
sSecureCaches.delete(userHandle);
sKnownMutationsInFlight.delete(userHandle);
+ onProfilesChanged();
+ }
+ }
+
+ /**
+ * Updates the list of managed profiles. It assumes that only the primary user
+ * can have managed profiles. Modify this code if that changes in the future.
+ */
+ void onProfilesChanged() {
+ synchronized (this) {
+ mManagedProfiles = mUserManager.getProfiles(UserHandle.USER_OWNER);
+ if (mManagedProfiles != null) {
+ // Remove the primary user from the list
+ for (int i = mManagedProfiles.size() - 1; i >= 0; i--) {
+ if (mManagedProfiles.get(i).id == UserHandle.USER_OWNER) {
+ mManagedProfiles.remove(i);
+ }
+ }
+ // If there are no managed profiles, reset the variable
+ if (mManagedProfiles.size() == 0) {
+ mManagedProfiles = null;
+ }
+ }
+ if (LOCAL_LOGV) {
+ Slog.d(TAG, "Managed Profiles = " + mManagedProfiles);
+ }
}
}
@@ -601,6 +647,24 @@
}
/**
+ * Checks if the calling user is a managed profile of the primary user.
+ * Currently only the primary user (USER_OWNER) can have managed profiles.
+ * @param callingUser the user trying to read/write settings
+ * @return true if it is a managed profile of the primary user
+ */
+ private boolean isManagedProfile(int callingUser) {
+ synchronized (this) {
+ if (mManagedProfiles == null) return false;
+ for (int i = mManagedProfiles.size() - 1; i >= 0; i--) {
+ if (mManagedProfiles.get(i).id == callingUser) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ /**
* Fast path that avoids the use of chatty remoted Cursors.
*/
@Override
@@ -625,12 +689,18 @@
// Get methods
if (Settings.CALL_METHOD_GET_SYSTEM.equals(method)) {
if (LOCAL_LOGV) Slog.v(TAG, "call(system:" + request + ") for " + callingUser);
+ if (isManagedProfile(callingUser) && sSystemCloneToManagedKeys.contains(request)) {
+ callingUser = UserHandle.USER_OWNER;
+ }
dbHelper = getOrEstablishDatabase(callingUser);
cache = sSystemCaches.get(callingUser);
return lookupValue(dbHelper, TABLE_SYSTEM, cache, request);
}
if (Settings.CALL_METHOD_GET_SECURE.equals(method)) {
if (LOCAL_LOGV) Slog.v(TAG, "call(secure:" + request + ") for " + callingUser);
+ if (isManagedProfile(callingUser) && sSecureCloneToManagedKeys.contains(request)) {
+ callingUser = UserHandle.USER_OWNER;
+ }
dbHelper = getOrEstablishDatabase(callingUser);
cache = sSecureCaches.get(callingUser);
return lookupValue(dbHelper, TABLE_SECURE, cache, request);
@@ -667,13 +737,70 @@
values.put(Settings.NameValueTable.NAME, request);
values.put(Settings.NameValueTable.VALUE, newValue);
if (Settings.CALL_METHOD_PUT_SYSTEM.equals(method)) {
- if (LOCAL_LOGV) Slog.v(TAG, "call_put(system:" + request + "=" + newValue + ") for " + callingUser);
+ if (LOCAL_LOGV) {
+ Slog.v(TAG, "call_put(system:" + request + "=" + newValue + ") for "
+ + callingUser);
+ }
+ // Extra check for USER_OWNER to optimize for the 99%
+ if (callingUser != UserHandle.USER_OWNER && isManagedProfile(callingUser)) {
+ if (sSystemCloneToManagedKeys.contains(request)) {
+ // Don't write these settings
+ return null;
+ }
+ }
insertForUser(Settings.System.CONTENT_URI, values, callingUser);
+ // Clone the settings to the managed profiles so that notifications can be sent out
+ if (callingUser == UserHandle.USER_OWNER && mManagedProfiles != null
+ && sSystemCloneToManagedKeys.contains(request)) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ for (int i = mManagedProfiles.size() - 1; i >= 0; i--) {
+ if (LOCAL_LOGV) {
+ Slog.v(TAG, "putting to additional user "
+ + mManagedProfiles.get(i).id);
+ }
+ insertForUser(Settings.System.CONTENT_URI, values,
+ mManagedProfiles.get(i).id);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
} else if (Settings.CALL_METHOD_PUT_SECURE.equals(method)) {
- if (LOCAL_LOGV) Slog.v(TAG, "call_put(secure:" + request + "=" + newValue + ") for " + callingUser);
+ if (LOCAL_LOGV) {
+ Slog.v(TAG, "call_put(secure:" + request + "=" + newValue + ") for "
+ + callingUser);
+ }
+ // Extra check for USER_OWNER to optimize for the 99%
+ if (callingUser != UserHandle.USER_OWNER && isManagedProfile(callingUser)) {
+ if (sSecureCloneToManagedKeys.contains(request)) {
+ // Don't write these settings
+ return null;
+ }
+ }
insertForUser(Settings.Secure.CONTENT_URI, values, callingUser);
+ // Clone the settings to the managed profiles so that notifications can be sent out
+ if (callingUser == UserHandle.USER_OWNER && mManagedProfiles != null
+ && sSecureCloneToManagedKeys.contains(request)) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ for (int i = mManagedProfiles.size() - 1; i >= 0; i--) {
+ if (LOCAL_LOGV) {
+ Slog.v(TAG, "putting to additional user "
+ + mManagedProfiles.get(i).id);
+ }
+ insertForUser(Settings.Secure.CONTENT_URI, values,
+ mManagedProfiles.get(i).id);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
} else if (Settings.CALL_METHOD_PUT_GLOBAL.equals(method)) {
- if (LOCAL_LOGV) Slog.v(TAG, "call_put(global:" + request + "=" + newValue + ") for " + callingUser);
+ if (LOCAL_LOGV) {
+ Slog.v(TAG, "call_put(global:" + request + "=" + newValue + ") for "
+ + callingUser);
+ }
insertForUser(Settings.Global.CONTENT_URI, values, callingUser);
} else {
Slog.w(TAG, "call() with invalid method: " + method);
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index c24fcae..d145172 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -709,13 +709,13 @@
<string name="guest_wipe_session_title">Welcome back, guest!</string>
<!-- Message of the notification when resuming an existing guest session [CHAR LIMIT=NONE] -->
- <string name="guest_wipe_session_message">Do you want to start a new session?</string>
+ <string name="guest_wipe_session_message">Do you want to continue your session?</string>
<!-- Notification when resuming an existing guest session: Action that starts a new session [CHAR LIMIT=35] -->
- <string name="guest_wipe_session_wipe">Yes</string>
+ <string name="guest_wipe_session_wipe">Start over</string>
<!-- Notification when resuming an existing guest session: Action that continues with the current session [CHAR LIMIT=35] -->
- <string name="guest_wipe_session_dontwipe">No, thanks</string>
+ <string name="guest_wipe_session_dontwipe">Yes, continue</string>
<!-- Zen mode condition: time duration in minutes. [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java
index 2113c68..9fbcd7f 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java
@@ -59,6 +59,7 @@
private boolean mAutomatic;
private boolean mListening;
+ private boolean mExternalChange;
public interface BrightnessStateChangeCallback {
public void onBrightnessLevelChanged();
@@ -86,19 +87,24 @@
@Override
public void onChange(boolean selfChange, Uri uri) {
if (selfChange) return;
- if (BRIGHTNESS_MODE_URI.equals(uri)) {
- updateMode();
- updateSlider();
- } else if (BRIGHTNESS_URI.equals(uri) && !mAutomatic) {
- updateSlider();
- } else if (BRIGHTNESS_ADJ_URI.equals(uri) && mAutomatic) {
- updateSlider();
- } else {
- updateMode();
- updateSlider();
- }
- for (BrightnessStateChangeCallback cb : mChangeCallbacks) {
- cb.onBrightnessLevelChanged();
+ try {
+ mExternalChange = true;
+ if (BRIGHTNESS_MODE_URI.equals(uri)) {
+ updateMode();
+ updateSlider();
+ } else if (BRIGHTNESS_URI.equals(uri) && !mAutomatic) {
+ updateSlider();
+ } else if (BRIGHTNESS_ADJ_URI.equals(uri) && mAutomatic) {
+ updateSlider();
+ } else {
+ updateMode();
+ updateSlider();
+ }
+ for (BrightnessStateChangeCallback cb : mChangeCallbacks) {
+ cb.onBrightnessLevelChanged();
+ }
+ } finally {
+ mExternalChange = false;
}
}
@@ -191,6 +197,8 @@
@Override
public void onChanged(ToggleSlider view, boolean tracking, boolean automatic, int value) {
updateIcon(mAutomatic);
+ if (mExternalChange) return;
+
if (!mAutomatic) {
final int val = value + mMinimumBacklight;
setBrightness(val);
diff --git a/packages/SystemUI/src/com/android/systemui/settings/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/BrightnessDialog.java
index d113139..a1704ff 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/BrightnessDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/BrightnessDialog.java
@@ -40,35 +40,27 @@
super.onCreate(savedInstanceState);
final Window window = getWindow();
- final WindowManager.LayoutParams lp = window.getAttributes();
- // Offset from the top
- lp.y = getResources().getDimensionPixelOffset(R.dimen.volume_panel_top);
- lp.type = WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY;
- lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
-
- window.setAttributes(lp);
window.setGravity(Gravity.TOP);
window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
window.requestFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.quick_settings_brightness_dialog);
+
+ final ImageView icon = (ImageView) findViewById(R.id.brightness_icon);
+ final ToggleSlider slider = (ToggleSlider) findViewById(R.id.brightness_slider);
+ mBrightnessController = new BrightnessController(this, icon, slider);
}
@Override
protected void onStart() {
super.onStart();
-
- final ImageView icon = (ImageView) findViewById(R.id.brightness_icon);
- final ToggleSlider slider = (ToggleSlider) findViewById(R.id.brightness_slider);
- mBrightnessController = new BrightnessController(this, icon, slider);
mBrightnessController.registerCallbacks();
}
@Override
protected void onStop() {
super.onStop();
-
mBrightnessController.unregisterCallbacks();
}
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index b2d1b71..c44474d 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -8599,7 +8599,15 @@
skip = true;
}
- if (!skip && mAutoRestore && mProvisioned) {
+ if (!mAutoRestore || !mProvisioned) {
+ if (DEBUG) {
+ Slog.w(TAG, "Non-restorable state: auto=" + mAutoRestore
+ + " prov=" + mProvisioned);
+ }
+ skip = true;
+ }
+
+ if (!skip) {
try {
// okay, we're going to attempt a restore of this package from this restore set.
// The eventual message back into the Package Manager to run the post-install
@@ -8632,7 +8640,7 @@
if (skip) {
// Auto-restore disabled or no way to attempt a restore; just tell the Package
// Manager to proceed with the post-install handling for this package.
- if (DEBUG) Slog.v(TAG, "Skipping");
+ if (DEBUG) Slog.v(TAG, "Finishing install immediately");
try {
mPackageManagerBinder.finishPackageInstall(token);
} catch (RemoteException e) { /* can't happen */ }
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index dd5a7ea..02695c5 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -1534,11 +1534,17 @@
return;
}
- if (mtu < 68 || mtu > 10000) {
+ if (LinkProperties.isValidMtu(mtu, newLp.hasGlobalIPv6Address())) {
loge("Unexpected mtu value: " + mtu + ", " + iface);
return;
}
+ // Cannot set MTU without interface name
+ if (TextUtils.isEmpty(iface)) {
+ loge("Setting MTU size with null iface.");
+ return;
+ }
+
try {
if (VDBG) log("Setting MTU size: " + iface + ", " + mtu);
mNetd.setMtu(iface, mtu);
diff --git a/services/core/java/com/android/server/NetworkScoreService.java b/services/core/java/com/android/server/NetworkScoreService.java
index ab4d4dc..395e365 100644
--- a/services/core/java/com/android/server/NetworkScoreService.java
+++ b/services/core/java/com/android/server/NetworkScoreService.java
@@ -17,9 +17,9 @@
package com.android.server;
import android.Manifest.permission;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
-import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.net.INetworkScoreCache;
import android.net.INetworkScoreService;
@@ -30,6 +30,7 @@
import android.os.Binder;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
@@ -51,9 +52,6 @@
public class NetworkScoreService extends INetworkScoreService.Stub {
private static final String TAG = "NetworkScoreService";
- /** SharedPreference bit set to true after the service is first initialized. */
- private static final String PREF_SCORING_PROVISIONED = "is_provisioned";
-
private final Context mContext;
private final Map<Integer, INetworkScoreCache> mScoreCaches;
@@ -65,8 +63,8 @@
/** Called when the system is ready to run third-party code but before it actually does so. */
void systemReady() {
- SharedPreferences prefs = mContext.getSharedPreferences(TAG, Context.MODE_PRIVATE);
- if (!prefs.getBoolean(PREF_SCORING_PROVISIONED, false)) {
+ ContentResolver cr = mContext.getContentResolver();
+ if (Settings.Global.getInt(cr, Settings.Global.NETWORK_SCORING_PROVISIONED, 0) == 0) {
// On first run, we try to initialize the scorer to the one configured at build time.
// This will be a no-op if the scorer isn't actually valid.
String defaultPackage = mContext.getResources().getString(
@@ -74,7 +72,7 @@
if (!TextUtils.isEmpty(defaultPackage)) {
NetworkScorerAppManager.setActiveScorer(mContext, defaultPackage);
}
- prefs.edit().putBoolean(PREF_SCORING_PROVISIONED, true).apply();
+ Settings.Global.putInt(cr, Settings.Global.NETWORK_SCORING_PROVISIONED, 1);
}
}
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index b7b5f98..758f334 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -169,24 +169,19 @@
return false;
}
}
- // audience has veto power over all following rules
- if (!audienceMatches(record)) {
- ZenLog.traceIntercepted(record, "!audienceMatches");
- return true;
- }
if (isCall(record)) {
if (!mConfig.allowCalls) {
ZenLog.traceIntercepted(record, "!allowCalls");
return true;
}
- return false;
+ return shouldInterceptAudience(record);
}
if (isMessage(record)) {
if (!mConfig.allowMessages) {
ZenLog.traceIntercepted(record, "!allowMessages");
return true;
}
- return false;
+ return shouldInterceptAudience(record);
}
ZenLog.traceIntercepted(record, "!allowed");
return true;
@@ -194,6 +189,14 @@
return false;
}
+ private boolean shouldInterceptAudience(NotificationRecord record) {
+ if (!audienceMatches(record)) {
+ ZenLog.traceIntercepted(record, "!audienceMatches");
+ return true;
+ }
+ return false;
+ }
+
public int getZenMode() {
return mZenMode;
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index c7e3fb7..dca8ad4 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -36,19 +36,23 @@
import android.app.ActivityManager;
import android.app.AppOpsManager;
+import android.app.PackageDeleteObserver;
+import android.app.PackageInstallObserver;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.IPackageDeleteObserver2;
+import android.content.IntentSender;
+import android.content.IntentSender.SendIntentException;
import android.content.pm.IPackageInstaller;
import android.content.pm.IPackageInstallerCallback;
import android.content.pm.IPackageInstallerSession;
-import android.content.pm.InstallSessionInfo;
-import android.content.pm.InstallSessionParams;
import android.content.pm.PackageInstaller;
+import android.content.pm.PackageInstaller.SessionInfo;
+import android.content.pm.PackageInstaller.SessionParams;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Binder;
+import android.os.Bundle;
import android.os.Environment;
import android.os.FileUtils;
import android.os.Handler;
@@ -63,6 +67,7 @@
import android.os.UserManager;
import android.system.ErrnoException;
import android.system.Os;
+import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.ArraySet;
import android.util.AtomicFile;
@@ -279,8 +284,8 @@
final File sessionStageDir = new File(readStringAttribute(in, ATTR_SESSION_STAGE_DIR));
final boolean sealed = readBooleanAttribute(in, ATTR_SEALED);
- final InstallSessionParams params = new InstallSessionParams(
- InstallSessionParams.MODE_INVALID);
+ final SessionParams params = new SessionParams(
+ SessionParams.MODE_INVALID);
params.mode = readIntAttribute(in, ATTR_MODE);
params.installFlags = readIntAttribute(in, ATTR_INSTALL_FLAGS);
params.installLocation = readIntAttribute(in, ATTR_INSTALL_LOCATION);
@@ -292,9 +297,9 @@
params.referrerUri = readUriAttribute(in, ATTR_REFERRER_URI);
params.abiOverride = readStringAttribute(in, ATTR_ABI_OVERRIDE);
- return new PackageInstallerSession(mInternalCallback, mPm, mInstallThread.getLooper(),
- sessionId, userId, installerPackageName, params, createdMillis, sessionStageDir,
- sealed);
+ return new PackageInstallerSession(mInternalCallback, mContext, mPm,
+ mInstallThread.getLooper(), sessionId, userId, installerPackageName, params,
+ createdMillis, sessionStageDir, sealed);
}
private void writeSessionsLocked() {
@@ -326,7 +331,7 @@
private void writeSessionLocked(XmlSerializer out, PackageInstallerSession session)
throws IOException {
- final InstallSessionParams params = session.params;
+ final SessionParams params = session.params;
final Snapshot snapshot = session.snapshot();
out.startTag(null, TAG_SESSION);
@@ -366,7 +371,7 @@
}
@Override
- public int createSession(InstallSessionParams params, String installerPackageName, int userId) {
+ public int createSession(SessionParams params, String installerPackageName, int userId) {
final int callingUid = Binder.getCallingUid();
mPm.enforceCrossUserPermission(callingUid, userId, true, "createSession");
@@ -389,8 +394,8 @@
}
switch (params.mode) {
- case InstallSessionParams.MODE_FULL_INSTALL:
- case InstallSessionParams.MODE_INHERIT_EXISTING:
+ case SessionParams.MODE_FULL_INSTALL:
+ case SessionParams.MODE_INHERIT_EXISTING:
break;
default:
throw new IllegalArgumentException("Params must have valid mode set");
@@ -437,7 +442,7 @@
final long createdMillis = System.currentTimeMillis();
final File sessionStageDir = prepareSessionStageDir(sessionId);
- session = new PackageInstallerSession(mInternalCallback, mPm,
+ session = new PackageInstallerSession(mInternalCallback, mContext, mPm,
mInstallThread.getLooper(), sessionId, userId, installerPackageName, params,
createdMillis, sessionStageDir, false);
mSessions.put(sessionId, session);
@@ -501,7 +506,7 @@
}
@Override
- public InstallSessionInfo getSessionInfo(int sessionId) {
+ public SessionInfo getSessionInfo(int sessionId) {
synchronized (mSessions) {
final PackageInstallerSession session = mSessions.get(sessionId);
if (!isCallingUidOwner(session)) {
@@ -512,11 +517,11 @@
}
@Override
- public List<InstallSessionInfo> getAllSessions(int userId) {
+ public List<SessionInfo> getAllSessions(int userId) {
mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "getAllSessions");
enforceCallerCanReadSessions();
- final List<InstallSessionInfo> result = new ArrayList<>();
+ final List<SessionInfo> result = new ArrayList<>();
synchronized (mSessions) {
for (int i = 0; i < mSessions.size(); i++) {
final PackageInstallerSession session = mSessions.valueAt(i);
@@ -529,11 +534,11 @@
}
@Override
- public List<InstallSessionInfo> getMySessions(String installerPackageName, int userId) {
+ public List<SessionInfo> getMySessions(String installerPackageName, int userId) {
mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "getMySessions");
mAppOps.checkPackage(Binder.getCallingUid(), installerPackageName);
- final List<InstallSessionInfo> result = new ArrayList<>();
+ final List<SessionInfo> result = new ArrayList<>();
synchronized (mSessions) {
for (int i = 0; i < mSessions.size(); i++) {
final PackageInstallerSession session = mSessions.valueAt(i);
@@ -547,37 +552,26 @@
}
@Override
- public void uninstall(String packageName, int flags, IPackageDeleteObserver2 observer,
- int userId) {
+ public void uninstall(String packageName, int flags, IntentSender statusReceiver, int userId) {
mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "uninstall");
+ final PackageDeleteObserverAdapter adapter = new PackageDeleteObserverAdapter(mContext,
+ statusReceiver);
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DELETE_PACKAGES)
== PackageManager.PERMISSION_GRANTED) {
// Sweet, call straight through!
- mPm.deletePackage(packageName, observer, userId, flags);
+ mPm.deletePackage(packageName, adapter.getBinder(), userId, flags);
} else {
// Take a short detour to confirm with user
final Intent intent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE);
intent.setData(Uri.fromParts("package", packageName, null));
- intent.putExtra(PackageInstaller.EXTRA_CALLBACK, observer.asBinder());
- try {
- observer.onUserActionRequired(intent);
- } catch (RemoteException ignored) {
- }
+ intent.putExtra(PackageInstaller.EXTRA_CALLBACK, adapter.getBinder().asBinder());
+ adapter.onUserActionRequired(intent);
}
}
@Override
- public void uninstallSplit(String basePackageName, String overlayName, int flags,
- IPackageDeleteObserver2 observer, int userId) {
- mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "uninstallSplit");
-
- // TODO: flesh out once PM has split support
- throw new UnsupportedOperationException();
- }
-
- @Override
public void setPermissionsResult(int sessionId, boolean accepted) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES, TAG);
@@ -636,6 +630,87 @@
}
}
+ static class PackageDeleteObserverAdapter extends PackageDeleteObserver {
+ private final Context mContext;
+ private final IntentSender mTarget;
+
+ public PackageDeleteObserverAdapter(Context context, IntentSender target) {
+ mContext = context;
+ mTarget = target;
+ }
+
+ @Override
+ public void onUserActionRequired(Intent intent) {
+ final Intent fillIn = new Intent();
+ fillIn.putExtra(PackageInstaller.EXTRA_STATUS,
+ PackageInstaller.STATUS_USER_ACTION_REQUIRED);
+ fillIn.putExtra(Intent.EXTRA_INTENT, intent);
+ try {
+ mTarget.sendIntent(mContext, 0, fillIn, null, null);
+ } catch (SendIntentException ignored) {
+ }
+ }
+
+ @Override
+ public void onPackageDeleted(String basePackageName, int returnCode, String msg) {
+ final Intent fillIn = new Intent();
+ fillIn.putExtra(PackageInstaller.EXTRA_STATUS,
+ PackageManager.deleteStatusToPublicStatus(returnCode));
+ fillIn.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE,
+ PackageManager.deleteStatusToString(returnCode, msg));
+ fillIn.putExtra(PackageInstaller.EXTRA_LEGACY_STATUS, returnCode);
+ try {
+ mTarget.sendIntent(mContext, 0, fillIn, null, null);
+ } catch (SendIntentException ignored) {
+ }
+ }
+ }
+
+ static class PackageInstallObserverAdapter extends PackageInstallObserver {
+ private final Context mContext;
+ private final IntentSender mTarget;
+
+ public PackageInstallObserverAdapter(Context context, IntentSender target) {
+ mContext = context;
+ mTarget = target;
+ }
+
+ @Override
+ public void onUserActionRequired(Intent intent) {
+ final Intent fillIn = new Intent();
+ fillIn.putExtra(PackageInstaller.EXTRA_STATUS,
+ PackageInstaller.STATUS_USER_ACTION_REQUIRED);
+ fillIn.putExtra(Intent.EXTRA_INTENT, intent);
+ try {
+ mTarget.sendIntent(mContext, 0, fillIn, null, null);
+ } catch (SendIntentException ignored) {
+ }
+ }
+
+ @Override
+ public void onPackageInstalled(String basePackageName, int returnCode, String msg,
+ Bundle extras) {
+ final Intent fillIn = new Intent();
+ fillIn.putExtra(PackageInstaller.EXTRA_STATUS,
+ PackageManager.installStatusToPublicStatus(returnCode));
+ fillIn.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE,
+ PackageManager.installStatusToString(returnCode, msg));
+ fillIn.putExtra(PackageInstaller.EXTRA_LEGACY_STATUS, returnCode);
+ if (extras != null) {
+ final String existing = extras.getString(
+ PackageManager.EXTRA_FAILURE_EXISTING_PACKAGE);
+ if (!TextUtils.isEmpty(existing)) {
+ fillIn.putExtra(PackageInstaller.EXTRA_PACKAGE_NAMES, new String[] {
+ existing });
+ }
+ }
+ try {
+ mTarget.sendIntent(mContext, 0, fillIn, null, null);
+ } catch (SendIntentException ignored) {
+ }
+ }
+ }
+
private static class Callbacks extends Handler {
private static final int MSG_SESSION_CREATED = 1;
private static final int MSG_SESSION_OPENED = 2;
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index a3184f0..5ef24f2 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -25,13 +25,15 @@
import static android.system.OsConstants.O_RDONLY;
import static android.system.OsConstants.O_WRONLY;
+import android.content.Context;
import android.content.Intent;
+import android.content.IntentSender;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageInstallObserver2;
import android.content.pm.IPackageInstallerSession;
-import android.content.pm.InstallSessionInfo;
-import android.content.pm.InstallSessionParams;
import android.content.pm.PackageInstaller;
+import android.content.pm.PackageInstaller.SessionInfo;
+import android.content.pm.PackageInstaller.SessionParams;
import android.content.pm.PackageManager;
import android.content.pm.PackageParser;
import android.content.pm.PackageParser.ApkLite;
@@ -59,6 +61,7 @@
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
+import com.android.server.pm.PackageInstallerService.PackageInstallObserverAdapter;
import libcore.io.Libcore;
@@ -81,13 +84,14 @@
// TODO: treat INHERIT_EXISTING as installExistingPackage()
private final PackageInstallerService.InternalCallback mCallback;
+ private final Context mContext;
private final PackageManagerService mPm;
private final Handler mHandler;
final int sessionId;
final int userId;
final String installerPackageName;
- final InstallSessionParams params;
+ final SessionParams params;
final long createdMillis;
final File sessionStageDir;
@@ -159,10 +163,11 @@
};
public PackageInstallerSession(PackageInstallerService.InternalCallback callback,
- PackageManagerService pm, Looper looper, int sessionId, int userId,
- String installerPackageName, InstallSessionParams params, long createdMillis,
+ Context context, PackageManagerService pm, Looper looper, int sessionId, int userId,
+ String installerPackageName, SessionParams params, long createdMillis,
File sessionStageDir, boolean sealed) {
mCallback = callback;
+ mContext = context;
mPm = pm;
mHandler = new Handler(looper, mHandlerCallback);
@@ -188,8 +193,8 @@
computeProgressLocked();
}
- public InstallSessionInfo generateInfo() {
- final InstallSessionInfo info = new InstallSessionInfo();
+ public SessionInfo generateInfo() {
+ final SessionInfo info = new SessionInfo();
info.sessionId = sessionId;
info.installerPackageName = installerPackageName;
@@ -246,8 +251,8 @@
}
@Override
- public String[] list() {
- assertNotSealed("list");
+ public String[] getNames() {
+ assertNotSealed("getNames");
return sessionStageDir.list();
}
@@ -337,9 +342,12 @@
}
@Override
- public void commit(IPackageInstallObserver2 observer) {
- Preconditions.checkNotNull(observer);
- mHandler.obtainMessage(MSG_COMMIT, observer).sendToTarget();
+ public void commit(IntentSender statusReceiver) {
+ Preconditions.checkNotNull(statusReceiver);
+
+ final PackageInstallObserverAdapter adapter = new PackageInstallObserverAdapter(mContext,
+ statusReceiver);
+ mHandler.obtainMessage(MSG_COMMIT, adapter.getBinder()).sendToTarget();
}
private void commitLocked() throws PackageManagerException {
@@ -385,7 +393,7 @@
// Inherit any packages and native libraries from existing install that
// haven't been overridden.
- if (params.mode == InstallSessionParams.MODE_INHERIT_EXISTING) {
+ if (params.mode == SessionParams.MODE_INHERIT_EXISTING) {
spliceExistingFilesIntoStage();
}
@@ -396,7 +404,6 @@
// We've reached point of no return; call into PMS to install the stage.
// Regardless of success or failure we always destroy session.
- final IPackageInstallObserver2 remoteObserver = mRemoteObserver;
final IPackageInstallObserver2 localObserver = new IPackageInstallObserver2.Stub() {
@Override
public void onUserActionRequired(Intent intent) {
@@ -488,7 +495,7 @@
// currently relying on PMS to do this.
// TODO: teach about compatible upgrade keysets.
- if (params.mode == InstallSessionParams.MODE_FULL_INSTALL) {
+ if (params.mode == SessionParams.MODE_FULL_INSTALL) {
// Full installs must include a base package
if (!seenSplits.contains(null)) {
throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 304441c..63f3c0f 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -107,12 +107,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.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.LegacyPackageDeleteObserver;
import android.content.pm.PackageParser.ActivityIntentInfo;
@@ -7846,7 +7846,7 @@
}
void installStage(String packageName, File stageDir, IPackageInstallObserver2 observer,
- InstallSessionParams params, String installerPackageName, int installerUid,
+ PackageInstaller.SessionParams params, String installerPackageName, int installerUid,
UserHandle user) {
final VerificationParams verifParams = new VerificationParams(null, params.originatingUri,
params.referrerUri, installerUid, null);
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 39c6e0e..db19285 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -58,8 +58,6 @@
import android.os.SystemService;
import android.os.UserHandle;
import android.os.WorkSource;
-import android.os.Parcel;
-import android.os.ServiceManager;
import android.provider.Settings;
import android.service.dreams.DreamManagerInternal;
import android.util.EventLog;
@@ -709,7 +707,6 @@
if (mLowPowerModeEnabled != lowPowerModeEnabled) {
mLowPowerModeEnabled = lowPowerModeEnabled;
powerHintInternal(POWER_HINT_LOW_POWER_MODE, lowPowerModeEnabled ? 1 : 0);
- setSurfaceFlingerLowPowerMode(lowPowerModeEnabled ? 1 : 0);
mLowPowerModeEnabled = lowPowerModeEnabled;
BackgroundThread.getHandler().post(new Runnable() {
@Override
@@ -2198,21 +2195,6 @@
nativeSendPowerHint(hintId, data);
}
- private static void setSurfaceFlingerLowPowerMode(int enabled) {
- try {
- final IBinder flinger = ServiceManager.getService("SurfaceFlinger");
- if (flinger != null) {
- final Parcel data = Parcel.obtain();
- data.writeInterfaceToken("android.ui.ISurfaceComposer");
- data.writeInt(enabled);
- flinger.transact(1016, data, null, 0);
- data.recycle();
- }
- } catch (RemoteException ex) {
- Slog.e(TAG, "Failed to reduce refresh rate", ex);
- }
- }
-
/**
* Low-level function turn the device off immediately, without trying
* to be clean. Most people should use {@link ShutdownThread} for a clean shutdown.
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index fc96991..d46ae42 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -19,6 +19,7 @@
import static android.Manifest.permission.MANAGE_CA_CERTIFICATES;
import android.app.admin.DevicePolicyManagerInternal;
+
import com.android.internal.R;
import com.android.internal.os.storage.ExternalStorageFormatter;
import com.android.internal.util.FastXmlSerializer;
@@ -58,6 +59,7 @@
import android.net.Uri;
import android.content.pm.PackageManager.NameNotFoundException;
import android.database.ContentObserver;
+import android.hardware.usb.UsbManager;
import android.net.ProxyInfo;
import android.os.Binder;
import android.os.Bundle;
@@ -3417,8 +3419,7 @@
synchronized (this) {
long ident = Binder.clearCallingIdentity();
try {
- mUserManager.setUserRestrictions(new Bundle(),
- new UserHandle(UserHandle.USER_OWNER));
+ clearUserRestrictions(new UserHandle(UserHandle.USER_OWNER));
if (mDeviceOwner != null) {
mDeviceOwner.clearDeviceOwner();
mDeviceOwner.writeOwnerFile();
@@ -3481,7 +3482,7 @@
synchronized (this) {
long ident = Binder.clearCallingIdentity();
try {
- mUserManager.setUserRestrictions(new Bundle(), callingUser);
+ clearUserRestrictions(callingUser);
if (mDeviceOwner != null) {
mDeviceOwner.removeProfileOwner(callingUser.getIdentifier());
mDeviceOwner.writeOwnerFile();
@@ -3492,6 +3493,19 @@
}
}
+ private void clearUserRestrictions(UserHandle userHandle) {
+ AudioManager audioManager =
+ (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+ Bundle userRestrictions = mUserManager.getUserRestrictions();
+ mUserManager.setUserRestrictions(new Bundle(), userHandle);
+ if (userRestrictions.getBoolean(UserManager.DISALLOW_ADJUST_VOLUME)) {
+ audioManager.setMasterMute(false);
+ }
+ if (userRestrictions.getBoolean(UserManager.DISALLOW_UNMUTE_MICROPHONE)) {
+ audioManager.setMicrophoneMute(false);
+ }
+ }
+
@Override
public boolean hasUserSetupCompleted() {
if (!mHasFeature) {
@@ -4034,7 +4048,57 @@
long id = Binder.clearCallingIdentity();
try {
+ AudioManager audioManager =
+ (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+ boolean alreadyRestricted = mUserManager.hasUserRestriction(key);
+
+ if (enabled && !alreadyRestricted) {
+ if (UserManager.DISALLOW_CONFIG_WIFI.equals(key)) {
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ Settings.Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 0,
+ userHandle.getIdentifier());
+ } else if (UserManager.DISALLOW_USB_FILE_TRANSFER.equals(key)) {
+ UsbManager manager =
+ (UsbManager) mContext.getSystemService(Context.USB_SERVICE);
+ manager.setCurrentFunction("none", false);
+ } else if (UserManager.DISALLOW_SHARE_LOCATION.equals(key)) {
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_OFF,
+ userHandle.getIdentifier());
+ Settings.Secure.putStringForUser(mContext.getContentResolver(),
+ Settings.Secure.LOCATION_PROVIDERS_ALLOWED, "",
+ userHandle.getIdentifier());
+ } else if (UserManager.DISALLOW_DEBUGGING_FEATURES.equals(key)) {
+ Settings.Global.putStringForUser(mContext.getContentResolver(),
+ Settings.Global.ADB_ENABLED, "0", userHandle.getIdentifier());
+ } else if (UserManager.ENSURE_VERIFY_APPS.equals(key)) {
+ Settings.Global.putStringForUser(mContext.getContentResolver(),
+ Settings.Global.PACKAGE_VERIFIER_ENABLE, "1",
+ userHandle.getIdentifier());
+ Settings.Global.putStringForUser(mContext.getContentResolver(),
+ Settings.Global.PACKAGE_VERIFIER_INCLUDE_ADB, "1",
+ userHandle.getIdentifier());
+ } else if (UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES.equals(key)) {
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ Settings.Secure.INSTALL_NON_MARKET_APPS, 0,
+ userHandle.getIdentifier());
+ } else if (UserManager.DISALLOW_UNMUTE_MICROPHONE.equals(key)) {
+ audioManager.setMicrophoneMute(true);
+ } else if (UserManager.DISALLOW_ADJUST_VOLUME.equals(key)) {
+ audioManager.setMasterMute(true);
+ }
+ }
+
mUserManager.setUserRestriction(key, enabled, userHandle);
+
+ if (!enabled && alreadyRestricted) {
+ if (UserManager.DISALLOW_UNMUTE_MICROPHONE.equals(key)) {
+ audioManager.setMicrophoneMute(false);
+ } else if (UserManager.DISALLOW_ADJUST_VOLUME.equals(key)) {
+ audioManager.setMasterMute(false);
+ }
+ }
+
} finally {
restoreCallingIdentity(id);
}
diff --git a/telecomm/java/android/telecomm/Call.java b/telecomm/java/android/telecomm/Call.java
index f988ac8..7223574 100644
--- a/telecomm/java/android/telecomm/Call.java
+++ b/telecomm/java/android/telecomm/Call.java
@@ -27,6 +27,7 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.concurrent.CopyOnWriteArrayList;
/**
* Represents an ongoing phone call that the in-call app should present to the user.
@@ -364,7 +365,7 @@
private final InCallAdapter mInCallAdapter;
private final List<Call> mChildren = new ArrayList<>();
private final List<Call> mUnmodifiableChildren = Collections.unmodifiableList(mChildren);
- private final List<Listener> mListeners = new ArrayList<>();
+ private final List<Listener> mListeners = new CopyOnWriteArrayList<>();
private final List<Call> mConferenceableCalls = new ArrayList<>();
private final List<Call> mUnmodifiableConferenceableCalls =
Collections.unmodifiableList(mConferenceableCalls);
@@ -589,7 +590,9 @@
* @param listener A {@code Listener}.
*/
public void removeListener(Listener listener) {
- mListeners.remove(listener);
+ if (listener != null) {
+ mListeners.remove(listener);
+ }
}
/** {@hide} */
@@ -709,72 +712,62 @@
}
private void fireStateChanged(int newState) {
- Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
- for (int i = 0; i < listeners.length; i++) {
- listeners[i].onStateChanged(this, newState);
+ for (Listener listener : mListeners) {
+ listener.onStateChanged(this, newState);
}
}
private void fireParentChanged(Call newParent) {
- Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
- for (int i = 0; i < listeners.length; i++) {
- listeners[i].onParentChanged(this, newParent);
+ for (Listener listener : mListeners) {
+ listener.onParentChanged(this, newParent);
}
}
private void fireChildrenChanged(List<Call> children) {
- Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
- for (int i = 0; i < listeners.length; i++) {
- listeners[i].onChildrenChanged(this, children);
+ for (Listener listener : mListeners) {
+ listener.onChildrenChanged(this, children);
}
}
private void fireDetailsChanged(Details details) {
- Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
- for (int i = 0; i < listeners.length; i++) {
- listeners[i].onDetailsChanged(this, details);
+ for (Listener listener : mListeners) {
+ listener.onDetailsChanged(this, details);
}
}
private void fireCannedTextResponsesLoaded(List<String> cannedTextResponses) {
- Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
- for (int i = 0; i < listeners.length; i++) {
- listeners[i].onCannedTextResponsesLoaded(this, cannedTextResponses);
+ for (Listener listener : mListeners) {
+ listener.onCannedTextResponsesLoaded(this, cannedTextResponses);
}
}
private void fireVideoCallChanged(InCallService.VideoCall videoCall) {
- Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
- for (int i = 0; i < listeners.length; i++) {
- listeners[i].onVideoCallChanged(this, videoCall);
+ for (Listener listener : mListeners) {
+ listener.onVideoCallChanged(this, videoCall);
}
}
private void firePostDialWait(String remainingPostDialSequence) {
- Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
- for (int i = 0; i < listeners.length; i++) {
- listeners[i].onPostDialWait(this, remainingPostDialSequence);
+ for (Listener listener : mListeners) {
+ listener.onPostDialWait(this, remainingPostDialSequence);
}
}
private void fireStartActivity(PendingIntent intent) {
- Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
- for (int i = 0; i < listeners.length; i++) {
- listeners[i].onStartActivity(this, intent);
+ for (Listener listener : mListeners) {
+ listener.onStartActivity(this, intent);
}
}
private void fireCallDestroyed() {
- Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
- for (int i = 0; i < listeners.length; i++) {
- listeners[i].onCallDestroyed(this);
+ for (Listener listener : mListeners) {
+ listener.onCallDestroyed(this);
}
}
private void fireConferenceableCallsChanged() {
- Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
- for (int i = 0; i < listeners.length; i++) {
- listeners[i].onConferenceableCallsChanged(this, mUnmodifiableConferenceableCalls);
+ for (Listener listener : mListeners) {
+ listener.onConferenceableCallsChanged(this, mUnmodifiableConferenceableCalls);
}
}
diff --git a/telecomm/java/android/telecomm/Connection.java b/telecomm/java/android/telecomm/Connection.java
index 3ecb4cb..78c34a1 100644
--- a/telecomm/java/android/telecomm/Connection.java
+++ b/telecomm/java/android/telecomm/Connection.java
@@ -32,7 +32,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Set;
-import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.concurrent.ConcurrentHashMap;
/**
* Represents a connection to a remote endpoint that carries voice traffic.
@@ -448,7 +448,13 @@
}
};
- private final Set<Listener> mListeners = new CopyOnWriteArraySet<>();
+ /**
+ * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is
+ * load factor before resizing, 1 means we only expect a single thread to
+ * access the map so make only a single shard
+ */
+ private final Set<Listener> mListeners = Collections.newSetFromMap(
+ new ConcurrentHashMap<Listener, Boolean>(8, 0.9f, 1));
private final List<Connection> mChildConnections = new ArrayList<>();
private final List<Connection> mUnmodifiableChildConnections =
Collections.unmodifiableList(mChildConnections);
@@ -587,7 +593,9 @@
* @hide
*/
public final Connection removeConnectionListener(Listener l) {
- mListeners.remove(l);
+ if (l != null) {
+ mListeners.remove(l);
+ }
return this;
}
@@ -874,13 +882,8 @@
* Tears down the Connection object.
*/
public final void destroy() {
- // It is possible that onDestroy() will trigger the listener to remove itself which will
- // result in a concurrent modification exception. To counteract this we make a copy of the
- // listeners and iterate on that.
- for (Listener l : new ArrayList<>(mListeners)) {
- if (mListeners.contains(l)) {
- l.onDestroyed(this);
- }
+ for (Listener l : mListeners) {
+ l.onDestroyed(this);
}
}
diff --git a/telecomm/java/android/telecomm/ConnectionService.java b/telecomm/java/android/telecomm/ConnectionService.java
index 44aacfc..8ab5e13 100644
--- a/telecomm/java/android/telecomm/ConnectionService.java
+++ b/telecomm/java/android/telecomm/ConnectionService.java
@@ -16,6 +16,7 @@
package android.telecomm;
+import android.Manifest;
import android.annotation.SdkConstant;
import android.app.PendingIntent;
import android.app.Service;
@@ -26,6 +27,8 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
+import android.os.Parcel;
+import android.os.RemoteException;
import android.telephony.DisconnectCause;
import com.android.internal.os.SomeArgs;
@@ -45,7 +48,6 @@
* Android device.
*/
public abstract class ConnectionService extends Service {
-
/**
* The {@link Intent} that must be declared as handled by the service.
*/
@@ -81,6 +83,18 @@
private final ConnectionServiceAdapter mAdapter = new ConnectionServiceAdapter();
private final IBinder mBinder = new IConnectionService.Stub() {
+ /**
+ * Enforces the requirement that all calls into the ConnectionService require the
+ * {@code BIND_CONNECTION_SERVICE} permission.
+ */
+ @Override
+ public boolean onTransact(int code, Parcel data, Parcel reply,
+ int flags) throws RemoteException
+ {
+ enforceBindConnectionServicePermission();
+ return super.onTransact(code, data, reply, flags);
+ }
+
@Override
public void addConnectionServiceAdapter(IConnectionServiceAdapter adapter) {
mHandler.obtainMessage(MSG_ADD_CONNECTION_SERVICE_ADAPTER, adapter).sendToTarget();
@@ -481,15 +495,12 @@
case Connection.STATE_INITIALIZING:
Log.d(this, "State changed to STATE_INITIALIZING; ignoring");
return; // Don't want to stop listening on this state transition.
- default:
- Log.d(this, "Connection created in state %s",
- Connection.stateToString(state));
- connectionCreated(callId, request, createdConnection);
- break;
}
c.removeConnectionListener(this);
}
});
+ Log.d(this, "Connection created in state INITIALIZING");
+ connectionCreated(callId, request, createdConnection);
} else if (createdConnection.getState() == Connection.STATE_CANCELED) {
// Tell telecomm not to attempt any more services.
mAdapter.handleCreateConnectionCancelled(callId, request);
@@ -620,7 +631,8 @@
public void onError(String request, int code, String reason) {
// no-op
}
- });
+ }
+ );
}
private void splitFromConference(String callId) {
@@ -833,4 +845,10 @@
return Connection.getNullConnection();
}
+ /**
+ * Enforces the {@code BIND_CONNECTION_SERVICE} permission for connection service calls.
+ */
+ private void enforceBindConnectionServicePermission() {
+ enforceCallingPermission(Manifest.permission.BIND_CONNECTION_SERVICE, null);
+ }
}
diff --git a/telecomm/java/android/telecomm/ConnectionServiceAdapter.java b/telecomm/java/android/telecomm/ConnectionServiceAdapter.java
index bfcb5c3..4144b81 100644
--- a/telecomm/java/android/telecomm/ConnectionServiceAdapter.java
+++ b/telecomm/java/android/telecomm/ConnectionServiceAdapter.java
@@ -36,8 +36,13 @@
* @hide
*/
final class ConnectionServiceAdapter implements DeathRecipient {
+ /**
+ * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is
+ * load factor before resizing, 1 means we only expect a single thread to
+ * access the map so make only a single shard
+ */
private final Set<IConnectionServiceAdapter> mAdapters = Collections.newSetFromMap(
- new ConcurrentHashMap<IConnectionServiceAdapter, Boolean>(2));
+ new ConcurrentHashMap<IConnectionServiceAdapter, Boolean>(8, 0.9f, 1));
ConnectionServiceAdapter() {
}
@@ -53,7 +58,7 @@
}
void removeAdapter(IConnectionServiceAdapter adapter) {
- if (mAdapters.remove(adapter)) {
+ if (adapter != null && mAdapters.remove(adapter)) {
adapter.asBinder().unlinkToDeath(this, 0);
}
}
diff --git a/telecomm/java/android/telecomm/Phone.java b/telecomm/java/android/telecomm/Phone.java
index 79e777a..03a8676 100644
--- a/telecomm/java/android/telecomm/Phone.java
+++ b/telecomm/java/android/telecomm/Phone.java
@@ -24,6 +24,7 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.concurrent.CopyOnWriteArrayList;
/**
* A unified virtual device providing a means of voice (and other) communication on a device.
@@ -89,7 +90,7 @@
private AudioState mAudioState;
- private final List<Listener> mListeners = new ArrayList<>();
+ private final List<Listener> mListeners = new CopyOnWriteArrayList<>();
/** {@hide} */
Phone(InCallAdapter adapter) {
@@ -171,7 +172,9 @@
* @param listener A {@code Listener} object.
*/
public final void removeListener(Listener listener) {
- mListeners.remove(listener);
+ if (listener != null) {
+ mListeners.remove(listener);
+ }
}
/**
@@ -236,30 +239,26 @@
}
private void fireCallAdded(Call call) {
- Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
- for (int i = 0; i < listeners.length; i++) {
- listeners[i].onCallAdded(this, call);
+ for (Listener listener : mListeners) {
+ listener.onCallAdded(this, call);
}
}
private void fireCallRemoved(Call call) {
- Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
- for (int i = 0; i < listeners.length; i++) {
- listeners[i].onCallRemoved(this, call);
+ for (Listener listener : mListeners) {
+ listener.onCallRemoved(this, call);
}
}
private void fireAudioStateChanged(AudioState audioState) {
- Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
- for (int i = 0; i < listeners.length; i++) {
- listeners[i].onAudioStateChanged(this, audioState);
+ for (Listener listener : mListeners) {
+ listener.onAudioStateChanged(this, audioState);
}
}
private void fireBringToForeground(boolean showDialpad) {
- Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
- for (int i = 0; i < listeners.length; i++) {
- listeners[i].onBringToForeground(this, showDialpad);
+ for (Listener listener : mListeners) {
+ listener.onBringToForeground(this, showDialpad);
}
}
diff --git a/telecomm/java/android/telecomm/RemoteConnection.java b/telecomm/java/android/telecomm/RemoteConnection.java
index d3972d31..f1cee10 100644
--- a/telecomm/java/android/telecomm/RemoteConnection.java
+++ b/telecomm/java/android/telecomm/RemoteConnection.java
@@ -184,8 +184,13 @@
private IConnectionService mConnectionService;
private final String mConnectionId;
+ /**
+ * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is
+ * load factor before resizing, 1 means we only expect a single thread to
+ * access the map so make only a single shard
+ */
private final Set<Listener> mListeners = Collections.newSetFromMap(
- new ConcurrentHashMap<Listener, Boolean>(2));
+ new ConcurrentHashMap<Listener, Boolean>(8, 0.9f, 1));
private final Set<RemoteConnection> mConferenceableConnections = new HashSet<>();
private int mState = Connection.STATE_NEW;
@@ -248,7 +253,9 @@
* @param listener A {@code Listener}.
*/
public void removeListener(Listener listener) {
- mListeners.remove(listener);
+ if (listener != null) {
+ mListeners.remove(listener);
+ }
}
/**
@@ -588,11 +595,10 @@
setDisconnected(DisconnectCause.ERROR_UNSPECIFIED, "Connection destroyed.");
}
- Set<Listener> listeners = new HashSet<Listener>(mListeners);
- mListeners.clear();
- for (Listener l : listeners) {
+ for (Listener l : mListeners) {
l.onDestroyed(this);
}
+ mListeners.clear();
mConnected = false;
}
diff --git a/telephony/java/com/android/internal/telephony/PhoneConstants.java b/telephony/java/com/android/internal/telephony/PhoneConstants.java
index cf87bec..b4b1ea5 100644
--- a/telephony/java/com/android/internal/telephony/PhoneConstants.java
+++ b/telephony/java/com/android/internal/telephony/PhoneConstants.java
@@ -187,4 +187,7 @@
REASON_RADIO_UNAVAILABLE,
REASON_SIM_REFRESH_RESET
};
+
+ // Initial MTU value.
+ public static final int UNSET_MTU = 0;
}
diff --git a/tests/OneMedia/AndroidManifest.xml b/tests/OneMedia/AndroidManifest.xml
index 9d78ca5..95072a4 100644
--- a/tests/OneMedia/AndroidManifest.xml
+++ b/tests/OneMedia/AndroidManifest.xml
@@ -23,7 +23,7 @@
</activity>
<service
android:name="com.android.onemedia.OnePlayerService"
- android:exported="false"
+ android:exported="true"
android:process="com.android.onemedia.service" />
<service
android:name=".provider.OneMediaRouteProvider"
diff --git a/tests/OneMedia/res/drawable/ic_fast_forward.xml b/tests/OneMedia/res/drawable/ic_fast_forward.xml
new file mode 100644
index 0000000..8daf07d
--- /dev/null
+++ b/tests/OneMedia/res/drawable/ic_fast_forward.xml
@@ -0,0 +1,25 @@
+<!--
+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"
+ android:width="36dp"
+ android:height="36dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24" >
+
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M4.0,18.0l8.5,-6.0L4.0,6.0L4.0,18.0zM13.0,6.0l0.0,12.0l8.5,-6.0L13.0,6.0z"/>
+</vector>
diff --git a/tests/OneMedia/res/drawable/ic_fast_rewind.xml b/tests/OneMedia/res/drawable/ic_fast_rewind.xml
new file mode 100644
index 0000000..4ed1f54
--- /dev/null
+++ b/tests/OneMedia/res/drawable/ic_fast_rewind.xml
@@ -0,0 +1,24 @@
+<!--
+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"
+ android:width="36dp"
+ android:height="36dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24" >
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M11.0,18.0L11.0,6.0l-8.5,6.0L11.0,18.0zM11.5,12.0l8.5,6.0L20.0,6.0L11.5,12.0z"/>
+</vector>
diff --git a/tests/OneMedia/res/drawable/ic_pause.xml b/tests/OneMedia/res/drawable/ic_pause.xml
new file mode 100644
index 0000000..15d0756
--- /dev/null
+++ b/tests/OneMedia/res/drawable/ic_pause.xml
@@ -0,0 +1,25 @@
+<!--
+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"
+ android:width="36dp"
+ android:height="36dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24" >
+
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M6.0,19.0l4.0,0.0L10.0,5.0L6.0,5.0L6.0,19.0zM14.0,5.0l0.0,14.0l4.0,0.0L18.0,5.0L14.0,5.0z"/>
+</vector>
diff --git a/tests/OneMedia/res/drawable/ic_play_arrow.xml b/tests/OneMedia/res/drawable/ic_play_arrow.xml
new file mode 100644
index 0000000..49d766d
--- /dev/null
+++ b/tests/OneMedia/res/drawable/ic_play_arrow.xml
@@ -0,0 +1,25 @@
+<!--
+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"
+ android:width="36dp"
+ android:height="36dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24" >
+
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M8.0,5.0l0.0,14.0 11.0,-7.0z"/>
+</vector>
diff --git a/tests/OneMedia/res/drawable/ic_skip_next.xml b/tests/OneMedia/res/drawable/ic_skip_next.xml
new file mode 100644
index 0000000..8a6ceca
--- /dev/null
+++ b/tests/OneMedia/res/drawable/ic_skip_next.xml
@@ -0,0 +1,25 @@
+<!--
+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"
+ android:width="36dp"
+ android:height="36dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24" >
+
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M6.0,18.0l8.5,-6.0L6.0,6.0L6.0,18.0zM16.0,6.0l0.0,12.0l2.0,0.0L18.0,6.0L16.0,6.0z"/>
+</vector>
diff --git a/tests/OneMedia/res/drawable/ic_skip_previous.xml b/tests/OneMedia/res/drawable/ic_skip_previous.xml
new file mode 100644
index 0000000..c5d07db
--- /dev/null
+++ b/tests/OneMedia/res/drawable/ic_skip_previous.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"
+ android:width="36dp"
+ android:height="36dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24" >
+
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M6.0,6.0l2.0,0.0l0.0,12.0l-2.0,0.0z"/>
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M9.5,12.0l8.5,6.0 0.0,-12.0z"/>
+</vector>
diff --git a/tests/OneMedia/res/drawable/ic_stop.xml b/tests/OneMedia/res/drawable/ic_stop.xml
new file mode 100644
index 0000000..6043fdb6
--- /dev/null
+++ b/tests/OneMedia/res/drawable/ic_stop.xml
@@ -0,0 +1,25 @@
+<!--
+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"
+ android:width="36dp"
+ android:height="36dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24" >
+
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M6.0,6.0l12.0,0.0l0.0,12.0l-12.0,0.0z"/>
+</vector>
diff --git a/tests/OneMedia/res/layout/activity_one_player.xml b/tests/OneMedia/res/layout/activity_one_player.xml
index 516562f..ce2d641 100644
--- a/tests/OneMedia/res/layout/activity_one_player.xml
+++ b/tests/OneMedia/res/layout/activity_one_player.xml
@@ -33,6 +33,19 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/has_video" />
+ <ImageView
+ android:id="@+id/art"
+ android:layout_width="match_parent"
+ android:layout_height="96dp"
+ android:scaleType="centerCrop"
+ android:visibility="gone"
+ />
+ <Button
+ android:id="@+id/art_picker"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/art_picker"
+ />
<LinearLayout
android:id="@+id/controls"
android:layout_width="match_parent"
diff --git a/tests/OneMedia/res/values/strings.xml b/tests/OneMedia/res/values/strings.xml
index 3735c8d..86657fd 100644
--- a/tests/OneMedia/res/values/strings.xml
+++ b/tests/OneMedia/res/values/strings.xml
@@ -12,5 +12,5 @@
<string name="media_next_hint">Next content</string>
<string name="has_video">Is video</string>
<string name="has_duration">Has duration</string>
-
+ <string name="art_picker">Choose artwork</string>
</resources>
diff --git a/tests/OneMedia/src/com/android/onemedia/IPlayerService.aidl b/tests/OneMedia/src/com/android/onemedia/IPlayerService.aidl
index d4df4c5..f53eac0 100644
--- a/tests/OneMedia/src/com/android/onemedia/IPlayerService.aidl
+++ b/tests/OneMedia/src/com/android/onemedia/IPlayerService.aidl
@@ -15,6 +15,7 @@
package com.android.onemedia;
+import android.graphics.Bitmap;
import android.media.session.MediaSession;
import android.os.Bundle;
@@ -26,4 +27,5 @@
void registerCallback(in IPlayerCallback cb);
void unregisterCallback(in IPlayerCallback cb);
void sendRequest(String action, in Bundle params, in IRequestCallback cb);
+ void setIcon(in Bitmap icon);
}
diff --git a/tests/OneMedia/src/com/android/onemedia/NotificationHelper.java b/tests/OneMedia/src/com/android/onemedia/NotificationHelper.java
new file mode 100644
index 0000000..a5bcda5
--- /dev/null
+++ b/tests/OneMedia/src/com/android/onemedia/NotificationHelper.java
@@ -0,0 +1,234 @@
+package com.android.onemedia;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.Bitmap;
+import android.media.MediaMetadata;
+import android.media.session.MediaController;
+import android.media.session.MediaSession;
+import android.media.session.PlaybackState;
+import android.text.format.DateUtils;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.onemedia.playback.RequestUtils;
+
+/**
+ * Keeps track of a notification and updates it automatically for a given
+ * MediaSession.
+ */
+public class NotificationHelper extends BroadcastReceiver {
+ private static final String TAG = "NotificationHelper";
+
+ private static final int NOTIFICATION_ID = 433; // John Cage, 1952
+
+ private final Service mService;
+ private final MediaSession mSession;
+ private final MediaController mController;
+ private final MediaController.TransportControls mTransportControls;
+ private final SparseArray<PendingIntent> mIntents = new SparseArray<PendingIntent>();
+
+ private PlaybackState mPlaybackState;
+ private MediaMetadata mMetadata;
+
+ private boolean mStarted = false;
+
+ public NotificationHelper(Service service, MediaSession session) {
+ mService = service;
+ mSession = session;
+ mController = session.getController();
+ mTransportControls = mController.getTransportControls();
+ String pkg = mService.getPackageName();
+
+ mIntents.put(R.drawable.ic_pause, PendingIntent.getBroadcast(mService, 100, new Intent(
+ com.android.onemedia.playback.RequestUtils.ACTION_PAUSE).setPackage(pkg),
+ PendingIntent.FLAG_CANCEL_CURRENT));
+ mIntents.put(R.drawable.ic_play_arrow, PendingIntent.getBroadcast(mService, 100,
+ new Intent(com.android.onemedia.playback.RequestUtils.ACTION_PLAY).setPackage(pkg),
+ PendingIntent.FLAG_CANCEL_CURRENT));
+ mIntents.put(R.drawable.ic_skip_previous, PendingIntent.getBroadcast(mService, 100,
+ new Intent(com.android.onemedia.playback.RequestUtils.ACTION_PREV).setPackage(pkg),
+ PendingIntent.FLAG_CANCEL_CURRENT));
+ mIntents.put(R.drawable.ic_skip_next, PendingIntent.getBroadcast(mService, 100,
+ new Intent(com.android.onemedia.playback.RequestUtils.ACTION_NEXT).setPackage(pkg),
+ PendingIntent.FLAG_CANCEL_CURRENT));
+ mIntents.put(R.drawable.ic_fast_rewind, PendingIntent.getBroadcast(mService, 100,
+ new Intent(com.android.onemedia.playback.RequestUtils.ACTION_REW).setPackage(pkg),
+ PendingIntent.FLAG_CANCEL_CURRENT));
+ mIntents.put(R.drawable.ic_fast_forward, PendingIntent.getBroadcast(mService, 100,
+ new Intent(com.android.onemedia.playback.RequestUtils.ACTION_FFWD).setPackage(pkg),
+ PendingIntent.FLAG_CANCEL_CURRENT));
+ }
+
+ /**
+ * Posts the notification and starts tracking the session to keep it
+ * updated. The notification will automatically be removed if the session is
+ * destroyed before {@link #onStop} is called.
+ */
+ public void onStart() {
+ mController.addCallback(mCb);
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(RequestUtils.ACTION_FFWD);
+ filter.addAction(RequestUtils.ACTION_NEXT);
+ filter.addAction(RequestUtils.ACTION_PAUSE);
+ filter.addAction(RequestUtils.ACTION_PLAY);
+ filter.addAction(RequestUtils.ACTION_PREV);
+ filter.addAction(RequestUtils.ACTION_REW);
+ mService.registerReceiver(this, filter);
+
+ mMetadata = mController.getMetadata();
+ mPlaybackState = mController.getPlaybackState();
+
+ mStarted = true;
+ // The notification must be updated after setting started to true
+ updateNotification();
+ }
+
+ /**
+ * Removes the notification and stops tracking the session. If the session
+ * was destroyed this has no effect.
+ */
+ public void onStop() {
+ mStarted = false;
+ mController.removeCallback(mCb);
+ mService.unregisterReceiver(this);
+ updateNotification();
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ Log.d(TAG, "Received intent with action " + action);
+ if (RequestUtils.ACTION_PAUSE.equals(action)) {
+ mTransportControls.pause();
+ } else if (RequestUtils.ACTION_PLAY.equals(action)) {
+ mTransportControls.play();
+ } else if (RequestUtils.ACTION_NEXT.equals(action)) {
+ mTransportControls.skipToNext();
+ } else if (RequestUtils.ACTION_PREV.equals(action)) {
+ mTransportControls.skipToPrevious();
+ } else if (RequestUtils.ACTION_REW.equals(action)) {
+ mTransportControls.rewind();
+ } else if (RequestUtils.ACTION_FFWD.equals(action)) {
+ mTransportControls.fastForward();
+ }
+
+ }
+
+ private final MediaController.Callback mCb = new MediaController.Callback() {
+ @Override
+ public void onPlaybackStateChanged(PlaybackState state) {
+ mPlaybackState = state;
+ Log.d(TAG, "Received new playback state" + state);
+ updateNotification();
+ }
+
+ @Override
+ public void onMetadataChanged(MediaMetadata metadata) {
+ mMetadata = metadata;
+ Log.d(TAG, "Received new metadata " + metadata);
+ updateNotification();
+ }
+ };
+
+ NotificationManager mNoMan = null;
+
+ private void updateNotification() {
+ if (mNoMan == null) {
+ mNoMan = (NotificationManager) mService.getSystemService(Context.NOTIFICATION_SERVICE);
+ }
+ if (mPlaybackState == null) {
+ mNoMan.cancel(NOTIFICATION_ID);
+ return;
+ }
+ if (!mStarted) {
+ mNoMan.cancel(NOTIFICATION_ID);
+ return;
+ }
+
+ String status;
+ final int state = mPlaybackState.getState();
+ switch (state) {
+ case PlaybackState.STATE_PLAYING:
+ status = "PLAYING: ";
+ break;
+ case PlaybackState.STATE_PAUSED:
+ status = "PAUSED: ";
+ break;
+ case PlaybackState.STATE_STOPPED:
+ status = "STOPPED: ";
+ break;
+ case PlaybackState.STATE_ERROR:
+ status = "ERROR: ";
+ break;
+ case PlaybackState.STATE_BUFFERING:
+ status = "BUFFERING: ";
+ break;
+ case PlaybackState.STATE_NONE:
+ default:
+ status = "";
+ break;
+ }
+ CharSequence title, text;
+ Bitmap art;
+ if (mMetadata == null) {
+ title = status;
+ text = "Empty metadata!";
+ art = null;
+ } else {
+ MediaMetadata.Description description = mMetadata.getDescription();
+ title = description.getTitle();
+ text = description.getSubtitle();
+ art = description.getIcon();
+ }
+
+ String playPauseLabel = "";
+ int playPauseIcon;
+ if (state == PlaybackState.STATE_PLAYING) {
+ playPauseLabel = "Pause";
+ playPauseIcon = R.drawable.ic_pause;
+ } else {
+ playPauseLabel = "Play";
+ playPauseIcon = R.drawable.ic_play_arrow;
+ }
+
+ final long pos = mPlaybackState.getPosition();
+ final long end = mMetadata == null ? 0 : mMetadata
+ .getLong(MediaMetadata.METADATA_KEY_DURATION);
+ Notification notification = new Notification.Builder(mService)
+ .setSmallIcon(android.R.drawable.stat_notify_chat)
+ .setContentTitle(title)
+ .setContentText(text)
+ .setShowWhen(false)
+ .setContentInfo(DateUtils.formatElapsedTime(pos))
+ .setProgress((int) end, (int) pos, false)
+ .setLargeIcon(art)
+ .addAction(R.drawable.ic_skip_previous, "Previous",
+ mIntents.get(R.drawable.ic_skip_previous))
+ .addAction(R.drawable.ic_fast_rewind, "Rewind",
+ mIntents.get(R.drawable.ic_fast_rewind))
+ .addAction(playPauseIcon, playPauseLabel,
+ mIntents.get(playPauseIcon))
+ .addAction(R.drawable.ic_fast_forward, "Fast Forward",
+ mIntents.get(R.drawable.ic_fast_forward))
+ .addAction(R.drawable.ic_skip_next, "Next",
+ mIntents.get(R.drawable.ic_skip_next))
+ .setStyle(new Notification.MediaStyle()
+ .setShowActionsInCompactView(2)
+ .setMediaSession(mSession.getSessionToken()))
+ .setColor(0xFFDB4437)
+ .build();
+
+ mService.startForeground(NOTIFICATION_ID, notification);
+ }
+
+}
diff --git a/tests/OneMedia/src/com/android/onemedia/OnePlayerActivity.java b/tests/OneMedia/src/com/android/onemedia/OnePlayerActivity.java
index 894377b..2ff3e20 100644
--- a/tests/OneMedia/src/com/android/onemedia/OnePlayerActivity.java
+++ b/tests/OneMedia/src/com/android/onemedia/OnePlayerActivity.java
@@ -17,20 +17,35 @@
import android.app.Activity;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
import android.media.MediaMetadata;
import android.media.session.PlaybackState;
+import android.net.Uri;
import android.os.Bundle;
+import android.provider.MediaStore;
+import android.text.format.DateUtils;
import android.util.Log;
import android.view.Menu;
import android.view.View;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
+import android.widget.ImageView;
import android.widget.TextView;
+import java.io.IOException;
+
public class OnePlayerActivity extends Activity {
private static final String TAG = "OnePlayerActivity";
+ private static final int READ_REQUEST_CODE = 42;
+
protected PlayerController mPlayer;
private Button mStartButton;
@@ -41,8 +56,10 @@
private EditText mContentText;
private EditText mNextContentText;
private CheckBox mHasVideo;
+ private ImageView mArtView;
- private int mPlaybackState;
+ private PlaybackState mPlaybackState;
+ private Bitmap mAlbumArtBitmap;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -58,6 +75,10 @@
mContentText = (EditText) findViewById(R.id.content);
mNextContentText = (EditText) findViewById(R.id.next_content);
mHasVideo = (CheckBox) findViewById(R.id.has_video);
+ mArtView = (ImageView) findViewById(R.id.art);
+
+ final Button artPicker = (Button) findViewById(R.id.art_picker);
+ artPicker.setOnClickListener(mButtonListener);
mStartButton.setOnClickListener(mButtonListener);
mPlayButton.setOnClickListener(mButtonListener);
@@ -86,6 +107,31 @@
super.onPause();
}
+ @Override
+ public void onActivityResult(int requestCode, int resultCode,
+ Intent resultData) {
+ if (requestCode == READ_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
+ Uri uri = null;
+ if (resultData != null) {
+ uri = resultData.getData();
+ Log.i(TAG, "Uri: " + uri.toString());
+ mAlbumArtBitmap = null;
+ try {
+ mAlbumArtBitmap = MediaStore.Images.Media.getBitmap(this.getContentResolver(), uri);
+ } catch (IOException e) {
+ Log.v(TAG, "Couldn't load album art", e);
+ }
+ mArtView.setImageBitmap(mAlbumArtBitmap);
+ if (mAlbumArtBitmap != null) {
+ mArtView.setVisibility(View.VISIBLE);
+ } else {
+ mArtView.setVisibility(View.GONE);
+ }
+ mPlayer.setArt(mAlbumArtBitmap);
+ }
+ }
+ }
+
private void setControlsEnabled(boolean enabled) {
mStartButton.setEnabled(enabled);
mPlayButton.setEnabled(enabled);
@@ -94,36 +140,46 @@
private View.OnClickListener mButtonListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
+ final int state = mPlaybackState.getState();
switch (v.getId()) {
case R.id.play_button:
- Log.d(TAG, "Play button pressed, in state " + mPlaybackState);
- if (mPlaybackState == PlaybackState.STATE_PAUSED
- || mPlaybackState == PlaybackState.STATE_STOPPED) {
+ Log.d(TAG, "Play button pressed, in state " + state);
+ if (state == PlaybackState.STATE_PAUSED
+ || state == PlaybackState.STATE_STOPPED) {
mPlayer.play();
- } else if (mPlaybackState == PlaybackState.STATE_PLAYING) {
+ } else if (state == PlaybackState.STATE_PLAYING) {
mPlayer.pause();
}
break;
case R.id.start_button:
- Log.d(TAG, "Start button pressed, in state " + mPlaybackState);
+ Log.d(TAG, "Start button pressed, in state " + state);
mPlayer.setContent(mContentText.getText().toString());
break;
case R.id.route_button:
mPlayer.showRoutePicker();
break;
+ case R.id.art_picker:
+ Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
+ intent.addCategory(Intent.CATEGORY_OPENABLE);
+ intent.setType("image/*");
+
+ startActivityForResult(intent, READ_REQUEST_CODE);
+ break;
}
}
};
private PlayerController.Listener mListener = new PlayerController.Listener() {
+ public MediaMetadata mMetadata;
+
@Override
public void onPlaybackStateChange(PlaybackState state) {
- mPlaybackState = state.getState();
+ mPlaybackState = state;
boolean enablePlay = false;
boolean enableControls = true;
StringBuilder statusBuilder = new StringBuilder();
- switch (mPlaybackState) {
+ switch (mPlaybackState.getState()) {
case PlaybackState.STATE_PLAYING:
statusBuilder.append("playing");
mPlayButton.setText("Pause");
@@ -172,7 +228,7 @@
@Override
public void onMetadataChange(MediaMetadata metadata) {
- Log.d(TAG, "Metadata update! Title: " + metadata);
+ mMetadata = metadata;
}
};
}
diff --git a/tests/OneMedia/src/com/android/onemedia/PlayerController.java b/tests/OneMedia/src/com/android/onemedia/PlayerController.java
index c0799fc..c8d72ca 100644
--- a/tests/OneMedia/src/com/android/onemedia/PlayerController.java
+++ b/tests/OneMedia/src/com/android/onemedia/PlayerController.java
@@ -30,6 +30,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
+import android.graphics.Bitmap;
import android.util.Log;
import com.android.onemedia.playback.RequestUtils;
@@ -52,6 +53,7 @@
private Handler mHandler = new Handler();
private boolean mResumed;
+ private Bitmap mArt;
public PlayerController(Activity context, Intent serviceIntent) {
mContext = context;
@@ -89,6 +91,16 @@
unbindFromService();
}
+ public void setArt(Bitmap art) {
+ mArt = art;
+ if (mBinder != null) {
+ try {
+ mBinder.setIcon(art);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
public void play() {
if (mTransportControls != null) {
mTransportControls.play();
@@ -125,6 +137,16 @@
// TODO
}
+ public MediaSession.Token getSessionToken() {
+ if (mBinder != null) {
+ try {
+ return mBinder.getSessionToken();
+ } catch (RemoteException e) {
+ }
+ }
+ return null;
+ }
+
private void unbindFromService() {
mContext.unbindService(mServiceConnection);
}
@@ -165,6 +187,9 @@
mContext.setMediaController(mController);
mController.addCallback(mControllerCb, mHandler);
mTransportControls = mController.getTransportControls();
+ if (mArt != null) {
+ setArt(mArt);
+ }
Log.d(TAG, "Ready to use PlayerService");
if (mListener != null) {
@@ -194,6 +219,9 @@
return;
}
Log.d(TAG, "Received metadata change, " + metadata.getDescription());
+ if (mListener != null) {
+ mListener.onMetadataChange(metadata);
+ }
}
}
diff --git a/tests/OneMedia/src/com/android/onemedia/PlayerService.java b/tests/OneMedia/src/com/android/onemedia/PlayerService.java
index 58ee4a1..9967c99 100644
--- a/tests/OneMedia/src/com/android/onemedia/PlayerService.java
+++ b/tests/OneMedia/src/com/android/onemedia/PlayerService.java
@@ -17,6 +17,7 @@
import android.app.Service;
import android.content.Intent;
+import android.graphics.Bitmap;
import android.media.session.MediaSession;
import android.media.session.PlaybackState;
import android.os.Bundle;
@@ -34,6 +35,7 @@
private PlayerBinder mBinder;
private PlayerSession mSession;
+ private NotificationHelper mNotifyHelper;
private Intent mIntent;
private boolean mStarted = false;
@@ -47,6 +49,7 @@
mSession = onCreatePlayerController();
mSession.createSession();
mSession.setListener(mPlayerListener);
+ mNotifyHelper = new NotificationHelper(this, mSession.mSession);
}
}
@@ -75,6 +78,7 @@
if (!mStarted) {
Log.d(TAG, "Starting self");
startService(onCreateServiceIntent());
+ mNotifyHelper.onStart();
mStarted = true;
}
}
@@ -82,6 +86,7 @@
public void onPlaybackEnded() {
if (mStarted) {
Log.d(TAG, "Stopping self");
+ mNotifyHelper.onStop();
stopSelf();
mStarted = false;
}
@@ -150,8 +155,17 @@
@Override
public MediaSession.Token getSessionToken() throws RemoteException {
+ if (mSession == null) {
+ Log.e(TAG, "Error in PlayerService: mSession=null in getSessionToken()");
+ return null;
+ }
return mSession.getSessionToken();
}
+
+ @Override
+ public void setIcon(Bitmap icon) {
+ mSession.setIcon(icon);
+ }
}
}
diff --git a/tests/OneMedia/src/com/android/onemedia/PlayerSession.java b/tests/OneMedia/src/com/android/onemedia/PlayerSession.java
index 890d68d..9afcf24 100644
--- a/tests/OneMedia/src/com/android/onemedia/PlayerSession.java
+++ b/tests/OneMedia/src/com/android/onemedia/PlayerSession.java
@@ -17,6 +17,8 @@
import android.content.Context;
import android.content.Intent;
+import android.graphics.Bitmap;
+import android.media.MediaMetadata;
import android.media.routing.MediaRouteSelector;
import android.media.routing.MediaRouter;
import android.media.routing.MediaRouter.ConnectionRequest;
@@ -50,6 +52,7 @@
protected Renderer mRenderer;
protected MediaSession.Callback mCallback;
protected Renderer.Listener mRenderListener;
+ protected MediaMetadata.Builder mMetadataBuilder;
protected PlaybackState mPlaybackState;
protected Listener mListener;
@@ -66,6 +69,8 @@
mPlaybackState = psBob.build();
mRenderer.registerListener(mRenderListener);
+
+ initMetadata();
}
public void createSession() {
@@ -92,6 +97,7 @@
| MediaSession.FLAG_HANDLES_MEDIA_BUTTONS);
mSession.setMediaRouter(mRouter);
mSession.setActive(true);
+ updateMetadata();
}
public void onDestroy() {
@@ -130,6 +136,19 @@
mRenderer.setNextContent(request);
}
+ public void setIcon(Bitmap icon) {
+ mMetadataBuilder.putBitmap(MediaMetadata.METADATA_KEY_DISPLAY_ICON, icon);
+ updateMetadata();
+ }
+
+ private void updateMetadata() {
+ // This is a mild abuse of metadata and shouldn't be duplicated in real
+ // code
+ if (mSession != null && mSession.isActive()) {
+ mSession.setMetadata(mMetadataBuilder.build());
+ }
+ }
+
private void updateState(int newState) {
float rate = newState == PlaybackState.STATE_PLAYING ? 1 : 0;
long position = mRenderer == null ? -1 : mRenderer.getSeekPosition();
@@ -140,6 +159,14 @@
mSession.setPlaybackState(mPlaybackState);
}
+ private void initMetadata() {
+ mMetadataBuilder = new MediaMetadata.Builder();
+ mMetadataBuilder.putString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE,
+ "OneMedia display title");
+ mMetadataBuilder.putString(MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE,
+ "OneMedia display subtitle");
+ }
+
public interface Listener {
public void onPlayStateChanged(PlaybackState state);
}
diff --git a/tests/OneMedia/src/com/android/onemedia/playback/RequestUtils.java b/tests/OneMedia/src/com/android/onemedia/playback/RequestUtils.java
index 3778c5f..1688395 100644
--- a/tests/OneMedia/src/com/android/onemedia/playback/RequestUtils.java
+++ b/tests/OneMedia/src/com/android/onemedia/playback/RequestUtils.java
@@ -26,6 +26,12 @@
public class RequestUtils {
public static final String ACTION_SET_CONTENT = "set_content";
public static final String ACTION_SET_NEXT_CONTENT = "set_next_content";
+ public static final String ACTION_PAUSE = "com.android.onemedia.pause";
+ public static final String ACTION_PLAY = "com.android.onemedia.play";
+ public static final String ACTION_REW = "com.android.onemedia.rew";
+ public static final String ACTION_FFWD = "com.android.onemedia.ffwd";
+ public static final String ACTION_PREV = "com.android.onemedia.prev";
+ public static final String ACTION_NEXT = "com.android.onemedia.next";
public static final String EXTRA_KEY_SOURCE = "source";
public static final String EXTRA_KEY_METADATA = "metadata";
diff --git a/tests/UsesFeature2Test/AndroidManifest.xml b/tests/UsesFeature2Test/AndroidManifest.xml
index 6b6c4da..8caf4a1 100644
--- a/tests/UsesFeature2Test/AndroidManifest.xml
+++ b/tests/UsesFeature2Test/AndroidManifest.xml
@@ -33,12 +33,5 @@
<uses-feature android:name="android.hardware.opengles.aep" />
</feature-group>
- <application android:label="@string/app_title">
- <activity android:name="ActivityMain">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- </application>
+ <application android:label="@string/app_title" android:hasCode="false" />
</manifest>
diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp
index 41102fe..fe0a601 100644
--- a/tools/aapt/Command.cpp
+++ b/tools/aapt/Command.cpp
@@ -764,12 +764,9 @@
return 1;
}
+ // The dynamicRefTable can be null if there are no resources for this asset cookie.
+ // This fine.
const DynamicRefTable* dynamicRefTable = res.getDynamicRefTableForCookie(assetsCookie);
- if (dynamicRefTable == NULL) {
- fprintf(stderr, "ERROR: failed to find dynamic reference table for asset cookie %d\n",
- assetsCookie);
- return 1;
- }
Asset* asset = NULL;
@@ -1676,12 +1673,8 @@
String8 name = getResolvedAttribute(&res, tree, NAME_ATTR, &error);
if (name != "" && error == "") {
- int required = getIntegerAttribute(tree, REQUIRED_ATTR, NULL, 1);
- top.features.add(name, required);
- if (required) {
- addParentFeatures(&top, name);
- }
-
+ top.features.add(name, true);
+ addParentFeatures(&top, name);
} else {
int vers = getIntegerAttribute(tree, GL_ES_VERSION_ATTR, &error);
if (error == "") {
diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp
index 010d59b..0a80805 100644
--- a/tools/aapt/Resource.cpp
+++ b/tools/aapt/Resource.cpp
@@ -1470,6 +1470,8 @@
String16 action16("action");
String16 category16("category");
String16 data16("scheme");
+ String16 feature_group16("feature-group");
+ String16 uses_feature16("uses-feature");
const char* packageIdentChars = "abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ._0123456789";
const char* packageIdentCharsWithTheStupid = "abcdefghijklmnopqrstuvwxyz"
@@ -1680,10 +1682,43 @@
schemeIdentChars, true) != ATTR_OKAY) {
hasErrors = true;
}
+ } else if (strcmp16(block.getElementName(&len), feature_group16.string()) == 0) {
+ int depth = 1;
+ while ((code=block.next()) != ResXMLTree::END_DOCUMENT
+ && code > ResXMLTree::BAD_DOCUMENT) {
+ if (code == ResXMLTree::START_TAG) {
+ depth++;
+ if (strcmp16(block.getElementName(&len), uses_feature16.string()) == 0) {
+ ssize_t idx = block.indexOfAttribute(
+ RESOURCES_ANDROID_NAMESPACE, "required");
+ if (idx < 0) {
+ continue;
+ }
+
+ int32_t data = block.getAttributeData(idx);
+ if (data == 0) {
+ fprintf(stderr, "%s:%d: Tag <uses-feature> can not have "
+ "android:required=\"false\" when inside a "
+ "<feature-group> tag.\n",
+ manifestPath.string(), block.getLineNumber());
+ hasErrors = true;
+ }
+ }
+ } else if (code == ResXMLTree::END_TAG) {
+ depth--;
+ if (depth == 0) {
+ break;
+ }
+ }
+ }
}
}
}
+ if (hasErrors) {
+ return UNKNOWN_ERROR;
+ }
+
if (resFile != NULL) {
// These resources are now considered to be a part of the included
// resources, for others to reference.