summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api/current.txt12
-rw-r--r--api/system-current.txt13
-rw-r--r--cmds/am/src/com/android/commands/am/Am.java2
-rw-r--r--cmds/pm/src/com/android/commands/pm/Pm.java581
-rw-r--r--core/java/android/app/Activity.java4
-rw-r--r--core/java/android/app/ActivityManager.java145
-rw-r--r--core/java/android/app/AppOpsManager.java2
-rw-r--r--core/java/android/app/Notification.java4
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java2
-rw-r--r--core/java/android/app/usage/UsageStatsManagerInternal.java3
-rw-r--r--core/java/android/content/pm/PackageManager.java2
-rw-r--r--core/java/android/net/http/X509TrustManagerExtensions.java13
-rw-r--r--core/java/android/os/UserManager.java10
-rw-r--r--core/java/android/os/UserManagerInternal.java4
-rw-r--r--core/java/android/util/FloatProperty.java8
-rw-r--r--core/java/android/util/IntProperty.java8
-rw-r--r--core/java/android/view/IWindowSession.aidl17
-rw-r--r--core/java/android/view/Surface.java14
-rw-r--r--core/java/android/view/SurfaceControl.java11
-rw-r--r--core/java/android/view/SurfaceView.java14
-rw-r--r--core/java/android/view/View.java2
-rw-r--r--core/java/android/view/ViewRootImpl.java14
-rw-r--r--core/java/android/view/Window.java4
-rw-r--r--core/java/android/webkit/WebResourceRequest.java4
-rw-r--r--core/java/android/webkit/WebViewFactory.java53
-rw-r--r--core/java/com/android/internal/os/InstallerConnection.java18
-rw-r--r--core/java/com/android/internal/policy/PhoneWindow.java43
-rw-r--r--core/java/com/android/internal/widget/NonClientDecorView.java6
-rwxr-xr-xcore/jni/android/graphics/Bitmap.cpp3
-rw-r--r--core/jni/android_os_Parcel.cpp36
-rw-r--r--core/jni/android_util_AssetManager.cpp44
-rw-r--r--core/jni/android_util_XmlBlock.cpp32
-rw-r--r--core/jni/android_view_DisplayListCanvas.cpp4
-rw-r--r--core/jni/android_view_Surface.cpp5
-rw-r--r--core/jni/android_view_SurfaceControl.cpp19
-rw-r--r--core/res/AndroidManifest.xml2
-rw-r--r--core/res/res/values/config.xml6
-rw-r--r--core/res/res/values/dimens.xml5
-rw-r--r--core/res/res/values/symbols.xml3
-rw-r--r--core/tests/benchmarks/src/android/content/res/ResourcesBenchmark.java84
-rw-r--r--core/tests/coretests/AndroidManifest.xml4
-rw-r--r--core/tests/coretests/src/android/widget/TextViewActivityTest.java6
-rw-r--r--graphics/java/android/graphics/drawable/Icon.java4
-rw-r--r--graphics/java/android/graphics/drawable/LayerDrawable.java4
-rw-r--r--libs/hwui/Android.mk2
-rw-r--r--libs/hwui/BakedOpRenderer.cpp120
-rw-r--r--libs/hwui/BakedOpRenderer.h23
-rw-r--r--libs/hwui/Caches.cpp1
-rw-r--r--libs/hwui/Caches.h1
-rw-r--r--libs/hwui/DisplayList.h6
-rw-r--r--libs/hwui/Extensions.cpp1
-rw-r--r--libs/hwui/Extensions.h6
-rw-r--r--libs/hwui/GlopBuilder.cpp14
-rw-r--r--libs/hwui/GlopBuilder.h1
-rw-r--r--libs/hwui/GradientCache.cpp2
-rw-r--r--libs/hwui/LayerUpdateQueue.cpp42
-rw-r--r--libs/hwui/LayerUpdateQueue.h53
-rw-r--r--libs/hwui/OpReorderer.cpp98
-rw-r--r--libs/hwui/OpReorderer.h36
-rw-r--r--libs/hwui/PathCache.cpp2
-rw-r--r--libs/hwui/Properties.cpp6
-rw-r--r--libs/hwui/Properties.h2
-rw-r--r--libs/hwui/RecordedOp.h34
-rw-r--r--libs/hwui/RecordingCanvas.cpp1
-rw-r--r--libs/hwui/RecordingCanvas.h7
-rw-r--r--libs/hwui/RenderBufferCache.cpp6
-rw-r--r--libs/hwui/RenderNode.cpp64
-rw-r--r--libs/hwui/RenderNode.h29
-rw-r--r--libs/hwui/TreeInfo.h10
-rw-r--r--libs/hwui/microbench/OpReordererBench.cpp4
-rw-r--r--libs/hwui/renderstate/MeshState.cpp18
-rw-r--r--libs/hwui/renderstate/MeshState.h3
-rw-r--r--libs/hwui/renderthread/CanvasContext.cpp12
-rw-r--r--libs/hwui/renderthread/CanvasContext.h8
-rw-r--r--libs/hwui/renderthread/RenderProxy.cpp5
-rw-r--r--libs/hwui/renderthread/RenderProxy.h3
-rw-r--r--libs/hwui/renderthread/RenderThread.cpp29
-rw-r--r--libs/hwui/renderthread/RenderThread.h12
-rw-r--r--libs/hwui/tests/TreeContentAnimation.cpp25
-rw-r--r--libs/hwui/unit_tests/FatVectorTests.cpp29
-rw-r--r--libs/hwui/unit_tests/LayerUpdateQueueTests.cpp82
-rw-r--r--libs/hwui/unit_tests/OpReordererTests.cpp189
-rw-r--r--libs/hwui/unit_tests/RecordingCanvasTests.cpp23
-rw-r--r--libs/hwui/unit_tests/TestUtils.h23
-rw-r--r--libs/hwui/utils/FatVector.h4
-rw-r--r--media/java/android/media/AudioManager.java6
-rw-r--r--packages/DocumentsUI/res/layout/fragment_directory.xml8
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java61
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/DirectoryItemAnimator.java195
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/ManageRootActivity.java17
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/MultiSelectManager.java5
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java10
-rw-r--r--packages/DocumentsUI/tests/Android.mk2
-rw-r--r--packages/DocumentsUI/tests/src/com/android/documentsui/CopyTest.java208
-rw-r--r--packages/DocumentsUI/tests/src/com/android/documentsui/DocumentsProviderHelper.java88
-rw-r--r--packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java155
-rw-r--r--packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java100
-rw-r--r--packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java170
-rw-r--r--packages/Shell/src/com/android/shell/BugreportReceiver.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSPanel.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/Recents.java48
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/RecentsResizeTaskDialog.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/misc/ParametricCurve.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java40
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java301
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java114
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java55
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarApps.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java2
-rw-r--r--services/core/java/com/android/server/MountService.java58
-rw-r--r--services/core/java/com/android/server/NativeDaemonConnector.java16
-rw-r--r--services/core/java/com/android/server/NetworkTimeUpdateService.java13
-rwxr-xr-xservices/core/java/com/android/server/am/ActiveServices.java15
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java376
-rw-r--r--services/core/java/com/android/server/am/ActivityStack.java29
-rw-r--r--services/core/java/com/android/server/am/ActivityStackSupervisor.java35
-rw-r--r--services/core/java/com/android/server/am/PendingIntentRecord.java2
-rw-r--r--services/core/java/com/android/server/am/ProcessRecord.java5
-rw-r--r--services/core/java/com/android/server/am/RecentTasks.java2
-rw-r--r--services/core/java/com/android/server/am/TaskRecord.java15
-rw-r--r--services/core/java/com/android/server/am/UserController.java292
-rw-r--r--services/core/java/com/android/server/am/UserSwitchingDialog.java2
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java6
-rw-r--r--services/core/java/com/android/server/content/AppIdleMonitor.java4
-rw-r--r--services/core/java/com/android/server/content/SyncManager.java13
-rw-r--r--services/core/java/com/android/server/job/controllers/AppIdleController.java4
-rw-r--r--services/core/java/com/android/server/net/NetworkPolicyManagerService.java2
-rw-r--r--services/core/java/com/android/server/pm/Installer.java8
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java15
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerShellCommand.java529
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java636
-rw-r--r--services/core/java/com/android/server/pm/UserRestrictionsUtils.java127
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java4
-rw-r--r--services/core/java/com/android/server/webkit/WebViewUpdateService.java21
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java9
-rw-r--r--services/core/java/com/android/server/wm/DockedStackDividerController.java2
-rw-r--r--services/core/java/com/android/server/wm/Session.java6
-rw-r--r--services/core/java/com/android/server/wm/Task.java13
-rw-r--r--services/core/java/com/android/server/wm/TaskPositioner.java2
-rw-r--r--services/core/java/com/android/server/wm/TaskStack.java35
-rw-r--r--services/core/java/com/android/server/wm/WallpaperController.java2
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java52
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java9
-rw-r--r--services/core/java/com/android/server/wm/WindowStateAnimator.java18
-rw-r--r--services/core/jni/com_android_server_UsbMidiDevice.cpp22
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java166
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java4
-rw-r--r--services/usage/java/com/android/server/usage/UsageStatsService.java42
-rw-r--r--telephony/java/android/telephony/SubscriptionInfo.java17
-rw-r--r--telephony/java/com/android/ims/ImsCallProfile.java14
-rw-r--r--tests/SurfaceComposition/src/android/surfacecomposition/SurfaceCompositionMeasuringActivity.java15
-rw-r--r--tests/SurfaceComposition/src/android/surfacecomposition/SurfaceCompositionTest.java19
-rw-r--r--tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java4
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java7
162 files changed, 4377 insertions, 2278 deletions
diff --git a/api/current.txt b/api/current.txt
index 3b4e3a9eeba2..62f0113a7d1c 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -34268,6 +34268,18 @@ package android.util {
public deprecated class FloatMath {
}
+ public abstract class FloatProperty extends android.util.Property {
+ ctor public FloatProperty(java.lang.String);
+ method public final void set(T, java.lang.Float);
+ method public abstract void setValue(T, float);
+ }
+
+ public abstract class IntProperty extends android.util.Property {
+ ctor public IntProperty(java.lang.String);
+ method public final void set(T, java.lang.Integer);
+ method public abstract void setValue(T, int);
+ }
+
public final class JsonReader implements java.io.Closeable {
ctor public JsonReader(java.io.Reader);
method public void beginArray() throws java.io.IOException;
diff --git a/api/system-current.txt b/api/system-current.txt
index 9db70e3cfa07..4118f338ed64 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -20523,6 +20523,7 @@ package android.net.http {
public class X509TrustManagerExtensions {
ctor public X509TrustManagerExtensions(javax.net.ssl.X509TrustManager) throws java.lang.IllegalArgumentException;
method public java.util.List<java.security.cert.X509Certificate> checkServerTrusted(java.security.cert.X509Certificate[], java.lang.String, java.lang.String) throws java.security.cert.CertificateException;
+ method public boolean isSameTrustConfiguration(java.lang.String, java.lang.String);
method public boolean isUserAddedCertificate(java.security.cert.X509Certificate);
}
@@ -36565,6 +36566,18 @@ package android.util {
public deprecated class FloatMath {
}
+ public abstract class FloatProperty extends android.util.Property {
+ ctor public FloatProperty(java.lang.String);
+ method public final void set(T, java.lang.Float);
+ method public abstract void setValue(T, float);
+ }
+
+ public abstract class IntProperty extends android.util.Property {
+ ctor public IntProperty(java.lang.String);
+ method public final void set(T, java.lang.Integer);
+ method public abstract void setValue(T, int);
+ }
+
public final class JsonReader implements java.io.Closeable {
ctor public JsonReader(java.io.Reader);
method public void beginArray() throws java.io.IOException;
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index 12780a8742b9..7f33cb5df1a3 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -18,7 +18,7 @@
package com.android.commands.am;
-import static android.app.ActivityManager.DOCKED_STACK_ID;
+import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
import static android.app.ActivityManager.RESIZE_MODE_SYSTEM;
import static android.app.ActivityManager.RESIZE_MODE_USER;
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index 2960cdc9084e..ab781bbac6ba 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -25,7 +25,6 @@ import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATIO
import android.accounts.IAccountManager;
import android.app.ActivityManager;
import android.app.ActivityManagerNative;
-import android.app.IActivityManager;
import android.app.PackageInstallObserver;
import android.content.ComponentName;
import android.content.Context;
@@ -34,30 +33,25 @@ 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.InstrumentationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
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;
-import android.content.pm.PermissionGroupInfo;
-import android.content.pm.PermissionInfo;
import android.content.pm.UserInfo;
import android.content.pm.VerificationParams;
-import android.content.res.AssetManager;
-import android.content.res.Resources;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
import android.os.IUserManager;
import android.os.RemoteException;
+import android.os.ResultReceiver;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserHandle;
@@ -80,11 +74,6 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-import java.util.WeakHashMap;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
@@ -96,9 +85,6 @@ public final class Pm {
IUserManager mUm;
IAccountManager mAm;
- private WeakHashMap<String, Resources> mResourceCache
- = new WeakHashMap<String, Resources>();
-
private String[] mArgs;
private int mNextArg;
private String mCurArgData;
@@ -275,10 +261,10 @@ public final class Pm {
if (args.length == 1) {
if (args[0].equalsIgnoreCase("-l")) {
validCommand = true;
- return runListPackages(false);
- } else if (args[0].equalsIgnoreCase("-lf")){
+ return runShellCommand("package", new String[] { "list", "package" });
+ } else if (args[0].equalsIgnoreCase("-lf")) {
validCommand = true;
- return runListPackages(true);
+ return runShellCommand("package", new String[] { "list", "package", "-f" });
}
} else if (args.length == 2) {
if (args[0].equalsIgnoreCase("-p")) {
@@ -297,473 +283,38 @@ public final class Pm {
}
}
- /**
- * Execute the list sub-command.
- *
- * pm list [package | packages]
- * pm list permission-groups
- * pm list permissions
- * pm list features
- * pm list libraries
- * pm list instrumentation
- */
- private int runList() {
- String type = nextArg();
- if (type == null) {
- System.err.println("Error: didn't specify type of data to list");
- return 1;
- }
- if ("package".equals(type) || "packages".equals(type)) {
- return runListPackages(false);
- } else if ("permission-groups".equals(type)) {
- return runListPermissionGroups();
- } else if ("permissions".equals(type)) {
- return runListPermissions();
- } else if ("features".equals(type)) {
- return runListFeatures();
- } else if ("libraries".equals(type)) {
- return runListLibraries();
- } else if ("instrumentation".equals(type)) {
- return runListInstrumentation();
- } else if ("users".equals(type)) {
- return runListUsers();
- } else {
- System.err.println("Error: unknown list type '" + type + "'");
- return 1;
- }
- }
-
- /**
- * Lists all the installed packages.
- */
- private int runListPackages(boolean showApplicationPackage) {
- int getFlags = 0;
- boolean listDisabled = false, listEnabled = false;
- boolean listSystem = false, listThirdParty = false;
- boolean listInstaller = false;
- int userId = UserHandle.USER_SYSTEM;
+ private int runShellCommand(String serviceName, String[] args) {
+ final HandlerThread handlerThread = new HandlerThread("results");
+ handlerThread.start();
try {
- String opt;
- while ((opt=nextOption()) != null) {
- if (opt.equals("-l")) {
- // old compat
- } else if (opt.equals("-lf")) {
- showApplicationPackage = true;
- } else if (opt.equals("-f")) {
- showApplicationPackage = true;
- } else if (opt.equals("-d")) {
- listDisabled = true;
- } else if (opt.equals("-e")) {
- listEnabled = true;
- } else if (opt.equals("-s")) {
- listSystem = true;
- } else if (opt.equals("-3")) {
- listThirdParty = true;
- } else if (opt.equals("-i")) {
- listInstaller = true;
- } else if (opt.equals("--user")) {
- userId = Integer.parseInt(nextArg());
- } else if (opt.equals("-u")) {
- getFlags |= PackageManager.GET_UNINSTALLED_PACKAGES;
- } else {
- System.err.println("Error: Unknown option: " + opt);
- return 1;
- }
- }
- } catch (RuntimeException ex) {
- System.err.println("Error: " + ex.toString());
- return 1;
- }
-
- String filter = nextArg();
-
- try {
- final List<PackageInfo> packages = getInstalledPackages(mPm, getFlags, userId);
-
- int count = packages.size();
- for (int p = 0 ; p < count ; p++) {
- PackageInfo info = packages.get(p);
- if (filter != null && !info.packageName.contains(filter)) {
- continue;
- }
- final boolean isSystem =
- (info.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) != 0;
- if ((!listDisabled || !info.applicationInfo.enabled) &&
- (!listEnabled || info.applicationInfo.enabled) &&
- (!listSystem || isSystem) &&
- (!listThirdParty || !isSystem)) {
- System.out.print("package:");
- if (showApplicationPackage) {
- System.out.print(info.applicationInfo.sourceDir);
- System.out.print("=");
- }
- System.out.print(info.packageName);
- if (listInstaller) {
- System.out.print(" installer=");
- System.out.print(mPm.getInstallerPackageName(info.packageName));
- }
- System.out.println();
- }
- }
+ ServiceManager.getService(serviceName).shellCommand(
+ FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
+ args, new ResultReceiver(new Handler(handlerThread.getLooper())));
return 0;
} catch (RemoteException e) {
- System.err.println(e.toString());
- System.err.println(PM_NOT_RUNNING_ERR);
- return 1;
+ e.printStackTrace();
+ } finally {
+ handlerThread.quitSafely();
}
- }
-
- @SuppressWarnings("unchecked")
- private List<PackageInfo> getInstalledPackages(IPackageManager pm, int flags, int userId)
- throws RemoteException {
- ParceledListSlice<PackageInfo> slice = pm.getInstalledPackages(flags, userId);
- return slice.getList();
+ return -1;
}
/**
- * Lists all of the features supported by the current device.
+ * Execute the list sub-command.
*
+ * pm list [package | packages]
+ * pm list permission-groups
+ * pm list permissions
* pm list features
- */
- private int runListFeatures() {
- try {
- List<FeatureInfo> list = new ArrayList<FeatureInfo>();
- FeatureInfo[] rawList = mPm.getSystemAvailableFeatures();
- for (int i=0; i<rawList.length; i++) {
- list.add(rawList[i]);
- }
-
-
- // Sort by name
- Collections.sort(list, new Comparator<FeatureInfo>() {
- public int compare(FeatureInfo o1, FeatureInfo o2) {
- if (o1.name == o2.name) return 0;
- if (o1.name == null) return -1;
- if (o2.name == null) return 1;
- return o1.name.compareTo(o2.name);
- }
- });
-
- int count = (list != null) ? list.size() : 0;
- for (int p = 0; p < count; p++) {
- FeatureInfo fi = list.get(p);
- System.out.print("feature:");
- if (fi.name != null) System.out.println(fi.name);
- else System.out.println("reqGlEsVersion=0x"
- + Integer.toHexString(fi.reqGlEsVersion));
- }
- return 0;
- } catch (RemoteException e) {
- System.err.println(e.toString());
- System.err.println(PM_NOT_RUNNING_ERR);
- return 1;
- }
- }
-
- /**
- * Lists all of the libraries supported by the current device.
- *
* pm list libraries
+ * pm list instrumentation
*/
- private int runListLibraries() {
- try {
- List<String> list = new ArrayList<String>();
- String[] rawList = mPm.getSystemSharedLibraryNames();
- for (int i=0; i<rawList.length; i++) {
- list.add(rawList[i]);
- }
-
-
- // Sort by name
- Collections.sort(list, new Comparator<String>() {
- public int compare(String o1, String o2) {
- if (o1 == o2) return 0;
- if (o1 == null) return -1;
- if (o2 == null) return 1;
- return o1.compareTo(o2);
- }
- });
-
- int count = (list != null) ? list.size() : 0;
- for (int p = 0; p < count; p++) {
- String lib = list.get(p);
- System.out.print("library:");
- System.out.println(lib);
- }
- return 0;
- } catch (RemoteException e) {
- System.err.println(e.toString());
- System.err.println(PM_NOT_RUNNING_ERR);
- return 1;
- }
- }
-
- /**
- * Lists all of the installed instrumentation, or all for a given package
- *
- * pm list instrumentation [package] [-f]
- */
- private int runListInstrumentation() {
- int flags = 0; // flags != 0 is only used to request meta-data
- boolean showPackage = false;
- String targetPackage = null;
-
- try {
- String opt;
- while ((opt=nextArg()) != null) {
- if (opt.equals("-f")) {
- showPackage = true;
- } else if (opt.charAt(0) != '-') {
- targetPackage = opt;
- } else {
- System.err.println("Error: Unknown option: " + opt);
- return 1;
- }
- }
- } catch (RuntimeException ex) {
- System.err.println("Error: " + ex.toString());
- return 1;
- }
-
- try {
- List<InstrumentationInfo> list = mPm.queryInstrumentation(targetPackage, flags);
-
- // Sort by target package
- Collections.sort(list, new Comparator<InstrumentationInfo>() {
- public int compare(InstrumentationInfo o1, InstrumentationInfo o2) {
- return o1.targetPackage.compareTo(o2.targetPackage);
- }
- });
-
- int count = (list != null) ? list.size() : 0;
- for (int p = 0; p < count; p++) {
- InstrumentationInfo ii = list.get(p);
- System.out.print("instrumentation:");
- if (showPackage) {
- System.out.print(ii.sourceDir);
- System.out.print("=");
- }
- ComponentName cn = new ComponentName(ii.packageName, ii.name);
- System.out.print(cn.flattenToShortString());
- System.out.print(" (target=");
- System.out.print(ii.targetPackage);
- System.out.println(")");
- }
- return 0;
- } catch (RemoteException e) {
- System.err.println(e.toString());
- System.err.println(PM_NOT_RUNNING_ERR);
- return 1;
- }
- }
-
- /**
- * Lists all the known permission groups.
- */
- private int runListPermissionGroups() {
- try {
- List<PermissionGroupInfo> pgs = mPm.getAllPermissionGroups(0);
-
- int count = pgs.size();
- for (int p = 0 ; p < count ; p++) {
- PermissionGroupInfo pgi = pgs.get(p);
- System.out.print("permission group:");
- System.out.println(pgi.name);
- }
- return 0;
- } catch (RemoteException e) {
- System.err.println(e.toString());
- System.err.println(PM_NOT_RUNNING_ERR);
- return 1;
- }
- }
-
- private String loadText(PackageItemInfo pii, int res, CharSequence nonLocalized) {
- if (nonLocalized != null) {
- return nonLocalized.toString();
- }
- if (res != 0) {
- Resources r = getResources(pii);
- if (r != null) {
- try {
- return r.getString(res);
- } catch (Resources.NotFoundException e) {
- }
- }
- }
- return null;
- }
-
- /**
- * Lists all the permissions in a group.
- */
- private int runListPermissions() {
- try {
- boolean labels = false;
- boolean groups = false;
- boolean userOnly = false;
- boolean summary = false;
- boolean dangerousOnly = false;
- String opt;
- while ((opt=nextOption()) != null) {
- if (opt.equals("-f")) {
- labels = true;
- } else if (opt.equals("-g")) {
- groups = true;
- } else if (opt.equals("-s")) {
- groups = true;
- labels = true;
- summary = true;
- } else if (opt.equals("-u")) {
- userOnly = true;
- } else if (opt.equals("-d")) {
- dangerousOnly = true;
- } else {
- System.err.println("Error: Unknown option: " + opt);
- return 1;
- }
- }
-
- String grp = nextArg();
- ArrayList<String> groupList = new ArrayList<String>();
- if (groups) {
- List<PermissionGroupInfo> infos =
- mPm.getAllPermissionGroups(0);
- for (int i=0; i<infos.size(); i++) {
- groupList.add(infos.get(i).name);
- }
- groupList.add(null);
- } else {
- groupList.add(grp);
- }
-
- if (dangerousOnly) {
- System.out.println("Dangerous Permissions:");
- System.out.println("");
- doListPermissions(groupList, groups, labels, summary,
- PermissionInfo.PROTECTION_DANGEROUS,
- PermissionInfo.PROTECTION_DANGEROUS);
- if (userOnly) {
- System.out.println("Normal Permissions:");
- System.out.println("");
- doListPermissions(groupList, groups, labels, summary,
- PermissionInfo.PROTECTION_NORMAL,
- PermissionInfo.PROTECTION_NORMAL);
- }
- } else if (userOnly) {
- System.out.println("Dangerous and Normal Permissions:");
- System.out.println("");
- doListPermissions(groupList, groups, labels, summary,
- PermissionInfo.PROTECTION_NORMAL,
- PermissionInfo.PROTECTION_DANGEROUS);
- } else {
- System.out.println("All Permissions:");
- System.out.println("");
- doListPermissions(groupList, groups, labels, summary,
- -10000, 10000);
- }
- return 0;
- } catch (RemoteException e) {
- System.err.println(e.toString());
- System.err.println(PM_NOT_RUNNING_ERR);
- return 1;
- }
- }
-
- private void doListPermissions(ArrayList<String> groupList,
- boolean groups, boolean labels, boolean summary,
- int startProtectionLevel, int endProtectionLevel)
- throws RemoteException {
- for (int i=0; i<groupList.size(); i++) {
- String groupName = groupList.get(i);
- String prefix = "";
- if (groups) {
- if (i > 0) System.out.println("");
- if (groupName != null) {
- PermissionGroupInfo pgi = mPm.getPermissionGroupInfo(
- groupName, 0);
- if (summary) {
- Resources res = getResources(pgi);
- if (res != null) {
- System.out.print(loadText(pgi, pgi.labelRes,
- pgi.nonLocalizedLabel) + ": ");
- } else {
- System.out.print(pgi.name + ": ");
-
- }
- } else {
- System.out.println((labels ? "+ " : "")
- + "group:" + pgi.name);
- if (labels) {
- System.out.println(" package:" + pgi.packageName);
- Resources res = getResources(pgi);
- if (res != null) {
- System.out.println(" label:"
- + loadText(pgi, pgi.labelRes,
- pgi.nonLocalizedLabel));
- System.out.println(" description:"
- + loadText(pgi, pgi.descriptionRes,
- pgi.nonLocalizedDescription));
- }
- }
- }
- } else {
- System.out.println(((labels && !summary)
- ? "+ " : "") + "ungrouped:");
- }
- prefix = " ";
- }
- List<PermissionInfo> ps = mPm.queryPermissionsByGroup(
- groupList.get(i), 0);
- int count = ps.size();
- boolean first = true;
- for (int p = 0 ; p < count ; p++) {
- PermissionInfo pi = ps.get(p);
- if (groups && groupName == null && pi.group != null) {
- continue;
- }
- final int base = pi.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE;
- if (base < startProtectionLevel
- || base > endProtectionLevel) {
- continue;
- }
- if (summary) {
- if (first) {
- first = false;
- } else {
- System.out.print(", ");
- }
- Resources res = getResources(pi);
- if (res != null) {
- System.out.print(loadText(pi, pi.labelRes,
- pi.nonLocalizedLabel));
- } else {
- System.out.print(pi.name);
- }
- } else {
- System.out.println(prefix + (labels ? "+ " : "")
- + "permission:" + pi.name);
- if (labels) {
- System.out.println(prefix + " package:" + pi.packageName);
- Resources res = getResources(pi);
- if (res != null) {
- System.out.println(prefix + " label:"
- + loadText(pi, pi.labelRes,
- pi.nonLocalizedLabel));
- System.out.println(prefix + " description:"
- + loadText(pi, pi.descriptionRes,
- pi.nonLocalizedDescription));
- }
- System.out.println(prefix + " protectionLevel:"
- + PermissionInfo.protectionToString(pi.protectionLevel));
- }
- }
- }
-
- if (summary) {
- System.out.println("");
- }
+ private int runList() {
+ final String type = nextArg();
+ if ("users".equals(type)) {
+ return runShellCommand("user", new String[] { "list" });
}
+ return runShellCommand("package", mArgs);
}
private int runPath() {
@@ -1467,29 +1018,6 @@ public final class Pm {
}
}
- public int runListUsers() {
- try {
- IActivityManager am = ActivityManagerNative.getDefault();
-
- List<UserInfo> users = mUm.getUsers(false);
- if (users == null) {
- System.err.println("Error: couldn't get users");
- return 1;
- } else {
- System.out.println("Users:");
- for (int i = 0; i < users.size(); i++) {
- String running = am.isUserRunning(users.get(i).id, false) ? " running" : "";
- System.out.println("\t" + users.get(i).toString() + running);
- }
- return 0;
- }
- } catch (RemoteException e) {
- System.err.println(e.toString());
- System.err.println(PM_NOT_RUNNING_ERR);
- return 1;
- }
- }
-
public int runGetMaxUsers() {
System.out.println("Maximum supported users: " + UserManager.getMaxSupportedUsers());
return 0;
@@ -1997,24 +1525,6 @@ public final class Pm {
return 1;
}
- private Resources getResources(PackageItemInfo pii) {
- Resources res = mResourceCache.get(pii.packageName);
- if (res != null) return res;
-
- try {
- ApplicationInfo ai = mPm.getApplicationInfo(pii.packageName, 0, 0);
- AssetManager am = new AssetManager();
- am.addAssetPath(ai.publicSourceDir);
- res = new Resources(am, null, null);
- mResourceCache.put(pii.packageName, res);
- return res;
- } catch (RemoteException e) {
- System.err.println(e.toString());
- System.err.println(PM_NOT_RUNNING_ERR);
- return null;
- }
- }
-
private static String checkAbiArgument(String abi) {
if (TextUtils.isEmpty(abi)) {
throw new IllegalArgumentException("Missing ABI argument");
@@ -2110,14 +1620,7 @@ public final class Pm {
}
private static int showUsage() {
- System.err.println("usage: pm list packages [-f] [-d] [-e] [-s] [-3] [-i] [-u] [--user USER_ID] [FILTER]");
- System.err.println(" pm list permission-groups");
- System.err.println(" pm list permissions [-g] [-f] [-d] [-u] [GROUP]");
- System.err.println(" pm list instrumentation [-f] [TARGET-PACKAGE]");
- System.err.println(" pm list features");
- System.err.println(" pm list libraries");
- System.err.println(" pm list users");
- System.err.println(" pm path [--user USER_ID] PACKAGE");
+ System.err.println("usage: pm path [--user USER_ID] PACKAGE");
System.err.println(" pm dump PACKAGE");
System.err.println(" pm install [-lrtsfd] [-i PACKAGE] [--user USER_ID] [PATH]");
System.err.println(" pm install-create [-lrtsfdp] [-i PACKAGE] [-S BYTES]");
@@ -2151,34 +1654,8 @@ public final class Pm {
System.err.println(" pm remove-user USER_ID");
System.err.println(" pm get-max-users");
System.err.println("");
- System.err.println("pm list packages: prints all packages, optionally only");
- System.err.println(" those whose package name contains the text in FILTER. Options:");
- System.err.println(" -f: see their associated file.");
- System.err.println(" -d: filter to only show disbled packages.");
- System.err.println(" -e: filter to only show enabled packages.");
- System.err.println(" -s: filter to only show system packages.");
- System.err.println(" -3: filter to only show third party packages.");
- System.err.println(" -i: see the installer for the packages.");
- System.err.println(" -u: also include uninstalled packages.");
- System.err.println("");
- System.err.println("pm list permission-groups: prints all known permission groups.");
- System.err.println("");
- System.err.println("pm list permissions: prints all known permissions, optionally only");
- System.err.println(" those in GROUP. Options:");
- System.err.println(" -g: organize by group.");
- System.err.println(" -f: print all information.");
- System.err.println(" -s: short summary.");
- System.err.println(" -d: only list dangerous permissions.");
- System.err.println(" -u: list only the permissions users will see.");
- System.err.println("");
- System.err.println("pm list instrumentation: use to list all test packages; optionally");
- System.err.println(" supply <TARGET-PACKAGE> to list the test packages for a particular");
- System.err.println(" application. Options:");
- System.err.println(" -f: list the .apk file for the test package.");
- System.err.println("");
- System.err.println("pm list features: prints all features of the system.");
- System.err.println("");
- System.err.println("pm list users: prints all users on the system.");
+ System.err.println("NOTE: 'pm list' commands have moved! Run 'adb shell cmd package'");
+ System.err.println(" to display the new commands.");
System.err.println("");
System.err.println("pm path: print the path to the .apk of the given PACKAGE.");
System.err.println("");
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 2f0849f39aa2..472d97fdb9ed 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -2728,8 +2728,8 @@ public class Activity extends ContextThemeWrapper
/**
* Called to move the window and its activity/task to a different stack container.
* For example, a window can move between
- * {@link android.app.ActivityManager#FULLSCREEN_WORKSPACE_STACK_ID} stack and
- * {@link android.app.ActivityManager#FREEFORM_WORKSPACE_STACK_ID} stack.
+ * {@link android.app.ActivityManager.StackId#FULLSCREEN_WORKSPACE_STACK_ID} stack and
+ * {@link android.app.ActivityManager.StackId#FREEFORM_WORKSPACE_STACK_ID} stack.
*
* @param stackId stack Id to change to.
* @hide
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 203f34236723..b809baa28119 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -73,7 +73,6 @@ import java.util.List;
*/
public class ActivityManager {
private static String TAG = "ActivityManager";
- private static boolean localLOGV = false;
private static int gMaxRecentTasks = -1;
@@ -397,60 +396,112 @@ public class ActivityManager {
*/
public static final int COMPAT_MODE_TOGGLE = 2;
- /**
- * Invalid stack ID.
- * @hide
- */
- public static final int INVALID_STACK_ID = -1;
+ /** @hide */
+ public static class StackId {
+ /** Invalid stack ID. */
+ public static final int INVALID_STACK_ID = -1;
- /**
- * First static stack ID.
- * @hide
- */
- public static final int FIRST_STATIC_STACK_ID = 0;
+ /** First static stack ID. */
+ public static final int FIRST_STATIC_STACK_ID = 0;
- /**
- * Home activity stack ID.
- * @hide
- */
- public static final int HOME_STACK_ID = FIRST_STATIC_STACK_ID;
+ /** Home activity stack ID. */
+ public static final int HOME_STACK_ID = FIRST_STATIC_STACK_ID;
- /**
- * ID of stack where fullscreen activities are normally launched into.
- * @hide
- */
- public static final int FULLSCREEN_WORKSPACE_STACK_ID = 1;
+ /** ID of stack where fullscreen activities are normally launched into. */
+ public static final int FULLSCREEN_WORKSPACE_STACK_ID = 1;
- /**
- * ID of stack where freeform/resized activities are normally launched into.
- * @hide
- */
- public static final int FREEFORM_WORKSPACE_STACK_ID = FULLSCREEN_WORKSPACE_STACK_ID + 1;
+ /** ID of stack where freeform/resized activities are normally launched into. */
+ public static final int FREEFORM_WORKSPACE_STACK_ID = FULLSCREEN_WORKSPACE_STACK_ID + 1;
- /**
- * ID of stack that occupies a dedicated region of the screen.
- * @hide
- */
- public static final int DOCKED_STACK_ID = FREEFORM_WORKSPACE_STACK_ID + 1;
+ /** ID of stack that occupies a dedicated region of the screen. */
+ public static final int DOCKED_STACK_ID = FREEFORM_WORKSPACE_STACK_ID + 1;
- /**
- * ID of stack that always on top (always visible) when it exist.
- * Mainly used for this in Picture-in-Picture mode.
- * @hide
- */
- public static final int PINNED_STACK_ID = DOCKED_STACK_ID + 1;
+ /** ID of stack that always on top (always visible) when it exist. */
+ public static final int PINNED_STACK_ID = DOCKED_STACK_ID + 1;
- /**
- * Last static stack stack ID.
- * @hide
- */
- public static final int LAST_STATIC_STACK_ID = PINNED_STACK_ID;
+ /** Last static stack stack ID. */
+ public static final int LAST_STATIC_STACK_ID = PINNED_STACK_ID;
- /**
- * Start of ID range used by stacks that are created dynamically.
- * @hide
- */
- public static final int FIRST_DYNAMIC_STACK_ID = LAST_STATIC_STACK_ID + 1;
+ /** Start of ID range used by stacks that are created dynamically. */
+ public static final int FIRST_DYNAMIC_STACK_ID = LAST_STATIC_STACK_ID + 1;
+
+ public static boolean isStaticStack(int stackId) {
+ return stackId >= FIRST_STATIC_STACK_ID && stackId <= LAST_STATIC_STACK_ID;
+ }
+
+ /**
+ * Returns true if the activities contained in the input stack display a shadow around
+ * their border.
+ */
+ public static boolean hasWindowShadow(int stackId) {
+ return stackId == FREEFORM_WORKSPACE_STACK_ID || stackId == PINNED_STACK_ID;
+ }
+
+ /**
+ * Returns true if the activities contained in the input stack display a decor view.
+ */
+ public static boolean hasWindowDecor(int stackId) {
+ return stackId == FREEFORM_WORKSPACE_STACK_ID;
+ }
+
+ /**
+ * Returns true if the tasks contained in the stack can be resized independently of the
+ * stack.
+ */
+ public static boolean isTaskResizeAllowed(int stackId) {
+ return stackId == FREEFORM_WORKSPACE_STACK_ID;
+ }
+
+ /**
+ * Returns true if the task bounds should persist across power cycles.
+ */
+ public static boolean persistTaskBounds(int stackId) {
+ return isStaticStack(stackId) &&
+ stackId != DOCKED_STACK_ID && stackId != PINNED_STACK_ID;
+ }
+
+ /**
+ * Returns true if dynamic stacks are allowed to be visible behind the input stack.
+ */
+ public static boolean isDynamicStacksVisibleBehindAllowed(int stackId) {
+ return stackId == PINNED_STACK_ID;
+ }
+
+ /**
+ * Returns true if we try to maintain focus in the current stack when the top activity
+ * finishes.
+ */
+ public static boolean keepFocusInStackIfPossible(int stackId) {
+ return stackId == FREEFORM_WORKSPACE_STACK_ID
+ || stackId == DOCKED_STACK_ID || stackId == PINNED_STACK_ID;
+ }
+
+ /**
+ * Returns true if Stack size is affected by the docked stack changing size.
+ */
+ public static boolean isResizeableByDockedStack(int stackId) {
+ return isStaticStack(stackId) &&
+ stackId != DOCKED_STACK_ID && stackId != PINNED_STACK_ID;
+ }
+
+ /**
+ * Returns true if the size of tasks in the input stack are affected by the docked stack
+ * changing size.
+ */
+ public static boolean isTaskResizeableByDockedStack(int stackId) {
+ return isStaticStack(stackId) && stackId != FREEFORM_WORKSPACE_STACK_ID
+ && stackId != DOCKED_STACK_ID && stackId != PINNED_STACK_ID;
+ }
+
+ /**
+ * Returns true if the windows of tasks being moved to this stack should be preserved so
+ * there isn't a display gap.
+ */
+ public static boolean preserveWindowOnTaskMove(int stackId) {
+ return stackId == FULLSCREEN_WORKSPACE_STACK_ID
+ || stackId == DOCKED_STACK_ID || stackId == PINNED_STACK_ID;
+ }
+ }
/**
* Input parameter to {@link android.app.IActivityManager#moveTaskToDockedStack} which
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 09c0a6e3ae46..77a9795bb59f 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -653,7 +653,7 @@ public class AppOpsManager {
null, //WRITE_SETTINGS
UserManager.DISALLOW_CREATE_WINDOWS, //SYSTEM_ALERT_WINDOW
null, //ACCESS_NOTIFICATIONS
- null, //CAMERA
+ UserManager.DISALLOW_CAMERA, //CAMERA
UserManager.DISALLOW_RECORD_AUDIO, //RECORD_AUDIO
null, //PLAY_AUDIO
null, //READ_CLIPBOARD
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 49edff411342..ffeb6ed32950 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -3829,7 +3829,9 @@ public class Notification implements Parcelable
@Override
public void purgeResources() {
super.purgeResources();
- if (mPicture != null && mPicture.isMutable()) {
+ if (mPicture != null &&
+ mPicture.isMutable() &&
+ mPicture.getAllocationByteCount() >= (128 * (1 << 10))) {
mPicture = mPicture.createAshmemBitmap();
}
if (mBigLargeIcon != null) {
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 9e9d9496b461..0fdf3d3693c3 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -2271,6 +2271,8 @@ public class DevicePolicyManager {
* on the device, for this user. After setting this, no applications running as this user
* will be able to access any cameras on the device.
*
+ * <p>If the caller is device owner, then the restriction will be applied to all users.
+ *
* <p>The calling device admin must have requested
* {@link DeviceAdminInfo#USES_POLICY_DISABLE_CAMERA} to be able to call
* this method; if it has not, a security exception will be thrown.
diff --git a/core/java/android/app/usage/UsageStatsManagerInternal.java b/core/java/android/app/usage/UsageStatsManagerInternal.java
index 948ea1effae7..498ff811cc74 100644
--- a/core/java/android/app/usage/UsageStatsManagerInternal.java
+++ b/core/java/android/app/usage/UsageStatsManagerInternal.java
@@ -71,10 +71,11 @@ public abstract class UsageStatsManagerInternal {
* Could be hours, could be days, who knows?
*
* @param packageName
+ * @param uidForAppId The uid of the app, which will be used for its app id
* @param userId
* @return
*/
- public abstract boolean isAppIdle(String packageName, int userId);
+ public abstract boolean isAppIdle(String packageName, int uidForAppId, int userId);
/**
* Returns all of the uids for a given user where all packages associating with that uid
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 27ecf9fc1309..c57fc89d19f8 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1125,7 +1125,7 @@ public abstract class PackageManager {
/**
* Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
- * The device has professional audio level of functionality, performance, and acoustics.
+ * The device has professional audio level of functionality and performance.
*/
@SdkConstant(SdkConstantType.FEATURE)
public static final String FEATURE_AUDIO_PRO = "android.hardware.audio.pro";
diff --git a/core/java/android/net/http/X509TrustManagerExtensions.java b/core/java/android/net/http/X509TrustManagerExtensions.java
index eb4cedab83b7..25ef8b500cda 100644
--- a/core/java/android/net/http/X509TrustManagerExtensions.java
+++ b/core/java/android/net/http/X509TrustManagerExtensions.java
@@ -16,6 +16,8 @@
package android.net.http;
+import android.annotation.SystemApi;
+
import com.android.org.conscrypt.TrustManagerImpl;
import java.security.cert.CertificateException;
@@ -80,4 +82,15 @@ public class X509TrustManagerExtensions {
public boolean isUserAddedCertificate(X509Certificate cert) {
return mDelegate.isUserAddedCertificate(cert);
}
+
+ /**
+ * Returns {@code true} if the TrustManager uses the same trust configuration for the provided
+ * hostnames.
+ *
+ * @hide
+ */
+ @SystemApi
+ public boolean isSameTrustConfiguration(String hostname1, String hostname2) {
+ return true;
+ }
}
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 2e31ab692a51..e892349c34f6 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -487,6 +487,16 @@ public class UserManager {
public static final String DISALLOW_RECORD_AUDIO = "no_record_audio";
/**
+ * Specifies if a user is not allowed to use the camera.
+ *
+ * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+ * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+ * @see #getUserRestrictions()
+ * @hide
+ */
+ public static final String DISALLOW_CAMERA = "no_camera";
+
+ /**
* Allows apps in the parent profile to handle web links from the managed profile.
*
* This user restriction has an effect only in a managed profile.
diff --git a/core/java/android/os/UserManagerInternal.java b/core/java/android/os/UserManagerInternal.java
index d7be6d8c6b29..b50cc79ad341 100644
--- a/core/java/android/os/UserManagerInternal.java
+++ b/core/java/android/os/UserManagerInternal.java
@@ -36,7 +36,7 @@ public abstract class UserManagerInternal {
*
* Must be called while taking the {@link #getUserRestrictionsLock()} lock.
*/
- public abstract void updateEffectiveUserRestrictionsRL(int userId);
+ public abstract void updateEffectiveUserRestrictionsLR(int userId);
/**
* Called by {@link com.android.server.devicepolicy.DevicePolicyManagerService} to get
@@ -44,7 +44,7 @@ public abstract class UserManagerInternal {
*
* Must be called while taking the {@link #getUserRestrictionsLock()} lock.
*/
- public abstract void updateEffectiveUserRestrictionsForAllUsersRL();
+ public abstract void updateEffectiveUserRestrictionsForAllUsersLR();
/**
* Returns the "base" user restrictions.
diff --git a/core/java/android/util/FloatProperty.java b/core/java/android/util/FloatProperty.java
index a67b3cb09f7a..4aac1962ebcf 100644
--- a/core/java/android/util/FloatProperty.java
+++ b/core/java/android/util/FloatProperty.java
@@ -15,18 +15,14 @@
*/
package android.util;
-import android.util.Property;
-
/**
* An implementation of {@link android.util.Property} to be used specifically with fields of type
* <code>float</code>. This type-specific subclass enables performance benefit by allowing
- * calls to a {@link #set(Object, Float) set()} function that takes the primitive
+ * calls to a {@link #setValue(Object, float) setValue()} function that takes the primitive
* <code>float</code> type and avoids autoboxing and other overhead associated with the
* <code>Float</code> class.
*
* @param <T> The class on which the Property is declared.
- *
- * @hide
*/
public abstract class FloatProperty<T> extends Property<T, Float> {
@@ -35,7 +31,7 @@ public abstract class FloatProperty<T> extends Property<T, Float> {
}
/**
- * A type-specific override of the {@link #set(Object, Float)} that is faster when dealing
+ * A type-specific variant of {@link #set(Object, Float)} that is faster when dealing
* with fields of type <code>float</code>.
*/
public abstract void setValue(T object, float value);
diff --git a/core/java/android/util/IntProperty.java b/core/java/android/util/IntProperty.java
index 17977ca63518..9e21cedf1b4b 100644
--- a/core/java/android/util/IntProperty.java
+++ b/core/java/android/util/IntProperty.java
@@ -15,18 +15,14 @@
*/
package android.util;
-import android.util.Property;
-
/**
* An implementation of {@link android.util.Property} to be used specifically with fields of type
* <code>int</code>. This type-specific subclass enables performance benefit by allowing
- * calls to a {@link #set(Object, Integer) set()} function that takes the primitive
+ * calls to a {@link #setValue(Object, int) setValue()} function that takes the primitive
* <code>int</code> type and avoids autoboxing and other overhead associated with the
* <code>Integer</code> class.
*
* @param <T> The class on which the Property is declared.
- *
- * @hide
*/
public abstract class IntProperty<T> extends Property<T, Integer> {
@@ -35,7 +31,7 @@ public abstract class IntProperty<T> extends Property<T, Integer> {
}
/**
- * A type-specific override of the {@link #set(Object, Integer)} that is faster when dealing
+ * A type-specific variant of {@link #set(Object, Integer)} that is faster when dealing
* with fields of type <code>int</code>.
*/
public abstract void setValue(T object, int value);
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 017364a5a0b1..1be2f95cf55a 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -96,6 +96,23 @@ interface IWindowSession {
out Rect outOutsets, out Configuration outConfig, out Surface outSurface);
/**
+ * Position a window relative to it's parent (attached) window without triggering
+ * a full relayout. This action may be deferred until a given frame number
+ * for the parent window appears. This allows for synchronizing movement of a child
+ * to repainting the contents of the parent.
+ *
+ * @param window The window being modified. Must be attached to a parent window
+ * or this call will fail.
+ * @param x The new x position
+ * @param y The new y position
+ * @param deferTransactionUntilFrame Frame number from our parent (attached) to
+ * defer this action until.
+ * @param outFrame Rect in which is placed the new position/size on screen.
+ */
+ void repositionChild(IWindow childWindow, int x, int y, long deferTransactionUntilFrame,
+ out Rect outFrame);
+
+ /**
* If a call to relayout() asked to have the surface destroy deferred,
* it must call this once it is okay to destroy that surface.
*/
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index 6de4d3e4e4c3..394660fb538b 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -56,6 +56,8 @@ public class Surface implements Parcelable {
private static native int nativeGetWidth(long nativeObject);
private static native int nativeGetHeight(long nativeObject);
+ private static native long nativeGetNextFrameNumber(long nativeObject);
+
public static final Parcelable.Creator<Surface> CREATOR =
new Parcelable.Creator<Surface>() {
@Override
@@ -220,6 +222,18 @@ public class Surface implements Parcelable {
}
/**
+ * Returns the next frame number which will be dequeued for rendering.
+ * Intended for use with SurfaceFlinger's deferred transactions API.
+ *
+ * @hide
+ */
+ public long getNextFrameNumber() {
+ synchronized (mLock) {
+ return nativeGetNextFrameNumber(mNativeObject);
+ }
+ }
+
+ /**
* Returns true if the consumer of this Surface is running behind the producer.
*
* @return True if the consumer is more than one buffer ahead of the producer.
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index bcf9b2c7fe7c..b58c68f782ee 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -81,6 +81,9 @@ public class SurfaceControl {
private static native boolean nativeSetActiveConfig(IBinder displayToken, int id);
private static native void nativeSetDisplayPowerMode(
IBinder displayToken, int mode);
+ private static native void nativeDeferTransactionUntil(long nativeObject,
+ IBinder handle, long frame);
+ private static native IBinder nativeGetHandle(long nativeObject);
private final CloseGuard mCloseGuard = CloseGuard.get();
@@ -358,6 +361,14 @@ public class SurfaceControl {
nativeCloseTransaction();
}
+ public void deferTransactionUntil(IBinder handle, long frame) {
+ nativeDeferTransactionUntil(mNativeObject, handle, frame);
+ }
+
+ public IBinder getHandle() {
+ return nativeGetHandle(mNativeObject);
+ }
+
/** flag the transaction as an animation */
public static void setAnimationTransaction() {
nativeSetAnimationTransaction();
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index db68c29d025f..dddea210ed83 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -448,11 +448,10 @@ public class SurfaceView extends View {
final boolean sizeChanged = mWidth != myWidth || mHeight != myHeight;
final boolean visibleChanged = mVisible != mRequestedVisible;
final boolean layoutSizeChanged = getWidth() != mLayout.width || getHeight() != mLayout.height;
+ final boolean positionChanged = mLeft != mLocation[0] || mTop != mLocation[1];
if (force || creating || formatChanged || sizeChanged || visibleChanged
- || mLeft != mLocation[0] || mTop != mLocation[1]
|| mUpdateWindowNeeded || mReportDrawNeeded || redrawNeeded || layoutSizeChanged) {
-
if (DEBUG) Log.i(TAG, "Changes: creating=" + creating
+ " format=" + formatChanged + " size=" + sizeChanged
+ " visible=" + visibleChanged
@@ -616,11 +615,22 @@ public class SurfaceView extends View {
mSession.performDeferredDestroy(mWindow);
}
} catch (RemoteException ex) {
+ Log.e(TAG, "Exception from relayout", ex);
}
if (DEBUG) Log.v(
TAG, "Layout: x=" + mLayout.x + " y=" + mLayout.y +
" w=" + mLayout.width + " h=" + mLayout.height +
", frame=" + mSurfaceFrame);
+ } else if (positionChanged) { // Only the position has changed
+ mLeft = mLocation[0];
+ mTop = mLocation[1];
+ try {
+ mSession.repositionChild(mWindow, mLeft, mTop,
+ viewRoot != null ? viewRoot.getNextFrameNumber() : -1,
+ mWinFrame);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Exception from relayout", ex);
+ }
}
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 8b804e8ddc8a..cd8c084ad14f 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -5455,7 +5455,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @hide
*/
protected boolean performButtonActionOnTouchDown(MotionEvent event) {
- if (event.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE &&
+ if (event.isFromSource(InputDevice.SOURCE_MOUSE) &&
(event.getButtonState() & MotionEvent.BUTTON_SECONDARY) != 0) {
showContextMenu(event.getX(), event.getY());
mPrivateFlags |= PFLAG_CANCEL_NEXT_UP_EVENT;
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index e17bdd7d9a3e..faeb35313174 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -6607,6 +6607,20 @@ public final class ViewRootImpl implements ViewParent,
}
}
+ long getNextFrameNumber() {
+ long frameNumber = -1;
+ if (mSurfaceHolder != null) {
+ mSurfaceHolder.mSurfaceLock.lock();
+ }
+ if (mSurface.isValid()) {
+ frameNumber = mSurface.getNextFrameNumber();
+ }
+ if (mSurfaceHolder != null) {
+ mSurfaceHolder.mSurfaceLock.unlock();
+ }
+ return frameNumber;
+ }
+
class TakenSurfaceHolder extends BaseSurfaceHolder {
@Override
public boolean onAllowLockCanvas() {
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 5d11c8b7d888..825937289233 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -552,8 +552,8 @@ public abstract class Window {
/**
* Called to move the window and its activity/task to a different stack container.
* For example, a window can move between
- * {@link android.app.ActivityManager#FULLSCREEN_WORKSPACE_STACK_ID} stack and
- * {@link android.app.ActivityManager#FREEFORM_WORKSPACE_STACK_ID} stack.
+ * {@link android.app.ActivityManager.StackId#FULLSCREEN_WORKSPACE_STACK_ID} stack and
+ * {@link android.app.ActivityManager.StackId#FREEFORM_WORKSPACE_STACK_ID} stack.
*
* @param stackId stack Id to change to.
*/
diff --git a/core/java/android/webkit/WebResourceRequest.java b/core/java/android/webkit/WebResourceRequest.java
index 23e9a0df56fb..ab935050eb73 100644
--- a/core/java/android/webkit/WebResourceRequest.java
+++ b/core/java/android/webkit/WebResourceRequest.java
@@ -40,9 +40,9 @@ public interface WebResourceRequest {
boolean isForMainFrame();
/**
- * Gets whether the request was a result of a redirect.
+ * Gets whether the request was a result of a server-side redirect.
*
- * @return whether the request was a result of a redirect.
+ * @return whether the request was a result of a server-side redirect.
*/
boolean isRedirect();
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index 584deff2659b..cb18b49b65f8 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -96,27 +96,49 @@ public final class WebViewFactory {
public MissingWebViewPackageException(Exception e) { super(e); }
}
+ /** @hide */
+ public static String[] getWebViewPackageNames() {
+ return AppGlobals.getInitialApplication().getResources().getStringArray(
+ com.android.internal.R.array.config_webViewPackageNames);
+ }
+
+ // TODO (gsennton) remove when committing webview xts test change
public static String getWebViewPackageName() {
- return AppGlobals.getInitialApplication().getString(
- com.android.internal.R.string.config_webViewPackageName);
+ String[] webViewPackageNames = getWebViewPackageNames();
+ return webViewPackageNames[webViewPackageNames.length-1];
}
- private static PackageInfo fetchPackageInfo() {
+ /**
+ * Return the package info of the first package in the webview priority list that contains
+ * webview.
+ *
+ * @hide
+ */
+ public static PackageInfo findPreferredWebViewPackage() {
PackageManager pm = AppGlobals.getInitialApplication().getPackageManager();
- try {
- return pm.getPackageInfo(getWebViewPackageName(), PackageManager.GET_META_DATA);
- } catch (PackageManager.NameNotFoundException e) {
- throw new MissingWebViewPackageException(e);
+
+ for (String packageName : getWebViewPackageNames()) {
+ try {
+ PackageInfo packageInfo = pm.getPackageInfo(packageName,
+ PackageManager.GET_META_DATA);
+ ApplicationInfo applicationInfo = packageInfo.applicationInfo;
+
+ // If the correct flag is set the package contains webview.
+ if (getWebViewLibrary(applicationInfo) != null) {
+ return packageInfo;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ }
}
+ throw new MissingWebViewPackageException("Could not find a loadable WebView package");
}
// throws MissingWebViewPackageException
private static ApplicationInfo getWebViewApplicationInfo() {
- if (sPackageInfo == null) {
- return fetchPackageInfo().applicationInfo;
- } else {
+ if (sPackageInfo == null)
+ return findPreferredWebViewPackage().applicationInfo;
+ else
return sPackageInfo.applicationInfo;
- }
}
private static String getWebViewLibrary(ApplicationInfo ai) {
@@ -134,7 +156,12 @@ public final class WebViewFactory {
* name is the same as the one providing the webview.
*/
public static int loadWebViewNativeLibraryFromPackage(String packageName) {
- sPackageInfo = fetchPackageInfo();
+ try {
+ sPackageInfo = findPreferredWebViewPackage();
+ } catch (MissingWebViewPackageException e) {
+ return LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES;
+ }
+
if (packageName != null && packageName.equals(sPackageInfo.packageName)) {
return loadNativeLibrary();
}
@@ -180,7 +207,7 @@ public final class WebViewFactory {
private static Class<WebViewFactoryProvider> getProviderClass() {
try {
// First fetch the package info so we can log the webview package version.
- sPackageInfo = fetchPackageInfo();
+ sPackageInfo = findPreferredWebViewPackage();
Log.i(LOGTAG, "Loading " + sPackageInfo.packageName + " version " +
sPackageInfo.versionName + " (code " + sPackageInfo.versionCode + ")");
diff --git a/core/java/com/android/internal/os/InstallerConnection.java b/core/java/com/android/internal/os/InstallerConnection.java
index 13d046e45070..830da79c2a0b 100644
--- a/core/java/com/android/internal/os/InstallerConnection.java
+++ b/core/java/com/android/internal/os/InstallerConnection.java
@@ -21,6 +21,8 @@ import android.net.LocalSocketAddress;
import android.os.SystemClock;
import android.util.Slog;
+import com.android.internal.util.Preconditions;
+
import libcore.io.IoUtils;
import libcore.io.Streams;
@@ -42,11 +44,22 @@ public class InstallerConnection {
private OutputStream mOut;
private LocalSocket mSocket;
+ private volatile Object mWarnIfHeld;
+
private final byte buf[] = new byte[1024];
public InstallerConnection() {
}
+ /**
+ * Yell loudly if someone tries making future calls while holding a lock on
+ * the given object.
+ */
+ public void setWarnIfHeld(Object warnIfHeld) {
+ Preconditions.checkState(mWarnIfHeld == null);
+ mWarnIfHeld = Preconditions.checkNotNull(warnIfHeld);
+ }
+
public synchronized String transact(String cmd) {
if (!connect()) {
Slog.e(TAG, "connection failed");
@@ -84,6 +97,11 @@ public class InstallerConnection {
}
public int execute(String cmd) {
+ if (mWarnIfHeld != null && Thread.holdsLock(mWarnIfHeld)) {
+ Slog.wtf(TAG, "Calling thread " + Thread.currentThread().getName() + " is holding 0x"
+ + Integer.toHexString(System.identityHashCode(mWarnIfHeld)), new Throwable());
+ }
+
String res = transact(cmd);
try {
return Integer.parseInt(res);
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index b3bd46dd3869..1bce585c8358 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -16,11 +16,8 @@
package com.android.internal.policy;
-import static android.app.ActivityManager.FIRST_DYNAMIC_STACK_ID;
-import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.INVALID_STACK_ID;
-import static android.app.ActivityManager.PINNED_STACK_ID;
+import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
import static android.view.View.MeasureSpec.AT_MOST;
import static android.view.View.MeasureSpec.EXACTLY;
import static android.view.View.MeasureSpec.getMode;
@@ -30,6 +27,7 @@ import static android.view.WindowManager.LayoutParams.*;
import android.animation.Animator;
import android.animation.ObjectAnimator;
+import android.app.ActivityManager.StackId;
import android.app.ActivityManagerNative;
import android.app.SearchManager;
import android.os.Build;
@@ -737,9 +735,9 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
if (mWorkspaceId != workspaceId) {
mWorkspaceId = workspaceId;
// We might have to change the kind of surface before we do anything else.
- mNonClientDecorView.phoneWindowUpdated(hasNonClientDecor(mWorkspaceId),
- nonClientDecorHasShadow(mWorkspaceId));
- mDecor.enableNonClientDecor(hasNonClientDecor(workspaceId));
+ mNonClientDecorView.phoneWindowUpdated(StackId.hasWindowDecor(mWorkspaceId),
+ StackId.hasWindowShadow(mWorkspaceId));
+ mDecor.enableNonClientDecor(StackId.hasWindowDecor(workspaceId));
}
}
}
@@ -3735,7 +3733,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
* @return Returns true when the window has a shadow created by the non client decor.
**/
private boolean windowHasShadow() {
- return windowHasNonClientDecor() && nonClientDecorHasShadow(mWindow.mWorkspaceId);
+ return windowHasNonClientDecor() && StackId.hasWindowShadow(mWindow.mWorkspaceId);
}
void setWindow(PhoneWindow phoneWindow) {
@@ -4234,7 +4232,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
mWorkspaceId = getWorkspaceId();
// Only a non floating application window on one of the allowed workspaces can get a non
// client decor.
- if (!isFloating() && isApplication && mWorkspaceId < FIRST_DYNAMIC_STACK_ID) {
+ if (!isFloating() && isApplication && StackId.isStaticStack(mWorkspaceId)) {
// Dependent on the brightness of the used title we either use the
// dark or the light button frame.
if (nonClientDecorView == null) {
@@ -4250,12 +4248,13 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
R.layout.non_client_decor_light, null);
}
}
- nonClientDecorView.setPhoneWindow(this, hasNonClientDecor(mWorkspaceId),
- nonClientDecorHasShadow(mWorkspaceId), getResizingBackgroundDrawable(),
+ nonClientDecorView.setPhoneWindow(this, StackId.hasWindowDecor(mWorkspaceId),
+ StackId.hasWindowShadow(mWorkspaceId), getResizingBackgroundDrawable(),
mDecor.getContext().getDrawable(R.drawable.non_client_decor_title_focused));
}
// Tell the decor if it has a visible non client decor.
- mDecor.enableNonClientDecor(nonClientDecorView != null && hasNonClientDecor(mWorkspaceId));
+ mDecor.enableNonClientDecor(
+ nonClientDecorView != null&& StackId.hasWindowDecor(mWorkspaceId));
return nonClientDecorView;
}
@@ -5428,24 +5427,6 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
return workspaceId;
}
- /**
- * Determines if the window should show a non client decor for the workspace it is in.
- * @param workspaceId The Id of the workspace which contains this window.
- * @Return Returns true if the window should show a non client decor.
- **/
- private static boolean hasNonClientDecor(int workspaceId) {
- return workspaceId == FREEFORM_WORKSPACE_STACK_ID;
- }
-
- /**
- * Determines if the window should show a shadow or not, dependent on the workspace.
- * @param workspaceId The Id of the workspace which contains this window.
- * @Return Returns true if the window should show a shadow.
- **/
- private static boolean nonClientDecorHasShadow(int workspaceId) {
- return workspaceId == FREEFORM_WORKSPACE_STACK_ID || workspaceId == PINNED_STACK_ID;
- }
-
@Override
public void setTheme(int resid) {
mTheme = resid;
diff --git a/core/java/com/android/internal/widget/NonClientDecorView.java b/core/java/com/android/internal/widget/NonClientDecorView.java
index be9be11f9da6..de542b1bc65f 100644
--- a/core/java/com/android/internal/widget/NonClientDecorView.java
+++ b/core/java/com/android/internal/widget/NonClientDecorView.java
@@ -16,8 +16,9 @@
package com.android.internal.widget;
+import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
+
import android.content.Context;
-import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Looper;
@@ -332,8 +333,7 @@ public class NonClientDecorView extends LinearLayout
Window.WindowControllerCallback callback = mOwner.getWindowControllerCallback();
if (callback != null) {
try {
- callback.changeWindowStack(
- android.app.ActivityManager.FULLSCREEN_WORKSPACE_STACK_ID);
+ callback.changeWindowStack(FULLSCREEN_WORKSPACE_STACK_ID);
} catch (RemoteException ex) {
Log.e(TAG, "Cannot change task workspace.");
}
diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp
index e6c7c2bcf9d7..703a9bd2fcd5 100755
--- a/core/jni/android/graphics/Bitmap.cpp
+++ b/core/jni/android/graphics/Bitmap.cpp
@@ -28,6 +28,7 @@
#include <cutils/ashmem.h>
#define DEBUG_PARCEL 0
+#define ASHMEM_BITMAP_MIN_SIZE (128 * (1 << 10))
namespace android {
@@ -993,7 +994,7 @@ static jobject Bitmap_createFromParcel(JNIEnv* env, jobject, jobject parcel) {
// Map the bitmap in place from the ashmem region if possible otherwise copy.
Bitmap* nativeBitmap;
- if (blob.fd() >= 0 && (blob.isMutable() || !isMutable)) {
+ if (blob.fd() >= 0 && (blob.isMutable() || !isMutable) && (size >= ASHMEM_BITMAP_MIN_SIZE)) {
#if DEBUG_PARCEL
ALOGD("Bitmap.createFromParcel: mapped contents of %s bitmap from %s blob "
"(fds %s)",
diff --git a/core/jni/android_os_Parcel.cpp b/core/jni/android_os_Parcel.cpp
index 41aa9ca977fb..0a8ae2bdeecd 100644
--- a/core/jni/android_os_Parcel.cpp
+++ b/core/jni/android_os_Parcel.cpp
@@ -722,33 +722,33 @@ static jlong android_os_Parcel_getBlobAshmemSize(JNIEnv* env, jclass clazz, jlon
// ----------------------------------------------------------------------------
static const JNINativeMethod gParcelMethods[] = {
- {"nativeDataSize", "(J)I", (void*)android_os_Parcel_dataSize},
- {"nativeDataAvail", "(J)I", (void*)android_os_Parcel_dataAvail},
- {"nativeDataPosition", "(J)I", (void*)android_os_Parcel_dataPosition},
- {"nativeDataCapacity", "(J)I", (void*)android_os_Parcel_dataCapacity},
- {"nativeSetDataSize", "(JI)J", (void*)android_os_Parcel_setDataSize},
- {"nativeSetDataPosition", "(JI)V", (void*)android_os_Parcel_setDataPosition},
- {"nativeSetDataCapacity", "(JI)V", (void*)android_os_Parcel_setDataCapacity},
+ {"nativeDataSize", "!(J)I", (void*)android_os_Parcel_dataSize},
+ {"nativeDataAvail", "!(J)I", (void*)android_os_Parcel_dataAvail},
+ {"nativeDataPosition", "!(J)I", (void*)android_os_Parcel_dataPosition},
+ {"nativeDataCapacity", "!(J)I", (void*)android_os_Parcel_dataCapacity},
+ {"nativeSetDataSize", "!(JI)J", (void*)android_os_Parcel_setDataSize},
+ {"nativeSetDataPosition", "!(JI)V", (void*)android_os_Parcel_setDataPosition},
+ {"nativeSetDataCapacity", "!(JI)V", (void*)android_os_Parcel_setDataCapacity},
- {"nativePushAllowFds", "(JZ)Z", (void*)android_os_Parcel_pushAllowFds},
- {"nativeRestoreAllowFds", "(JZ)V", (void*)android_os_Parcel_restoreAllowFds},
+ {"nativePushAllowFds", "!(JZ)Z", (void*)android_os_Parcel_pushAllowFds},
+ {"nativeRestoreAllowFds", "!(JZ)V", (void*)android_os_Parcel_restoreAllowFds},
{"nativeWriteByteArray", "(J[BII)V", (void*)android_os_Parcel_writeNative},
{"nativeWriteBlob", "(J[BII)V", (void*)android_os_Parcel_writeBlob},
- {"nativeWriteInt", "(JI)V", (void*)android_os_Parcel_writeInt},
- {"nativeWriteLong", "(JJ)V", (void*)android_os_Parcel_writeLong},
- {"nativeWriteFloat", "(JF)V", (void*)android_os_Parcel_writeFloat},
- {"nativeWriteDouble", "(JD)V", (void*)android_os_Parcel_writeDouble},
+ {"nativeWriteInt", "!(JI)V", (void*)android_os_Parcel_writeInt},
+ {"nativeWriteLong", "!(JJ)V", (void*)android_os_Parcel_writeLong},
+ {"nativeWriteFloat", "!(JF)V", (void*)android_os_Parcel_writeFloat},
+ {"nativeWriteDouble", "!(JD)V", (void*)android_os_Parcel_writeDouble},
{"nativeWriteString", "(JLjava/lang/String;)V", (void*)android_os_Parcel_writeString},
{"nativeWriteStrongBinder", "(JLandroid/os/IBinder;)V", (void*)android_os_Parcel_writeStrongBinder},
{"nativeWriteFileDescriptor", "(JLjava/io/FileDescriptor;)J", (void*)android_os_Parcel_writeFileDescriptor},
{"nativeCreateByteArray", "(J)[B", (void*)android_os_Parcel_createByteArray},
{"nativeReadBlob", "(J)[B", (void*)android_os_Parcel_readBlob},
- {"nativeReadInt", "(J)I", (void*)android_os_Parcel_readInt},
- {"nativeReadLong", "(J)J", (void*)android_os_Parcel_readLong},
- {"nativeReadFloat", "(J)F", (void*)android_os_Parcel_readFloat},
- {"nativeReadDouble", "(J)D", (void*)android_os_Parcel_readDouble},
+ {"nativeReadInt", "!(J)I", (void*)android_os_Parcel_readInt},
+ {"nativeReadLong", "!(J)J", (void*)android_os_Parcel_readLong},
+ {"nativeReadFloat", "!(J)F", (void*)android_os_Parcel_readFloat},
+ {"nativeReadDouble", "!(J)D", (void*)android_os_Parcel_readDouble},
{"nativeReadString", "(J)Ljava/lang/String;", (void*)android_os_Parcel_readString},
{"nativeReadStrongBinder", "(J)Landroid/os/IBinder;", (void*)android_os_Parcel_readStrongBinder},
{"nativeReadFileDescriptor", "(J)Ljava/io/FileDescriptor;", (void*)android_os_Parcel_readFileDescriptor},
@@ -765,7 +765,7 @@ static const JNINativeMethod gParcelMethods[] = {
{"nativeMarshall", "(J)[B", (void*)android_os_Parcel_marshall},
{"nativeUnmarshall", "(J[BII)J", (void*)android_os_Parcel_unmarshall},
{"nativeAppendFrom", "(JJII)J", (void*)android_os_Parcel_appendFrom},
- {"nativeHasFileDescriptors", "(J)Z", (void*)android_os_Parcel_hasFileDescriptors},
+ {"nativeHasFileDescriptors", "!(J)Z", (void*)android_os_Parcel_hasFileDescriptors},
{"nativeWriteInterfaceToken", "(JLjava/lang/String;)V", (void*)android_os_Parcel_writeInterfaceToken},
{"nativeEnforceInterface", "(JLjava/lang/String;)V", (void*)android_os_Parcel_enforceInterface},
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index c94bc649014e..55b7e7ec8325 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -2156,9 +2156,9 @@ static const JNINativeMethod gAssetManagerMethods[] = {
(void*) android_content_AssetManager_readAsset },
{ "seekAsset", "(JJI)J",
(void*) android_content_AssetManager_seekAsset },
- { "getAssetLength", "(J)J",
+ { "getAssetLength", "!(J)J",
(void*) android_content_AssetManager_getAssetLength },
- { "getAssetRemainingLength", "(J)J",
+ { "getAssetRemainingLength", "!(J)J",
(void*) android_content_AssetManager_getAssetRemainingLength },
{ "addAssetPathNative", "(Ljava/lang/String;Z)I",
(void*) android_content_AssetManager_addAssetPath },
@@ -2174,25 +2174,25 @@ static const JNINativeMethod gAssetManagerMethods[] = {
(void*) android_content_AssetManager_getLocales },
{ "getSizeConfigurations", "()[Landroid/content/res/Configuration;",
(void*) android_content_AssetManager_getSizeConfigurations },
- { "setConfiguration", "(IILjava/lang/String;IIIIIIIIIIIIII)V",
+ { "setConfiguration", "!(IILjava/lang/String;IIIIIIIIIIIIII)V",
(void*) android_content_AssetManager_setConfiguration },
- { "getResourceIdentifier","(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I",
+ { "getResourceIdentifier","!(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I",
(void*) android_content_AssetManager_getResourceIdentifier },
- { "getResourceName","(I)Ljava/lang/String;",
+ { "getResourceName","!(I)Ljava/lang/String;",
(void*) android_content_AssetManager_getResourceName },
- { "getResourcePackageName","(I)Ljava/lang/String;",
+ { "getResourcePackageName","!(I)Ljava/lang/String;",
(void*) android_content_AssetManager_getResourcePackageName },
- { "getResourceTypeName","(I)Ljava/lang/String;",
+ { "getResourceTypeName","!(I)Ljava/lang/String;",
(void*) android_content_AssetManager_getResourceTypeName },
- { "getResourceEntryName","(I)Ljava/lang/String;",
+ { "getResourceEntryName","!(I)Ljava/lang/String;",
(void*) android_content_AssetManager_getResourceEntryName },
- { "loadResourceValue","(ISLandroid/util/TypedValue;Z)I",
+ { "loadResourceValue","!(ISLandroid/util/TypedValue;Z)I",
(void*) android_content_AssetManager_loadResourceValue },
- { "loadResourceBagValue","(IILandroid/util/TypedValue;Z)I",
+ { "loadResourceBagValue","!(IILandroid/util/TypedValue;Z)I",
(void*) android_content_AssetManager_loadResourceBagValue },
- { "getStringBlockCount","()I",
+ { "getStringBlockCount","!()I",
(void*) android_content_AssetManager_getStringBlockCount },
- { "getNativeStringBlock","(I)J",
+ { "getNativeStringBlock","!(I)J",
(void*) android_content_AssetManager_getNativeStringBlock },
{ "getCookieName","(I)Ljava/lang/String;",
(void*) android_content_AssetManager_getCookieName },
@@ -2210,21 +2210,21 @@ static const JNINativeMethod gAssetManagerMethods[] = {
(void*) android_content_AssetManager_copyTheme },
{ "clearTheme", "(J)V",
(void*) android_content_AssetManager_clearTheme },
- { "loadThemeAttributeValue", "(JILandroid/util/TypedValue;Z)I",
+ { "loadThemeAttributeValue", "!(JILandroid/util/TypedValue;Z)I",
(void*) android_content_AssetManager_loadThemeAttributeValue },
- { "getThemeChangingConfigurations", "(J)I",
+ { "getThemeChangingConfigurations", "!(J)I",
(void*) android_content_AssetManager_getThemeChangingConfigurations },
{ "dumpTheme", "(JILjava/lang/String;Ljava/lang/String;)V",
(void*) android_content_AssetManager_dumpTheme },
- { "applyStyle","(JIIJ[I[I[I)Z",
+ { "applyStyle","!(JIIJ[I[I[I)Z",
(void*) android_content_AssetManager_applyStyle },
- { "resolveAttrs","(JII[I[I[I[I)Z",
+ { "resolveAttrs","!(JII[I[I[I[I)Z",
(void*) android_content_AssetManager_resolveAttrs },
- { "retrieveAttributes","(J[I[I[I)Z",
+ { "retrieveAttributes","!(J[I[I[I)Z",
(void*) android_content_AssetManager_retrieveAttributes },
- { "getArraySize","(I)I",
+ { "getArraySize","!(I)I",
(void*) android_content_AssetManager_getArraySize },
- { "retrieveArray","(I[I)I",
+ { "retrieveArray","!(I[I)I",
(void*) android_content_AssetManager_retrieveArray },
// XML files.
@@ -2234,11 +2234,11 @@ static const JNINativeMethod gAssetManagerMethods[] = {
// Arrays.
{ "getArrayStringResource","(I)[Ljava/lang/String;",
(void*) android_content_AssetManager_getArrayStringResource },
- { "getArrayStringInfo","(I)[I",
+ { "getArrayStringInfo","!(I)[I",
(void*) android_content_AssetManager_getArrayStringInfo },
- { "getArrayIntResource","(I)[I",
+ { "getArrayIntResource","!(I)[I",
(void*) android_content_AssetManager_getArrayIntResource },
- { "getStyleAttributes","(I)[I",
+ { "getStyleAttributes","!(I)[I",
(void*) android_content_AssetManager_getStyleAttributes },
// Bookkeeping.
diff --git a/core/jni/android_util_XmlBlock.cpp b/core/jni/android_util_XmlBlock.cpp
index 7ae51c89fab7..a15c23cd879a 100644
--- a/core/jni/android_util_XmlBlock.cpp
+++ b/core/jni/android_util_XmlBlock.cpp
@@ -372,37 +372,37 @@ static const JNINativeMethod gXmlBlockMethods[] = {
(void*) android_content_XmlBlock_nativeGetStringBlock },
{ "nativeCreateParseState", "(J)J",
(void*) android_content_XmlBlock_nativeCreateParseState },
- { "nativeNext", "(J)I",
+ { "nativeNext", "!(J)I",
(void*) android_content_XmlBlock_nativeNext },
- { "nativeGetNamespace", "(J)I",
+ { "nativeGetNamespace", "!(J)I",
(void*) android_content_XmlBlock_nativeGetNamespace },
- { "nativeGetName", "(J)I",
+ { "nativeGetName", "!(J)I",
(void*) android_content_XmlBlock_nativeGetName },
- { "nativeGetText", "(J)I",
+ { "nativeGetText", "!(J)I",
(void*) android_content_XmlBlock_nativeGetText },
- { "nativeGetLineNumber", "(J)I",
+ { "nativeGetLineNumber", "!(J)I",
(void*) android_content_XmlBlock_nativeGetLineNumber },
- { "nativeGetAttributeCount", "(J)I",
+ { "nativeGetAttributeCount", "!(J)I",
(void*) android_content_XmlBlock_nativeGetAttributeCount },
- { "nativeGetAttributeNamespace","(JI)I",
+ { "nativeGetAttributeNamespace","!(JI)I",
(void*) android_content_XmlBlock_nativeGetAttributeNamespace },
- { "nativeGetAttributeName", "(JI)I",
+ { "nativeGetAttributeName", "!(JI)I",
(void*) android_content_XmlBlock_nativeGetAttributeName },
- { "nativeGetAttributeResource", "(JI)I",
+ { "nativeGetAttributeResource", "!(JI)I",
(void*) android_content_XmlBlock_nativeGetAttributeResource },
- { "nativeGetAttributeDataType", "(JI)I",
+ { "nativeGetAttributeDataType", "!(JI)I",
(void*) android_content_XmlBlock_nativeGetAttributeDataType },
- { "nativeGetAttributeData", "(JI)I",
+ { "nativeGetAttributeData", "!(JI)I",
(void*) android_content_XmlBlock_nativeGetAttributeData },
- { "nativeGetAttributeStringValue", "(JI)I",
+ { "nativeGetAttributeStringValue", "!(JI)I",
(void*) android_content_XmlBlock_nativeGetAttributeStringValue },
- { "nativeGetAttributeIndex", "(JLjava/lang/String;Ljava/lang/String;)I",
+ { "nativeGetAttributeIndex", "!(JLjava/lang/String;Ljava/lang/String;)I",
(void*) android_content_XmlBlock_nativeGetAttributeIndex },
- { "nativeGetIdAttribute", "(J)I",
+ { "nativeGetIdAttribute", "!(J)I",
(void*) android_content_XmlBlock_nativeGetIdAttribute },
- { "nativeGetClassAttribute", "(J)I",
+ { "nativeGetClassAttribute", "!(J)I",
(void*) android_content_XmlBlock_nativeGetClassAttribute },
- { "nativeGetStyleAttribute", "(J)I",
+ { "nativeGetStyleAttribute", "!(J)I",
(void*) android_content_XmlBlock_nativeGetStyleAttribute },
{ "nativeDestroyParseState", "(J)V",
(void*) android_content_XmlBlock_nativeDestroyParseState },
diff --git a/core/jni/android_view_DisplayListCanvas.cpp b/core/jni/android_view_DisplayListCanvas.cpp
index b64acc32e634..9b41eb3c659b 100644
--- a/core/jni/android_view_DisplayListCanvas.cpp
+++ b/core/jni/android_view_DisplayListCanvas.cpp
@@ -151,10 +151,10 @@ static jboolean android_view_DisplayListCanvas_isAvailable(JNIEnv* env, jobject
// not in the emulator
return JNI_TRUE;
}
- // In the emulator this property will be set to 1 when hardware GLES is
+ // In the emulator this property will be set > 0 when OpenGL ES 2.0 is
// enabled, 0 otherwise. On old emulator versions it will be undefined.
property_get("ro.kernel.qemu.gles", prop, "0");
- return atoi(prop) == 1 ? JNI_TRUE : JNI_FALSE;
+ return atoi(prop) > 0 ? JNI_TRUE : JNI_FALSE;
}
// ----------------------------------------------------------------------------
diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp
index da96b935883e..ff51e4ee1263 100644
--- a/core/jni/android_view_Surface.cpp
+++ b/core/jni/android_view_Surface.cpp
@@ -460,6 +460,10 @@ static jint nativeGetHeight(JNIEnv* env, jclass clazz, jlong nativeObject) {
anw->query(anw, NATIVE_WINDOW_HEIGHT, &value);
return value;
}
+static jlong nativeGetNextFrameNumber(JNIEnv *env, jclass clazz, jlong nativeObject) {
+ Surface* surface = reinterpret_cast<Surface*>(nativeObject);
+ return surface->getNextFrameNumber();
+}
namespace uirenderer {
@@ -536,6 +540,7 @@ static const JNINativeMethod gSurfaceMethods[] = {
(void*)nativeWriteToParcel },
{"nativeGetWidth", "(J)I", (void*)nativeGetWidth },
{"nativeGetHeight", "(J)I", (void*)nativeGetHeight },
+ {"nativeGetNextFrameNumber", "(J)J", (void*)nativeGetNextFrameNumber },
// HWUI context
{"nHwuiCreate", "(JJ)J", (void*) hwui::create },
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 931ad54fb5e8..1dfe40a324b3 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -571,6 +571,21 @@ static jboolean nativeGetAnimationFrameStats(JNIEnv* env, jclass clazz, jobject
return JNI_TRUE;
}
+
+static void nativeDeferTransactionUntil(JNIEnv* env, jclass clazz, jlong nativeObject,
+ jobject handleObject, jlong frameNumber) {
+ auto ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
+ sp<IBinder> handle = ibinderForJavaObject(env, handleObject);
+
+ ctrl->deferTransactionUntil(handle, frameNumber);
+}
+
+static jobject nativeGetHandle(JNIEnv* env, jclass clazz, jlong nativeObject) {
+ auto ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
+
+ return javaObjectForIBinder(env, ctrl->getHandle());
+}
+
// ----------------------------------------------------------------------------
static const JNINativeMethod sSurfaceControlMethods[] = {
@@ -638,6 +653,10 @@ static const JNINativeMethod sSurfaceControlMethods[] = {
(void*)nativeGetAnimationFrameStats },
{"nativeSetDisplayPowerMode", "(Landroid/os/IBinder;I)V",
(void*)nativeSetDisplayPowerMode },
+ {"nativeDeferTransactionUntil", "(JLandroid/os/IBinder;J)V",
+ (void*)nativeDeferTransactionUntil },
+ {"nativeGetHandle", "(J)Landroid/os/IBinder;",
+ (void*)nativeGetHandle }
};
int register_android_view_SurfaceControl(JNIEnv* env)
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 5828829511c1..561bcbc26d0c 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2127,7 +2127,7 @@
<!-- Allows an application to grant specific permissions.
@hide -->
<permission android:name="android.permission.GRANT_RUNTIME_PERMISSIONS"
- android:protectionLevel="signature|installer" />
+ android:protectionLevel="signature|installer|verifier" />
<!-- Allows an app that has this permission and the permissions to install packages
to request certain runtime permissions to be granted at installation.
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 400c8224b28e..76f70629c5ac 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2130,8 +2130,10 @@
string that's stored in 8-bit unpacked format) characters.-->
<bool translatable="false" name="config_sms_decode_gsm_8bit_data">false</bool>
- <!-- Package name providing WebView implementation. -->
- <string name="config_webViewPackageName" translatable="false">com.android.webview</string>
+ <!-- List of package names (ordered by preference) providing WebView implementations. -->
+ <string-array name="config_webViewPackageNames" translatable="false">
+ <item>com.android.webview</item>
+ </string-array>
<!-- If EMS is not supported, framework breaks down EMS into single segment SMS
and adds page info " x/y". This config is used to set which carrier doesn't
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index dd42c2278ba2..bfb0d103d7f2 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -367,11 +367,6 @@
<dimen name="resolver_max_width">480dp</dimen>
- <!-- @deprecated Use config_windowOutsetBottom instead.
- Size of the offset applied to the position of the circular mask. This
- is only used on circular displays. In the case where there is no
- "chin", this will default to 0 -->
- <dimen name="circular_display_mask_offset">0px</dimen>
<!-- Amount to reduce the size of the circular mask by (to compensate for
aliasing effects). This is only used on circular displays. -->
<dimen name="circular_display_mask_thickness">1px</dimen>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index d1932fc9061d..ba28e81e5794 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -445,7 +445,6 @@
<java-symbol type="dimen" name="notification_large_icon_circle_padding" />
<java-symbol type="dimen" name="notification_badge_size" />
<java-symbol type="dimen" name="immersive_mode_cling_width" />
- <java-symbol type="dimen" name="circular_display_mask_offset" />
<java-symbol type="dimen" name="accessibility_magnification_indicator_width" />
<java-symbol type="dimen" name="circular_display_mask_thickness" />
@@ -2019,7 +2018,7 @@
<java-symbol type="attr" name="actionModeWebSearchDrawable" />
<java-symbol type="string" name="websearch" />
<java-symbol type="drawable" name="ic_media_video_poster" />
- <java-symbol type="string" name="config_webViewPackageName" />
+ <java-symbol type="array" name="config_webViewPackageNames" />
<!-- From SubtitleView -->
<java-symbol type="dimen" name="subtitle_corner_radius" />
diff --git a/core/tests/benchmarks/src/android/content/res/ResourcesBenchmark.java b/core/tests/benchmarks/src/android/content/res/ResourcesBenchmark.java
new file mode 100644
index 000000000000..3638473aac03
--- /dev/null
+++ b/core/tests/benchmarks/src/android/content/res/ResourcesBenchmark.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2015 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.res;
+
+import android.util.AttributeSet;
+import android.util.Xml;
+
+import com.android.internal.R;
+import com.google.caliper.SimpleBenchmark;
+
+import org.xmlpull.v1.XmlPullParser;
+
+public class ResourcesBenchmark extends SimpleBenchmark {
+
+ private AssetManager mAsset;
+ private Resources mRes;
+
+ private int mTextId;
+ private int mColorId;
+ private int mIntegerId;
+ private int mLayoutId;
+
+ @Override
+ protected void setUp() {
+ mAsset = new AssetManager();
+ mAsset.addAssetPath("/system/framework/framework-res.apk");
+ mRes = new Resources(mAsset, null, null);
+
+ mTextId = mRes.getIdentifier("cancel", "string", "android");
+ mColorId = mRes.getIdentifier("transparent", "color", "android");
+ mIntegerId = mRes.getIdentifier("config_shortAnimTime", "integer", "android");
+ mLayoutId = mRes.getIdentifier("two_line_list_item", "layout", "android");
+ }
+
+ @Override
+ protected void tearDown() {
+ mAsset.close();
+ }
+
+ public void timeGetString(int reps) {
+ for (int i = 0; i < reps; i++) {
+ mRes.getText(mTextId);
+ }
+ }
+
+ public void timeGetColor(int reps) {
+ for (int i = 0; i < reps; i++) {
+ mRes.getColor(mColorId, null);
+ }
+ }
+
+ public void timeGetInteger(int reps) {
+ for (int i = 0; i < reps; i++) {
+ mRes.getInteger(mIntegerId);
+ }
+ }
+
+ public void timeGetLayoutAndTraverse(int reps) throws Exception {
+ for (int i = 0; i < reps; i++) {
+ final XmlResourceParser parser = mRes.getLayout(mLayoutId);
+ try {
+ while (parser.next() != XmlPullParser.END_DOCUMENT) {
+ // Walk the entire tree
+ }
+ } finally {
+ parser.close();
+ }
+ }
+ }
+}
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index 3c1036846595..6903b7b8a3fc 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -136,7 +136,9 @@
</intent-filter>
</activity>
- <activity android:name="android.widget.TextViewActivity" android:label="TextViewActivity">
+ <activity android:name="android.widget.TextViewActivity"
+ android:label="TextViewActivity"
+ android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
diff --git a/core/tests/coretests/src/android/widget/TextViewActivityTest.java b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
index 6a76a27c0bd3..bb515701ef8e 100644
--- a/core/tests/coretests/src/android/widget/TextViewActivityTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
@@ -36,7 +36,6 @@ import com.android.frameworks.coretests.R;
import android.test.ActivityInstrumentationTestCase2;
import android.test.suitebuilder.annotation.SmallTest;
-import android.util.OrientationUtil;
import android.view.KeyEvent;
/**
@@ -44,16 +43,13 @@ import android.view.KeyEvent;
*/
public class TextViewActivityTest extends ActivityInstrumentationTestCase2<TextViewActivity>{
- private OrientationUtil mOrientationUtil;
-
public TextViewActivityTest() {
super(TextViewActivity.class);
}
@Override
public void setUp() {
- mOrientationUtil = OrientationUtil.initializeAndStartActivityIfNotStarted(this);
- mOrientationUtil.setPortraitOrientation();
+ getActivity();
}
@SmallTest
diff --git a/graphics/java/android/graphics/drawable/Icon.java b/graphics/java/android/graphics/drawable/Icon.java
index 26232a9bc89c..44d7530c6c11 100644
--- a/graphics/java/android/graphics/drawable/Icon.java
+++ b/graphics/java/android/graphics/drawable/Icon.java
@@ -382,7 +382,9 @@ public final class Icon implements Parcelable {
* @hide
*/
public void convertToAshmem() {
- if (mType == TYPE_BITMAP && getBitmap().isMutable()) {
+ if (mType == TYPE_BITMAP &&
+ getBitmap().isMutable() &&
+ getBitmap().getAllocationByteCount() >= (128 * (1 << 10))) {
setBitmap(getBitmap().createAshmemBitmap());
}
}
diff --git a/graphics/java/android/graphics/drawable/LayerDrawable.java b/graphics/java/android/graphics/drawable/LayerDrawable.java
index 340503074447..1ede1052d21b 100644
--- a/graphics/java/android/graphics/drawable/LayerDrawable.java
+++ b/graphics/java/android/graphics/drawable/LayerDrawable.java
@@ -1772,7 +1772,9 @@ public class LayerDrawable extends Drawable implements Drawable.Callback {
final Drawable clone;
if (dr != null) {
final ConstantState cs = dr.getConstantState();
- if (res != null) {
+ if (cs == null) {
+ clone = dr;
+ } else if (res != null) {
clone = cs.newDrawable(res);
} else {
clone = cs.newDrawable();
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index d94c91d64572..ae5fa6c15c01 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -56,6 +56,7 @@ hwui_src_files := \
Layer.cpp \
LayerCache.cpp \
LayerRenderer.cpp \
+ LayerUpdateQueue.cpp \
Matrix.cpp \
OpenGLRenderer.cpp \
Patch.cpp \
@@ -204,6 +205,7 @@ LOCAL_SRC_FILES += \
unit_tests/ClipAreaTests.cpp \
unit_tests/DamageAccumulatorTests.cpp \
unit_tests/FatVectorTests.cpp \
+ unit_tests/LayerUpdateQueueTests.cpp \
unit_tests/LinearAllocatorTests.cpp \
unit_tests/StringUtilsTests.cpp
diff --git a/libs/hwui/BakedOpRenderer.cpp b/libs/hwui/BakedOpRenderer.cpp
index 086885320cce..2fca5ea8b962 100644
--- a/libs/hwui/BakedOpRenderer.cpp
+++ b/libs/hwui/BakedOpRenderer.cpp
@@ -20,6 +20,7 @@
#include "Glop.h"
#include "GlopBuilder.h"
#include "renderstate/RenderState.h"
+#include "utils/FatVector.h"
#include "utils/GLUtils.h"
namespace android {
@@ -29,10 +30,13 @@ namespace uirenderer {
// OffscreenBuffer
////////////////////////////////////////////////////////////////////////////////
-OffscreenBuffer::OffscreenBuffer(Caches& caches, uint32_t textureWidth, uint32_t textureHeight,
+OffscreenBuffer::OffscreenBuffer(RenderState& renderState, Caches& caches,
+ uint32_t textureWidth, uint32_t textureHeight,
uint32_t viewportWidth, uint32_t viewportHeight)
- : texture(caches)
- , texCoords(0, viewportHeight / float(textureHeight), viewportWidth / float(textureWidth), 0) {
+ : renderState(renderState)
+ , viewportWidth(viewportWidth)
+ , viewportHeight(viewportHeight)
+ , texture(caches) {
texture.width = textureWidth;
texture.height = textureHeight;
@@ -48,16 +52,72 @@ OffscreenBuffer::OffscreenBuffer(Caches& caches, uint32_t textureWidth, uint32_t
GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
}
+void OffscreenBuffer::updateMeshFromRegion() {
+ // avoid T-junctions as they cause artifacts in between the resultant
+ // geometry when complex transforms occur.
+ // TODO: generate the safeRegion only if necessary based on drawing transform
+ Region safeRegion = Region::createTJunctionFreeRegion(region);
+
+ size_t count;
+ const android::Rect* rects = safeRegion.getArray(&count);
+
+ const float texX = 1.0f / float(viewportWidth);
+ const float texY = 1.0f / float(viewportHeight);
+
+ FatVector<TextureVertex, 64> meshVector(count * 4); // uses heap if more than 64 vertices needed
+ TextureVertex* mesh = &meshVector[0];
+ for (size_t i = 0; i < count; i++) {
+ const android::Rect* r = &rects[i];
+
+ const float u1 = r->left * texX;
+ const float v1 = (viewportHeight - r->top) * texY;
+ const float u2 = r->right * texX;
+ const float v2 = (viewportHeight - r->bottom) * texY;
+
+ TextureVertex::set(mesh++, r->left, r->top, u1, v1);
+ TextureVertex::set(mesh++, r->right, r->top, u2, v1);
+ TextureVertex::set(mesh++, r->left, r->bottom, u1, v2);
+ TextureVertex::set(mesh++, r->right, r->bottom, u2, v2);
+ }
+ elementCount = count * 6;
+ renderState.meshState().genOrUpdateMeshBuffer(&vbo,
+ sizeof(TextureVertex) * count * 4,
+ &meshVector[0],
+ GL_DYNAMIC_DRAW); // TODO: GL_STATIC_DRAW if savelayer
+}
+
+OffscreenBuffer::~OffscreenBuffer() {
+ texture.deleteTexture();
+ renderState.meshState().deleteMeshBuffer(vbo);
+ elementCount = 0;
+ vbo = 0;
+}
+
////////////////////////////////////////////////////////////////////////////////
// BakedOpRenderer
////////////////////////////////////////////////////////////////////////////////
-OffscreenBuffer* BakedOpRenderer::startLayer(uint32_t width, uint32_t height) {
+OffscreenBuffer* BakedOpRenderer::createOffscreenBuffer(RenderState& renderState,
+ uint32_t width, uint32_t height) {
+ // TODO: get from cache!
+ return new OffscreenBuffer(renderState, Caches::getInstance(), width, height, width, height);
+}
+
+void BakedOpRenderer::destroyOffscreenBuffer(OffscreenBuffer* offscreenBuffer) {
+ // TODO: return texture/offscreenbuffer to cache!
+ delete offscreenBuffer;
+}
+
+OffscreenBuffer* BakedOpRenderer::createLayer(uint32_t width, uint32_t height) {
LOG_ALWAYS_FATAL_IF(mRenderTarget.offscreenBuffer, "already has layer...");
- // TODO: really should be caching these!
- OffscreenBuffer* buffer = new OffscreenBuffer(mCaches, width, height, width, height);
- mRenderTarget.offscreenBuffer = buffer;
+ OffscreenBuffer* buffer = createOffscreenBuffer(mRenderState, width, height);
+ startLayer(buffer);
+ return buffer;
+}
+
+void BakedOpRenderer::startLayer(OffscreenBuffer* offscreenBuffer) {
+ mRenderTarget.offscreenBuffer = offscreenBuffer;
// create and bind framebuffer
mRenderTarget.frameBufferId = mRenderState.genFramebuffer();
@@ -65,7 +125,7 @@ OffscreenBuffer* BakedOpRenderer::startLayer(uint32_t width, uint32_t height) {
// attach the texture to the FBO
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
- buffer->texture.id, 0);
+ offscreenBuffer->texture.id, 0);
LOG_ALWAYS_FATAL_IF(GLUtils::dumpGLErrors(), "startLayer FAILED");
LOG_ALWAYS_FATAL_IF(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE,
"framebuffer incomplete!");
@@ -75,11 +135,11 @@ OffscreenBuffer* BakedOpRenderer::startLayer(uint32_t width, uint32_t height) {
glClear(GL_COLOR_BUFFER_BIT);
// Change the viewport & ortho projection
- setViewport(width, height);
- return buffer;
+ setViewport(offscreenBuffer->viewportWidth, offscreenBuffer->viewportHeight);
}
void BakedOpRenderer::endLayer() {
+ mRenderTarget.offscreenBuffer->updateMeshFromRegion();
mRenderTarget.offscreenBuffer = nullptr;
// Detach the texture from the FBO
@@ -144,6 +204,12 @@ void BakedOpRenderer::renderGlop(const BakedOpState& state, const Glop& glop) {
mRenderState.scissor().set(clip.left, mRenderTarget.viewportHeight - clip.bottom,
clip.getWidth(), clip.getHeight());
}
+ if (mRenderTarget.offscreenBuffer) { // TODO: not with multi-draw
+ // register layer damage to draw-back region
+ const Rect& uiDirty = state.computedState.clippedBounds;
+ android::Rect dirty(uiDirty.left, uiDirty.top, uiDirty.right, uiDirty.bottom);
+ mRenderTarget.offscreenBuffer->region.orSelf(dirty);
+ }
mRenderState.render(glop, mRenderTarget.orthoMatrix);
mHasDrawn = true;
}
@@ -156,6 +222,14 @@ void BakedOpDispatcher::onRenderNodeOp(BakedOpRenderer&, const RenderNodeOp&, co
LOG_ALWAYS_FATAL("unsupported operation");
}
+void BakedOpDispatcher::onBeginLayerOp(BakedOpRenderer& renderer, const BeginLayerOp& op, const BakedOpState& state) {
+ LOG_ALWAYS_FATAL("unsupported operation");
+}
+
+void BakedOpDispatcher::onEndLayerOp(BakedOpRenderer& renderer, const EndLayerOp& op, const BakedOpState& state) {
+ LOG_ALWAYS_FATAL("unsupported operation");
+}
+
void BakedOpDispatcher::onBitmapOp(BakedOpRenderer& renderer, const BitmapOp& op, const BakedOpState& state) {
renderer.caches().textureState().activateTexture(0); // TODO: should this be automatic, and/or elsewhere?
Texture* texture = renderer.getTexture(op.bitmap);
@@ -199,36 +273,26 @@ void BakedOpDispatcher::onSimpleRectsOp(BakedOpRenderer& renderer, const SimpleR
renderer.renderGlop(state, glop);
}
-void BakedOpDispatcher::onBeginLayerOp(BakedOpRenderer& renderer, const BeginLayerOp& op, const BakedOpState& state) {
- LOG_ALWAYS_FATAL("unsupported operation");
-}
-
-void BakedOpDispatcher::onEndLayerOp(BakedOpRenderer& renderer, const EndLayerOp& op, const BakedOpState& state) {
- LOG_ALWAYS_FATAL("unsupported operation");
-}
-
void BakedOpDispatcher::onLayerOp(BakedOpRenderer& renderer, const LayerOp& op, const BakedOpState& state) {
OffscreenBuffer* buffer = *op.layerHandle;
// TODO: extend this to handle HW layers & paint properties which
// reside in node.properties().layerProperties()
- float layerAlpha = (op.paint->getAlpha() / 255.0f) * state.alpha;
- const bool tryToSnap = state.computedState.transform.isPureTranslate();
+ float layerAlpha = op.alpha * state.alpha;
Glop glop;
GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
.setRoundRectClipState(state.roundRectClipState)
- .setMeshTexturedUvQuad(nullptr, buffer->texCoords)
- .setFillLayer(buffer->texture, op.paint->getColorFilter(), layerAlpha, PaintUtils::getXfermodeDirect(op.paint), Blend::ModeOrderSwap::NoSwap)
+ .setMeshTexturedIndexedVbo(buffer->vbo, buffer->elementCount)
+ .setFillLayer(buffer->texture, op.colorFilter, layerAlpha, op.mode, Blend::ModeOrderSwap::NoSwap)
.setTransform(state.computedState.transform, TransformFlags::None)
- .setModelViewMapUnitToRectOptionalSnap(tryToSnap, op.unmappedBounds)
+ .setModelViewOffsetRectSnap(op.unmappedBounds.left, op.unmappedBounds.top,
+ Rect(op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight()))
.build();
renderer.renderGlop(state, glop);
- // destroy and delete, since each clipped saveLayer is only drawn once.
- buffer->texture.deleteTexture();
-
- // TODO: return texture/offscreenbuffer to cache!
- delete buffer;
+ if (op.destroy) {
+ BakedOpRenderer::destroyOffscreenBuffer(buffer);
+ }
}
} // namespace uirenderer
diff --git a/libs/hwui/BakedOpRenderer.h b/libs/hwui/BakedOpRenderer.h
index 16afad44657e..aa1e67d45d56 100644
--- a/libs/hwui/BakedOpRenderer.h
+++ b/libs/hwui/BakedOpRenderer.h
@@ -35,12 +35,24 @@ class RenderState;
*/
class OffscreenBuffer {
public:
- OffscreenBuffer(Caches& caches, uint32_t textureWidth, uint32_t textureHeight,
+ OffscreenBuffer(RenderState& renderState, Caches& caches,
+ uint32_t textureWidth, uint32_t textureHeight,
uint32_t viewportWidth, uint32_t viewportHeight);
+ ~OffscreenBuffer();
+ // must be called prior to rendering, to construct/update vertex buffer
+ void updateMeshFromRegion();
+
+ RenderState& renderState;
+ uint32_t viewportWidth;
+ uint32_t viewportHeight;
Texture texture;
- Rect texCoords;
+
+ // Portion of offscreen buffer that has been drawn to. Used to minimize drawing area when
+ // drawing back to screen / parent FBO.
Region region;
+ GLsizei elementCount = 0;
+ GLuint vbo = 0;
};
/**
@@ -60,12 +72,17 @@ public:
, mOpaque(opaque) {
}
+ static OffscreenBuffer* createOffscreenBuffer(RenderState& renderState,
+ uint32_t width, uint32_t height);
+ static void destroyOffscreenBuffer(OffscreenBuffer*);
+
RenderState& renderState() { return mRenderState; }
Caches& caches() { return mCaches; }
void startFrame(uint32_t width, uint32_t height);
void endFrame();
- OffscreenBuffer* startLayer(uint32_t width, uint32_t height);
+ OffscreenBuffer* createLayer(uint32_t width, uint32_t height);
+ void startLayer(OffscreenBuffer* offscreenBuffer);
void endLayer();
Texture* getTexture(const SkBitmap* bitmap);
diff --git a/libs/hwui/Caches.cpp b/libs/hwui/Caches.cpp
index 7c63e316ba38..94a11f131229 100644
--- a/libs/hwui/Caches.cpp
+++ b/libs/hwui/Caches.cpp
@@ -23,6 +23,7 @@
#include "ShadowTessellator.h"
#include "utils/GLUtils.h"
+#include <cutils/properties.h>
#include <utils/Log.h>
#include <utils/String8.h>
diff --git a/libs/hwui/Caches.h b/libs/hwui/Caches.h
index 61e958d42148..330dc2951ec9 100644
--- a/libs/hwui/Caches.h
+++ b/libs/hwui/Caches.h
@@ -43,7 +43,6 @@
#include <GLES3/gl3.h>
#include <utils/KeyedVector.h>
-#include <utils/Singleton.h>
#include <cutils/compiler.h>
diff --git a/libs/hwui/DisplayList.h b/libs/hwui/DisplayList.h
index 86796c5a5e0c..00c4e2d47e4c 100644
--- a/libs/hwui/DisplayList.h
+++ b/libs/hwui/DisplayList.h
@@ -154,7 +154,11 @@ public:
return allocator.usedSize();
}
bool isEmpty() {
+#if HWUI_NEW_OPS
+ return ops.empty();
+#else
return !hasDrawOps;
+#endif
}
private:
@@ -179,7 +183,7 @@ private:
// List of functors
LsaVector<Functor*> functors;
- bool hasDrawOps;
+ bool hasDrawOps; // only used if !HWUI_NEW_OPS
void cleanupResources();
};
diff --git a/libs/hwui/Extensions.cpp b/libs/hwui/Extensions.cpp
index 06c8a21b019b..6dd29ad8c703 100644
--- a/libs/hwui/Extensions.cpp
+++ b/libs/hwui/Extensions.cpp
@@ -20,6 +20,7 @@
#include "Properties.h"
#include "utils/StringUtils.h"
+#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#include <utils/Log.h>
diff --git a/libs/hwui/Extensions.h b/libs/hwui/Extensions.h
index 0a30d162f2e8..6689b88f17e3 100644
--- a/libs/hwui/Extensions.h
+++ b/libs/hwui/Extensions.h
@@ -19,12 +19,6 @@
#include <cutils/compiler.h>
-#include <utils/Singleton.h>
-#include <utils/SortedVector.h>
-#include <utils/String8.h>
-
-#include <GLES2/gl2.h>
-
namespace android {
namespace uirenderer {
diff --git a/libs/hwui/GlopBuilder.cpp b/libs/hwui/GlopBuilder.cpp
index d2da8513ff56..f3ac93b89893 100644
--- a/libs/hwui/GlopBuilder.cpp
+++ b/libs/hwui/GlopBuilder.cpp
@@ -70,6 +70,20 @@ GlopBuilder::GlopBuilder(RenderState& renderState, Caches& caches, Glop* outGlop
// Mesh
////////////////////////////////////////////////////////////////////////////////
+GlopBuilder& GlopBuilder::setMeshTexturedIndexedVbo(GLuint vbo, GLsizei elementCount) {
+ TRIGGER_STAGE(kMeshStage);
+
+ mOutGlop->mesh.primitiveMode = GL_TRIANGLES;
+ mOutGlop->mesh.indices = { mRenderState.meshState().getQuadListIBO(), nullptr };
+ mOutGlop->mesh.vertices = {
+ vbo,
+ VertexAttribFlags::TextureCoord,
+ nullptr, nullptr, nullptr,
+ kTextureVertexStride };
+ mOutGlop->mesh.elementCount = elementCount;
+ return *this;
+}
+
GlopBuilder& GlopBuilder::setMeshUnitQuad() {
TRIGGER_STAGE(kMeshStage);
diff --git a/libs/hwui/GlopBuilder.h b/libs/hwui/GlopBuilder.h
index 6f5802eedefd..6270dcbe7a84 100644
--- a/libs/hwui/GlopBuilder.h
+++ b/libs/hwui/GlopBuilder.h
@@ -47,6 +47,7 @@ class GlopBuilder {
public:
GlopBuilder(RenderState& renderState, Caches& caches, Glop* outGlop);
+ GlopBuilder& setMeshTexturedIndexedVbo(GLuint vbo, GLsizei elementCount);
GlopBuilder& setMeshUnitQuad();
GlopBuilder& setMeshTexturedUnitQuad(const UvMapper* uvMapper);
GlopBuilder& setMeshTexturedUvQuad(const UvMapper* uvMapper, const Rect uvs);
diff --git a/libs/hwui/GradientCache.cpp b/libs/hwui/GradientCache.cpp
index aa105f9fec0a..8c4645092c97 100644
--- a/libs/hwui/GradientCache.cpp
+++ b/libs/hwui/GradientCache.cpp
@@ -21,6 +21,8 @@
#include "GradientCache.h"
#include "Properties.h"
+#include <cutils/properties.h>
+
namespace android {
namespace uirenderer {
diff --git a/libs/hwui/LayerUpdateQueue.cpp b/libs/hwui/LayerUpdateQueue.cpp
new file mode 100644
index 000000000000..db5f676d09dc
--- /dev/null
+++ b/libs/hwui/LayerUpdateQueue.cpp
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "LayerUpdateQueue.h"
+
+#include "RenderNode.h"
+
+namespace android {
+namespace uirenderer {
+
+void LayerUpdateQueue::clear() {
+ mEntries.clear();
+}
+
+void LayerUpdateQueue::enqueueLayerWithDamage(RenderNode* renderNode, Rect damage) {
+ damage.doIntersect(0, 0, renderNode->getWidth(), renderNode->getHeight());
+ if (!damage.isEmpty()) {
+ for (Entry& entry : mEntries) {
+ if (CC_UNLIKELY(entry.renderNode == renderNode)) {
+ entry.damage.unionWith(damage);
+ return;
+ }
+ }
+ mEntries.emplace_back(renderNode, damage);
+ }
+}
+
+} // namespace uirenderer
+} // namespace android
diff --git a/libs/hwui/LayerUpdateQueue.h b/libs/hwui/LayerUpdateQueue.h
new file mode 100644
index 000000000000..be612d2a15e7
--- /dev/null
+++ b/libs/hwui/LayerUpdateQueue.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+#ifndef ANDROID_HWUI_LAYER_UPDATE_QUEUE_H
+#define ANDROID_HWUI_LAYER_UPDATE_QUEUE_H
+
+#include "Rect.h"
+#include "utils/Macros.h"
+
+#include <vector>
+#include <unordered_map>
+
+namespace android {
+namespace uirenderer {
+
+class RenderNode;
+
+class LayerUpdateQueue {
+ PREVENT_COPY_AND_ASSIGN(LayerUpdateQueue);
+public:
+ struct Entry {
+ Entry(RenderNode* renderNode, const Rect& damage)
+ : renderNode(renderNode)
+ , damage(damage) {}
+ RenderNode* renderNode;
+ Rect damage;
+ };
+
+ LayerUpdateQueue() {}
+ void enqueueLayerWithDamage(RenderNode* renderNode, Rect dirty);
+ void clear();
+ const std::vector<Entry> entries() const { return mEntries; }
+private:
+ std::vector<Entry> mEntries;
+};
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_HWUI_LAYER_UPDATE_QUEUE_H
diff --git a/libs/hwui/OpReorderer.cpp b/libs/hwui/OpReorderer.cpp
index ddeb33624798..163f7cc4b1d0 100644
--- a/libs/hwui/OpReorderer.cpp
+++ b/libs/hwui/OpReorderer.cpp
@@ -18,6 +18,7 @@
#include "utils/PaintUtils.h"
#include "RenderNode.h"
+#include "LayerUpdateQueue.h"
#include "SkCanvas.h"
#include "utils/Trace.h"
@@ -202,6 +203,14 @@ private:
Rect mClipRect;
};
+OpReorderer::LayerReorderer::LayerReorderer(uint32_t width, uint32_t height,
+ const BeginLayerOp* beginLayerOp, RenderNode* renderNode)
+ : width(width)
+ , height(height)
+ , offscreenBuffer(renderNode ? renderNode->getLayer() : nullptr)
+ , beginLayerOp(beginLayerOp)
+ , renderNode(renderNode) {}
+
// iterate back toward target to see if anything drawn since should overlap the new op
// if no target, merging ops still iterate to find similar batch to insert after
void OpReorderer::LayerReorderer::locateInsertIndex(int batchId, const Rect& clippedBounds,
@@ -288,33 +297,48 @@ void OpReorderer::LayerReorderer::replayBakedOpsImpl(void* arg, BakedOpDispatche
}
void OpReorderer::LayerReorderer::dump() const {
+ ALOGD("LayerReorderer %p, %ux%u buffer %p, blo %p, rn %p",
+ this, width, height, offscreenBuffer, beginLayerOp, renderNode);
for (const BatchBase* batch : mBatches) {
batch->dump();
}
}
-OpReorderer::OpReorderer(const SkRect& clip, uint32_t viewportWidth, uint32_t viewportHeight,
+OpReorderer::OpReorderer(const LayerUpdateQueue& layers, const SkRect& clip,
+ uint32_t viewportWidth, uint32_t viewportHeight,
const std::vector< sp<RenderNode> >& nodes)
: mCanvasState(*this) {
ATRACE_NAME("prepare drawing commands");
-
mLayerReorderers.emplace_back(viewportWidth, viewportHeight);
- mLayerStack.push_back(0);
+ mLayerStack.push_back(0);
mCanvasState.initializeSaveStack(viewportWidth, viewportHeight,
clip.fLeft, clip.fTop, clip.fRight, clip.fBottom,
Vector3());
+
+ // Render all layers to be updated, in order. Defer in reverse order, so that they'll be
+ // updated in the order they're passed in (mLayerReorderers are issued to Renderer in reverse)
+ for (int i = layers.entries().size() - 1; i >= 0; i--) {
+ RenderNode* layerNode = layers.entries()[i].renderNode;
+ const Rect& layerDamage = layers.entries()[i].damage;
+
+ saveForLayer(layerNode->getWidth(), layerNode->getHeight(), nullptr, layerNode);
+ mCanvasState.writableSnapshot()->setClip(
+ layerDamage.left, layerDamage.top, layerDamage.right, layerDamage.bottom);
+
+ if (layerNode->getDisplayList()) {
+ deferImpl(*(layerNode->getDisplayList()));
+ }
+ restoreForLayer();
+ }
+
+ // Defer Fbo0
for (const sp<RenderNode>& node : nodes) {
if (node->nothingToDraw()) continue;
- // TODO: dedupe this code with onRenderNode()
- mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
- if (node->applyViewProperties(mCanvasState)) {
- // not rejected do ops...
- const DisplayList& displayList = node->getDisplayList();
- deferImpl(displayList);
- }
- mCanvasState.restore();
+ int count = mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
+ deferNodePropsAndOps(*node);
+ mCanvasState.restoreToCount(count);
}
}
@@ -334,6 +358,23 @@ void OpReorderer::onViewportInitialized() {}
void OpReorderer::onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) {}
+void OpReorderer::deferNodePropsAndOps(RenderNode& node) {
+ if (node.applyViewProperties(mCanvasState)) {
+ // not rejected so render
+ if (node.getLayer()) {
+ // HW layer
+ LayerOp* drawLayerOp = new (mAllocator) LayerOp(node);
+ BakedOpState* bakedOpState = tryBakeOpState(*drawLayerOp);
+ if (bakedOpState) {
+ // Layer will be drawn into parent layer (which is now current, since we popped mLayerStack)
+ currentLayer().deferUnmergeableOp(mAllocator, bakedOpState, OpBatchType::Bitmap);
+ }
+ } else {
+ deferImpl(*(node.getDisplayList()));
+ }
+ }
+}
+
/**
* Used to define a list of lambdas referencing private OpReorderer::onXXXXOp() methods.
*
@@ -365,11 +406,9 @@ void OpReorderer::onRenderNodeOp(const RenderNodeOp& op) {
mCanvasState.clipRect(op.localClipRect.left, op.localClipRect.top,
op.localClipRect.right, op.localClipRect.bottom, SkRegion::kIntersect_Op);
- // apply RenderProperties state
- if (op.renderNode->applyViewProperties(mCanvasState)) {
- // if node not rejected based on properties, do ops...
- deferImpl(op.renderNode->getDisplayList());
- }
+ // then apply state from node properties, and defer ops
+ deferNodePropsAndOps(*op.renderNode);
+
mCanvasState.restoreToCount(count);
}
@@ -400,10 +439,8 @@ void OpReorderer::onSimpleRectsOp(const SimpleRectsOp& op) {
currentLayer().deferUnmergeableOp(mAllocator, bakedStateOp, OpBatchType::Vertices);
}
-// TODO: test rejection at defer time, where the bounds become empty
-void OpReorderer::onBeginLayerOp(const BeginLayerOp& op) {
- const uint32_t layerWidth = (uint32_t) op.unmappedBounds.getWidth();
- const uint32_t layerHeight = (uint32_t) op.unmappedBounds.getHeight();
+void OpReorderer::saveForLayer(uint32_t layerWidth, uint32_t layerHeight,
+ const BeginLayerOp* beginLayerOp, RenderNode* renderNode) {
mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
mCanvasState.writableSnapshot()->transform->loadIdentity();
@@ -412,18 +449,27 @@ void OpReorderer::onBeginLayerOp(const BeginLayerOp& op) {
// create a new layer, and push its index on the stack
mLayerStack.push_back(mLayerReorderers.size());
- mLayerReorderers.emplace_back(layerWidth, layerHeight);
- mLayerReorderers.back().beginLayerOp = &op;
+ mLayerReorderers.emplace_back(layerWidth, layerHeight, beginLayerOp, renderNode);
}
-void OpReorderer::onEndLayerOp(const EndLayerOp& /* ignored */) {
+void OpReorderer::restoreForLayer() {
+ // restore canvas, and pop finished layer off of the stack
mCanvasState.restore();
+ mLayerStack.pop_back();
+}
- const BeginLayerOp& beginLayerOp = *currentLayer().beginLayerOp;
+// TODO: test rejection at defer time, where the bounds become empty
+void OpReorderer::onBeginLayerOp(const BeginLayerOp& op) {
+ const uint32_t layerWidth = (uint32_t) op.unmappedBounds.getWidth();
+ const uint32_t layerHeight = (uint32_t) op.unmappedBounds.getHeight();
+ saveForLayer(layerWidth, layerHeight, &op, nullptr);
+}
- // pop finished layer off of the stack
+void OpReorderer::onEndLayerOp(const EndLayerOp& /* ignored */) {
+ const BeginLayerOp& beginLayerOp = *currentLayer().beginLayerOp;
int finishedLayerIndex = mLayerStack.back();
- mLayerStack.pop_back();
+
+ restoreForLayer();
// record the draw operation into the previous layer's list of draw commands
// uses state from the associated beginLayerOp, since it has all the state needed for drawing
diff --git a/libs/hwui/OpReorderer.h b/libs/hwui/OpReorderer.h
index 927ecfae3b9f..77be40292205 100644
--- a/libs/hwui/OpReorderer.h
+++ b/libs/hwui/OpReorderer.h
@@ -32,6 +32,7 @@ namespace uirenderer {
class BakedOpState;
class BatchBase;
+class LayerUpdateQueue;
class MergingOpBatch;
class OffscreenBuffer;
class OpBatch;
@@ -64,9 +65,14 @@ class OpReorderer : public CanvasStateClient {
*/
class LayerReorderer {
public:
+ // Create LayerReorderer for Fbo0
LayerReorderer(uint32_t width, uint32_t height)
- : width(width)
- , height(height) {}
+ : LayerReorderer(width, height, nullptr, nullptr) {};
+
+ // Create LayerReorderer for an offscreen layer, where beginLayerOp is present for a
+ // saveLayer, renderNode is present for a HW layer.
+ LayerReorderer(uint32_t width, uint32_t height,
+ const BeginLayerOp* beginLayerOp, RenderNode* renderNode);
// iterate back toward target to see if anything drawn since should overlap the new op
// if no target, merging ops still iterate to find similar batch to insert after
@@ -92,12 +98,12 @@ class OpReorderer : public CanvasStateClient {
void dump() const;
- OffscreenBuffer* offscreenBuffer = nullptr;
- const BeginLayerOp* beginLayerOp = nullptr;
const uint32_t width;
const uint32_t height;
+ OffscreenBuffer* offscreenBuffer;
+ const BeginLayerOp* beginLayerOp;
+ const RenderNode* renderNode;
private:
-
std::vector<BatchBase*> mBatches;
/**
@@ -112,8 +118,8 @@ class OpReorderer : public CanvasStateClient {
};
public:
- // TODO: not final, just presented this way for simplicity. Layers too?
- OpReorderer(const SkRect& clip, uint32_t viewportWidth, uint32_t viewportHeight,
+ OpReorderer(const LayerUpdateQueue& layers, const SkRect& clip,
+ uint32_t viewportWidth, uint32_t viewportHeight,
const std::vector< sp<RenderNode> >& nodes);
OpReorderer(int viewportWidth, int viewportHeight, const DisplayList& displayList);
@@ -144,8 +150,13 @@ public:
// later in the list will be drawn by earlier ones
for (int i = mLayerReorderers.size() - 1; i >= 1; i--) {
LayerReorderer& layer = mLayerReorderers[i];
- if (!layer.empty()) {
- layer.offscreenBuffer = renderer.startLayer(layer.width, layer.height);
+ if (layer.renderNode) {
+ // cached HW layer - can't skip layer if empty
+ renderer.startLayer(layer.offscreenBuffer);
+ layer.replayBakedOpsImpl((void*)&renderer, receivers);
+ renderer.endLayer();
+ } else if (!layer.empty()) { // save layer - skip entire layer if empty
+ layer.offscreenBuffer = renderer.createLayer(layer.width, layer.height);
layer.replayBakedOpsImpl((void*)&renderer, receivers);
renderer.endLayer();
}
@@ -171,12 +182,19 @@ public:
virtual GLuint getTargetFbo() const override { return 0; }
private:
+ void saveForLayer(uint32_t layerWidth, uint32_t layerHeight,
+ const BeginLayerOp* beginLayerOp, RenderNode* renderNode);
+ void restoreForLayer();
+
LayerReorderer& currentLayer() { return mLayerReorderers[mLayerStack.back()]; }
BakedOpState* tryBakeOpState(const RecordedOp& recordedOp) {
return BakedOpState::tryConstruct(mAllocator, *mCanvasState.currentSnapshot(), recordedOp);
}
+ // should always be surrounded by a save/restore pair
+ void deferNodePropsAndOps(RenderNode& node);
+
void deferImpl(const DisplayList& displayList);
void replayBakedOpsImpl(void* arg, BakedOpDispatcher* receivers);
diff --git a/libs/hwui/PathCache.cpp b/libs/hwui/PathCache.cpp
index 4031f2e13f39..fd9ab1847f0d 100644
--- a/libs/hwui/PathCache.cpp
+++ b/libs/hwui/PathCache.cpp
@@ -30,6 +30,8 @@
#include "thread/Signal.h"
#include "thread/TaskProcessor.h"
+#include <cutils/properties.h>
+
namespace android {
namespace uirenderer {
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index c0c61db0f5d1..e81818679f3e 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -17,8 +17,12 @@
#include "Debug.h"
-#include <algorithm>
+#include <cutils/compiler.h>
#include <cutils/log.h>
+#include <cutils/properties.h>
+
+#include <algorithm>
+#include <cstdlib>
namespace android {
namespace uirenderer {
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index 74cd74bde176..1293c786a0bd 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -18,8 +18,6 @@
#define ANDROID_HWUI_PROPERTIES_H
#include <cutils/properties.h>
-#include <stdlib.h>
-#include <utils/Singleton.h>
/**
* This file contains the list of system properties used to configure
diff --git a/libs/hwui/RecordedOp.h b/libs/hwui/RecordedOp.h
index 7874d85f249c..9ae868a9858c 100644
--- a/libs/hwui/RecordedOp.h
+++ b/libs/hwui/RecordedOp.h
@@ -20,6 +20,7 @@
#include "utils/LinearAllocator.h"
#include "Rect.h"
#include "Matrix.h"
+#include "RenderNode.h"
#include "SkXfermode.h"
@@ -136,13 +137,42 @@ struct EndLayerOp : RecordedOp {
: RecordedOp(RecordedOpId::EndLayerOp, Rect(0, 0), Matrix4::identity(), Rect(0, 0), nullptr) {}
};
+/**
+ * Draws an OffscreenBuffer.
+ *
+ * Alpha, mode, and colorfilter are embedded, since LayerOps are always dynamically generated,
+ * when creating/tracking a SkPaint* during defer isn't worth the bother.
+ */
struct LayerOp : RecordedOp {
+ // Records a one-use (saveLayer) layer for drawing. Once drawn, the layer will be destroyed.
LayerOp(BASE_PARAMS, OffscreenBuffer** layerHandle)
- : SUPER(LayerOp)
- , layerHandle(layerHandle) {}
+ : SUPER_PAINTLESS(LayerOp)
+ , layerHandle(layerHandle)
+ , alpha(paint->getAlpha() / 255.0f)
+ , mode(PaintUtils::getXfermodeDirect(paint))
+ , colorFilter(paint->getColorFilter())
+ , destroy(true) {}
+
+ LayerOp(RenderNode& node)
+ : RecordedOp(RecordedOpId::LayerOp, Rect(node.getWidth(), node.getHeight()), Matrix4::identity(), Rect(node.getWidth(), node.getHeight()), nullptr)
+ , layerHandle(node.getLayerHandle())
+ , alpha(node.properties().layerProperties().alpha() / 255.0f)
+ , mode(node.properties().layerProperties().xferMode())
+ , colorFilter(node.properties().layerProperties().colorFilter())
+ , destroy(false) {}
+
// Records a handle to the Layer object, since the Layer itself won't be
// constructed until after this operation is constructed.
OffscreenBuffer** layerHandle;
+ const float alpha;
+ const SkXfermode::Mode mode;
+
+ // pointer to object owned by either LayerProperties, or a recorded Paint object in a
+ // BeginLayerOp. Lives longer than LayerOp in either case, so no skia ref counting is used.
+ SkColorFilter* colorFilter;
+
+ // whether to destroy the layer, once rendered
+ const bool destroy;
};
}; // namespace uirenderer
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index 273af3ac5997..7c460b1bbc1a 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -77,7 +77,6 @@ SkCanvas* RecordingCanvas::asSkCanvas() {
// ----------------------------------------------------------------------------
void RecordingCanvas::onViewportInitialized() {
-
}
void RecordingCanvas::onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) {
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index 454ee24cfc64..8a564758e7b1 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -26,9 +26,10 @@
#include "SkiaCanvasProxy.h"
#include "Snapshot.h"
-#include "SkDrawFilter.h"
-#include "SkPaint.h"
-#include "SkTLazy.h"
+#include <SkDrawFilter.h>
+#include <SkPaint.h>
+#include <SkTLazy.h>
+
#include <vector>
namespace android {
diff --git a/libs/hwui/RenderBufferCache.cpp b/libs/hwui/RenderBufferCache.cpp
index 8beed2540e1c..11d7a6af3a6a 100644
--- a/libs/hwui/RenderBufferCache.cpp
+++ b/libs/hwui/RenderBufferCache.cpp
@@ -14,12 +14,14 @@
* limitations under the License.
*/
-#include <utils/Log.h>
-
#include "Debug.h"
#include "Properties.h"
#include "RenderBufferCache.h"
+#include <utils/Log.h>
+
+#include <cstdlib>
+
namespace android {
namespace uirenderer {
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index 39cb8e9229b1..0601944905a8 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -20,6 +20,7 @@
#include "Debug.h"
#if HWUI_NEW_OPS
#include "RecordedOp.h"
+#include "BakedOpRenderer.h"
#endif
#include "DisplayListOp.h"
#include "LayerRenderer.h"
@@ -42,11 +43,15 @@ namespace android {
namespace uirenderer {
void RenderNode::debugDumpLayers(const char* prefix) {
+#if HWUI_NEW_OPS
+ LOG_ALWAYS_FATAL("TODO: dump layer");
+#else
if (mLayer) {
ALOGD("%sNode %p (%s) has layer %p (fbo = %u, wasBuildLayered = %s)",
prefix, this, getName(), mLayer, mLayer->getFbo(),
mLayer->wasBuildLayered ? "true" : "false");
}
+#endif
if (mDisplayList) {
for (auto&& child : mDisplayList->getChildren()) {
child->renderNode->debugDumpLayers(prefix);
@@ -60,18 +65,21 @@ RenderNode::RenderNode()
, mDisplayList(nullptr)
, mStagingDisplayList(nullptr)
, mAnimatorManager(*this)
- , mLayer(nullptr)
, mParentCount(0) {
}
RenderNode::~RenderNode() {
deleteDisplayList();
delete mStagingDisplayList;
+#if HWUI_NEW_OPS
+ LOG_ALWAYS_FATAL_IF(mLayer, "layer missed detachment!");
+#else
if (mLayer) {
ALOGW("Memory Warning: Layer %p missed its detachment, held on to for far too long!", mLayer);
mLayer->postDecStrong();
mLayer = nullptr;
}
+#endif
}
void RenderNode::setStagingDisplayList(DisplayList* displayList) {
@@ -240,13 +248,29 @@ void RenderNode::prepareLayer(TreeInfo& info, uint32_t dirtyMask) {
}
}
+layer_t* createLayer(RenderState& renderState, uint32_t width, uint32_t height) {
+#if HWUI_NEW_OPS
+ return BakedOpRenderer::createOffscreenBuffer(renderState, width, height);
+#else
+ return LayerRenderer::createRenderLayer(renderState, width, height);
+#endif
+}
+
+void destroyLayer(layer_t* layer) {
+#if HWUI_NEW_OPS
+ BakedOpRenderer::destroyOffscreenBuffer(layer);
+#else
+ LayerRenderer::destroyLayer(layer);
+#endif
+}
+
void RenderNode::pushLayerUpdate(TreeInfo& info) {
LayerType layerType = properties().effectiveLayerType();
// If we are not a layer OR we cannot be rendered (eg, view was detached)
// we need to destroy any Layers we may have had previously
if (CC_LIKELY(layerType != LayerType::RenderLayer) || CC_UNLIKELY(!isRenderable())) {
if (CC_UNLIKELY(mLayer)) {
- LayerRenderer::destroyLayer(mLayer);
+ destroyLayer(mLayer);
mLayer = nullptr;
}
return;
@@ -254,14 +278,18 @@ void RenderNode::pushLayerUpdate(TreeInfo& info) {
bool transformUpdateNeeded = false;
if (!mLayer) {
- mLayer = LayerRenderer::createRenderLayer(
- info.canvasContext.getRenderState(), getWidth(), getHeight());
- applyLayerPropertiesToLayer(info);
- damageSelf(info);
- transformUpdateNeeded = true;
+ mLayer = createLayer(info.canvasContext.getRenderState(), getWidth(), getHeight());
+ damageSelf(info);
+ transformUpdateNeeded = true;
+#if HWUI_NEW_OPS
+ } else if (mLayer->viewportWidth != getWidth() || mLayer->viewportHeight != getHeight()) {
+ // TODO: allow it to grow larger
+ if (getWidth() > mLayer->texture.width || getHeight() > mLayer->texture.height) {
+#else
} else if (mLayer->layer.getWidth() != getWidth() || mLayer->layer.getHeight() != getHeight()) {
if (!LayerRenderer::resizeLayer(mLayer, getWidth(), getHeight())) {
- LayerRenderer::destroyLayer(mLayer);
+#endif
+ destroyLayer(mLayer);
mLayer = nullptr;
}
damageSelf(info);
@@ -276,7 +304,7 @@ void RenderNode::pushLayerUpdate(TreeInfo& info) {
if (info.errorHandler) {
std::ostringstream err;
err << "Unable to create layer for " << getName();
- const int maxTextureSize = Caches::getInstance().maxTextureSize;
+ const uint32_t maxTextureSize = Caches::getInstance().maxTextureSize;
if (getWidth() > maxTextureSize || getHeight() > maxTextureSize) {
err << ", size " << getWidth() << "x" << getHeight()
<< " exceeds max size " << maxTextureSize;
@@ -292,9 +320,16 @@ void RenderNode::pushLayerUpdate(TreeInfo& info) {
// update the transform in window of the layer to reset its origin wrt light source position
Matrix4 windowTransform;
info.damageAccumulator->computeCurrentTransform(&windowTransform);
+#if HWUI_NEW_OPS
+ // TODO: update layer transform (perhaps as part of enqueueLayerWithDamage)
+#else
mLayer->setWindowTransform(windowTransform);
+#endif
}
+#if HWUI_NEW_OPS
+ info.layerUpdateQueue->enqueueLayerWithDamage(this, dirty);
+#else
if (dirty.intersect(0, 0, getWidth(), getHeight())) {
dirty.roundOut(&dirty);
mLayer->updateDeferred(this, dirty.fLeft, dirty.fTop, dirty.fRight, dirty.fBottom);
@@ -304,6 +339,7 @@ void RenderNode::pushLayerUpdate(TreeInfo& info) {
if (info.renderer && mLayer->deferredUpdateScheduled) {
info.renderer->pushLayerUpdate(mLayer);
}
+#endif
// There might be prefetched layers that need to be accounted for.
// That might be us, so tell CanvasContext that this layer is in the
@@ -365,7 +401,9 @@ void RenderNode::pushStagingPropertiesChanges(TreeInfo& info) {
damageSelf(info);
info.damageAccumulator->popTransform();
syncProperties();
+#if !HWUI_NEW_OPS
applyLayerPropertiesToLayer(info);
+#endif
// We could try to be clever and only re-damage if the matrix changed.
// However, we don't need to worry about that. The cost of over-damaging
// here is only going to be a single additional map rect of this node
@@ -376,6 +414,7 @@ void RenderNode::pushStagingPropertiesChanges(TreeInfo& info) {
}
}
+#if !HWUI_NEW_OPS
void RenderNode::applyLayerPropertiesToLayer(TreeInfo& info) {
if (CC_LIKELY(!mLayer)) return;
@@ -384,6 +423,7 @@ void RenderNode::applyLayerPropertiesToLayer(TreeInfo& info) {
mLayer->setColorFilter(props.colorFilter());
mLayer->setBlend(props.needsBlending());
}
+#endif
void RenderNode::syncDisplayList() {
// Make sure we inc first so that we don't fluctuate between 0 and 1,
@@ -451,7 +491,7 @@ void RenderNode::prepareSubTree(TreeInfo& info, bool functorsNeedLayer, DisplayL
void RenderNode::destroyHardwareResources() {
if (mLayer) {
- LayerRenderer::destroyLayer(mLayer);
+ destroyLayer(mLayer);
mLayer = nullptr;
}
if (mDisplayList) {
@@ -978,7 +1018,11 @@ void RenderNode::issueOperations(OpenGLRenderer& renderer, T& handler) {
return;
}
+#if HWUI_NEW_OPS
+ const bool drawLayer = false;
+#else
const bool drawLayer = (mLayer && (&renderer != mLayer->renderer.get()));
+#endif
// If we are updating the contents of mLayer, we don't want to apply any of
// the RenderNode's properties to this issueOperations pass. Those will all
// be applied when the layer is drawn, aka when this is true.
diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h
index 57e41c611547..3500cb200a51 100644
--- a/libs/hwui/RenderNode.h
+++ b/libs/hwui/RenderNode.h
@@ -44,13 +44,22 @@ namespace android {
namespace uirenderer {
class CanvasState;
-class DisplayListOp;
class DisplayListCanvas;
+class DisplayListOp;
class OpenGLRenderer;
+class OpReorderer;
class Rect;
-class Layer;
class SkiaShader;
+
+#if HWUI_NEW_OPS
+class OffscreenBuffer;
+typedef OffscreenBuffer layer_t;
+#else
+class Layer;
+typedef Layer layer_t;
+#endif
+
class ClipRectOp;
class SaveLayerOp;
class SaveOp;
@@ -162,11 +171,11 @@ public:
return mStagingProperties;
}
- int getWidth() {
+ uint32_t getWidth() {
return properties().getWidth();
}
- int getHeight() {
+ uint32_t getHeight() {
return properties().getHeight();
}
@@ -193,9 +202,13 @@ public:
}
// Only call if RenderNode has DisplayList...
- const DisplayList& getDisplayList() const {
- return *mDisplayList;
+ const DisplayList* getDisplayList() const {
+ return mDisplayList;
}
+#if HWUI_NEW_OPS
+ OffscreenBuffer* getLayer() const { return mLayer; }
+ OffscreenBuffer** getLayerHandle() { return &mLayer; } // ugh...
+#endif
private:
typedef key_value_pair_t<float, DrawRenderNodeOp*> ZDrawRenderNodeOpPair;
@@ -262,7 +275,9 @@ private:
void pushStagingPropertiesChanges(TreeInfo& info);
void pushStagingDisplayListChanges(TreeInfo& info);
void prepareSubTree(TreeInfo& info, bool functorsNeedLayer, DisplayList* subtree);
+#if !HWUI_NEW_OPS
void applyLayerPropertiesToLayer(TreeInfo& info);
+#endif
void prepareLayer(TreeInfo& info, uint32_t dirtyMask);
void pushLayerUpdate(TreeInfo& info);
void deleteDisplayList();
@@ -287,7 +302,7 @@ private:
// Owned by RT. Lifecycle is managed by prepareTree(), with the exception
// being in ~RenderNode() which may happen on any thread.
- Layer* mLayer;
+ layer_t* mLayer = nullptr;
/**
* Draw time state - these properties are only set and used during rendering
diff --git a/libs/hwui/TreeInfo.h b/libs/hwui/TreeInfo.h
index 1c3148726b63..be25516c587a 100644
--- a/libs/hwui/TreeInfo.h
+++ b/libs/hwui/TreeInfo.h
@@ -16,11 +16,11 @@
#ifndef TREEINFO_H
#define TREEINFO_H
-#include <string>
+#include "utils/Macros.h"
#include <utils/Timers.h>
-#include "utils/Macros.h"
+#include <string>
namespace android {
namespace uirenderer {
@@ -30,6 +30,7 @@ class CanvasContext;
}
class DamageAccumulator;
+class LayerUpdateQueue;
class OpenGLRenderer;
class RenderState;
@@ -75,9 +76,14 @@ public:
// Must not be null during actual usage
DamageAccumulator* damageAccumulator = nullptr;
+
+#if HWUI_NEW_OPS
+ LayerUpdateQueue* layerUpdateQueue = nullptr;
+#else
// The renderer that will be drawing the next frame. Use this to push any
// layer updates or similar. May be NULL.
OpenGLRenderer* renderer = nullptr;
+#endif
ErrorHandler* errorHandler = nullptr;
struct Out {
diff --git a/libs/hwui/microbench/OpReordererBench.cpp b/libs/hwui/microbench/OpReordererBench.cpp
index 43f170f54d41..7b8d0e542a2f 100644
--- a/libs/hwui/microbench/OpReordererBench.cpp
+++ b/libs/hwui/microbench/OpReordererBench.cpp
@@ -56,7 +56,9 @@ void BM_OpReorderer_defer::Run(int iters) {
BENCHMARK_NO_ARG(BM_OpReorderer_deferAndRender);
void BM_OpReorderer_deferAndRender::Run(int iters) {
- TestUtils::runOnRenderThread([this, iters](RenderState& renderState, Caches& caches) {
+ TestUtils::runOnRenderThread([this, iters](renderthread::RenderThread& thread) {
+ RenderState& renderState = thread.renderState();
+ Caches& caches = Caches::getInstance();
StartBenchmarkTiming();
for (int i = 0; i < iters; i++) {
OpReorderer reorderer(200, 200, *sReorderingDisplayList);
diff --git a/libs/hwui/renderstate/MeshState.cpp b/libs/hwui/renderstate/MeshState.cpp
index 0521f6573e39..03cb5ce8ce2e 100644
--- a/libs/hwui/renderstate/MeshState.cpp
+++ b/libs/hwui/renderstate/MeshState.cpp
@@ -100,6 +100,24 @@ bool MeshState::bindMeshBufferInternal(GLuint buffer) {
return false;
}
+void MeshState::genOrUpdateMeshBuffer(GLuint* buffer, GLsizeiptr size,
+ const void* data, GLenum usage) {
+ if (!*buffer) {
+ glGenBuffers(1, buffer);
+ }
+ bindMeshBuffer(*buffer);
+ glBufferData(GL_ARRAY_BUFFER, size, data, usage);
+}
+
+void MeshState::deleteMeshBuffer(GLuint buffer) {
+ if (buffer == mCurrentBuffer) {
+ // GL defines that deleting the currently bound VBO rebinds to 0 (no VBO).
+ // Reflect this in our cached value.
+ mCurrentBuffer = 0;
+ }
+ glDeleteBuffers(1, &buffer);
+}
+
///////////////////////////////////////////////////////////////////////////////
// Vertices
///////////////////////////////////////////////////////////////////////////////
diff --git a/libs/hwui/renderstate/MeshState.h b/libs/hwui/renderstate/MeshState.h
index e80f4d0d6c41..6c0fb78cae17 100644
--- a/libs/hwui/renderstate/MeshState.h
+++ b/libs/hwui/renderstate/MeshState.h
@@ -75,6 +75,9 @@ public:
*/
bool unbindMeshBuffer();
+ void genOrUpdateMeshBuffer(GLuint* buffer, GLsizeiptr size, const void* data, GLenum usage);
+ void deleteMeshBuffer(GLuint);
+
///////////////////////////////////////////////////////////////////////////////
// Vertices
///////////////////////////////////////////////////////////////////////////////
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index c1f667024784..b6fecb45a8ad 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -20,6 +20,7 @@
#include "Caches.h"
#include "DeferredLayerUpdater.h"
#include "EglManager.h"
+#include "LayerUpdateQueue.h"
#include "LayerRenderer.h"
#include "OpenGLRenderer.h"
#include "Properties.h"
@@ -198,7 +199,11 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo,
mCurrentFrameInfo->markSyncStart();
info.damageAccumulator = &mDamageAccumulator;
+#if HWUI_NEW_OPS
+ info.layerUpdateQueue = &mLayerUpdateQueue;
+#else
info.renderer = mCanvas;
+#endif
mAnimationContext->startFrame(info.mode);
for (const sp<RenderNode>& node : mRenderNodes) {
@@ -333,7 +338,8 @@ void CanvasContext::draw() {
mEglManager.damageFrame(frame, dirty);
#if HWUI_NEW_OPS
- OpReorderer reorderer(dirty, frame.width(), frame.height(), mRenderNodes);
+ OpReorderer reorderer(mLayerUpdateQueue, dirty, frame.width(), frame.height(), mRenderNodes);
+ mLayerUpdateQueue.clear();
BakedOpRenderer renderer(Caches::getInstance(), mRenderThread.renderState(), mOpaque);
// TODO: profiler().draw(mCanvas);
reorderer.replayBakedOps<BakedOpDispatcher>(renderer);
@@ -555,7 +561,11 @@ void CanvasContext::buildLayer(RenderNode* node) {
TreeInfo info(TreeInfo::MODE_FULL, *this);
info.damageAccumulator = &mDamageAccumulator;
+#if HWUI_NEW_OPS
+ info.layerUpdateQueue = &mLayerUpdateQueue;
+#else
info.renderer = mCanvas;
+#endif
info.runAnimations = false;
node->prepareTree(info);
SkRect ignore;
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 30e6562526d5..d656014fdbcb 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -18,9 +18,10 @@
#define CANVASCONTEXT_H_
#include "DamageAccumulator.h"
-#include "IContextFactory.h"
#include "FrameInfo.h"
#include "FrameInfoVisualizer.h"
+#include "IContextFactory.h"
+#include "LayerUpdateQueue.h"
#include "RenderNode.h"
#include "utils/RingBuffer.h"
#include "renderthread/RenderTask.h"
@@ -83,7 +84,7 @@ public:
void draw();
void destroy();
- // IFrameCallback, Chroreographer-driven frame callback entry point
+ // IFrameCallback, Choreographer-driven frame callback entry point
virtual void doFrame() override;
void prepareAndDraw(RenderNode* node);
@@ -118,7 +119,7 @@ public:
void addRenderNode(RenderNode* node, bool placeFront) {
int pos = placeFront ? 0 : static_cast<int>(mRenderNodes.size());
- mRenderNodes.emplace( mRenderNodes.begin() + pos, node);
+ mRenderNodes.emplace(mRenderNodes.begin() + pos, node);
}
void removeRenderNode(RenderNode* node) {
@@ -166,6 +167,7 @@ private:
OpenGLRenderer* mCanvas = nullptr;
bool mHaveNewSurface = false;
DamageAccumulator mDamageAccumulator;
+ LayerUpdateQueue mLayerUpdateQueue;
std::unique_ptr<AnimationContext> mAnimationContext;
std::vector< sp<RenderNode> > mRenderNodes;
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index 15ccd6ac5b6b..a1107f029691 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -563,10 +563,7 @@ void RenderProxy::post(RenderTask* task) {
void* RenderProxy::postAndWait(MethodInvokeRenderTask* task) {
void* retval;
task->setReturnPtr(&retval);
- SignalingRenderTask syncTask(task, &mSyncMutex, &mSyncCondition);
- AutoMutex _lock(mSyncMutex);
- mRenderThread.queue(&syncTask);
- mSyncCondition.wait(mSyncMutex);
+ mRenderThread.queueAndWait(task);
return retval;
}
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index 338fab650876..d0e601e09be6 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -117,9 +117,6 @@ private:
DrawFrameTask mDrawFrameTask;
- Mutex mSyncMutex;
- Condition mSyncCondition;
-
void destroyContext();
void post(RenderTask* task);
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 8fcd10967e17..526a84861d98 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -28,9 +28,6 @@
#include <utils/Log.h>
namespace android {
-using namespace uirenderer::renderthread;
-ANDROID_SINGLETON_STATIC_INSTANCE(RenderThread);
-
namespace uirenderer {
namespace renderthread {
@@ -136,7 +133,22 @@ public:
}
};
-RenderThread::RenderThread() : Thread(true), Singleton<RenderThread>()
+static bool gHasRenderThreadInstance = false;
+
+bool RenderThread::hasInstance() {
+ return gHasRenderThreadInstance;
+}
+
+RenderThread& RenderThread::getInstance() {
+ // This is a pointer because otherwise __cxa_finalize
+ // will try to delete it like a Good Citizen but that causes us to crash
+ // because we don't want to delete the RenderThread normally.
+ static RenderThread* sInstance = new RenderThread();
+ gHasRenderThreadInstance = true;
+ return *sInstance;
+}
+
+RenderThread::RenderThread() : Thread(true)
, mNextWakeup(LLONG_MAX)
, mDisplayEventReceiver(nullptr)
, mVsyncRequested(false)
@@ -313,13 +325,10 @@ void RenderThread::queue(RenderTask* task) {
}
void RenderThread::queueAndWait(RenderTask* task) {
- Mutex mutex;
- Condition condition;
- SignalingRenderTask syncTask(task, &mutex, &condition);
-
- AutoMutex _lock(mutex);
+ SignalingRenderTask syncTask(task, &mSyncMutex, &mSyncCondition);
+ AutoMutex _lock(mSyncMutex);
queue(&syncTask);
- condition.wait(mutex);
+ mSyncCondition.wait(mSyncMutex);
}
void RenderThread::queueAtFront(RenderTask* task) {
diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h
index f3444a85a336..d8c7e61f34eb 100644
--- a/libs/hwui/renderthread/RenderThread.h
+++ b/libs/hwui/renderthread/RenderThread.h
@@ -25,11 +25,11 @@
#include <cutils/compiler.h>
#include <ui/DisplayInfo.h>
#include <utils/Looper.h>
-#include <utils/Mutex.h>
-#include <utils/Singleton.h>
#include <utils/Thread.h>
+#include <condition_variable>
#include <memory>
+#include <mutex>
#include <set>
namespace android {
@@ -72,7 +72,7 @@ protected:
~IFrameCallback() {}
};
-class ANDROID_API RenderThread : public Thread, protected Singleton<RenderThread> {
+class ANDROID_API RenderThread : public Thread {
public:
// RenderThread takes complete ownership of tasks that are queued
// and will delete them after they are run
@@ -100,7 +100,6 @@ protected:
virtual bool threadLoop() override;
private:
- friend class Singleton<RenderThread>;
friend class DispatchFrameCallbacks;
friend class RenderProxy;
friend class android::uirenderer::TestUtils;
@@ -108,6 +107,9 @@ private:
RenderThread();
virtual ~RenderThread();
+ static bool hasInstance();
+ static RenderThread& getInstance();
+
void initThreadLocals();
void initializeDisplayEventReceiver();
static int displayEventReceiverCallback(int fd, int events, void* data);
@@ -125,6 +127,8 @@ private:
nsecs_t mNextWakeup;
TaskQueue mQueue;
+ Mutex mSyncMutex;
+ Condition mSyncCondition;
DisplayInfo mDisplayInfo;
diff --git a/libs/hwui/tests/TreeContentAnimation.cpp b/libs/hwui/tests/TreeContentAnimation.cpp
index 2eefd37561a6..29d9803ddf9c 100644
--- a/libs/hwui/tests/TreeContentAnimation.cpp
+++ b/libs/hwui/tests/TreeContentAnimation.cpp
@@ -24,6 +24,7 @@
#include <RenderNode.h>
#include <renderthread/RenderProxy.h>
#include <renderthread/RenderTask.h>
+#include <unit_tests/TestUtils.h>
#include "Benchmark.h"
#include "TestContext.h"
@@ -401,3 +402,27 @@ static Benchmark _SaveLayer(BenchmarkInfo{
"Tests the clipped saveLayer codepath. Draws content into offscreen buffers and back again.",
TreeContentAnimation::run<SaveLayerAnimation>
});
+
+
+class HwLayerAnimation : public TreeContentAnimation {
+public:
+ sp<RenderNode> card = TestUtils::createNode<TestCanvas>(0, 0, 200, 200, [] (TestCanvas& canvas) {
+ canvas.drawColor(0xFF0000FF, SkXfermode::kSrcOver_Mode);
+ }, true);
+ void createContent(int width, int height, TestCanvas* canvas) override {
+ canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); // background
+ canvas->drawRenderNode(card.get());
+ }
+ void doFrame(int frameNr) override {
+ int curFrame = frameNr % 150;
+ card->mutateStagingProperties().setTranslationX(curFrame);
+ card->mutateStagingProperties().setTranslationY(curFrame);
+ card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+ }
+};
+static Benchmark _HwLayer(BenchmarkInfo{
+ "hwlayer",
+ "A nested pair of nodes with LAYER_TYPE_HARDWARE set on each. "
+ "Tests the hardware layer codepath.",
+ TreeContentAnimation::run<HwLayerAnimation>
+});
diff --git a/libs/hwui/unit_tests/FatVectorTests.cpp b/libs/hwui/unit_tests/FatVectorTests.cpp
index fb760ac549cd..3ef329a0d51b 100644
--- a/libs/hwui/unit_tests/FatVectorTests.cpp
+++ b/libs/hwui/unit_tests/FatVectorTests.cpp
@@ -56,6 +56,27 @@ TEST(FatVector, simpleAllocate) {
}
}
+TEST(FatVector, preSizeConstructor) {
+ {
+ FatVector<int, 4> v(32);
+ EXPECT_EQ(32u, v.capacity());
+ EXPECT_EQ(32u, v.size());
+ EXPECT_FALSE(allocationIsInternal(v));
+ }
+ {
+ FatVector<int, 4> v(4);
+ EXPECT_EQ(4u, v.capacity());
+ EXPECT_EQ(4u, v.size());
+ EXPECT_TRUE(allocationIsInternal(v));
+ }
+ {
+ FatVector<int, 4> v(2);
+ EXPECT_EQ(4u, v.capacity());
+ EXPECT_EQ(2u, v.size());
+ EXPECT_TRUE(allocationIsInternal(v));
+ }
+}
+
TEST(FatVector, shrink) {
FatVector<int, 10> v;
EXPECT_TRUE(allocationIsInternal(v));
@@ -78,9 +99,9 @@ TEST(FatVector, destructorInternal) {
FatVector<TestUtils::SignalingDtor, 0> v;
v.emplace_back(&count);
EXPECT_FALSE(allocationIsInternal(v));
- EXPECT_EQ(0, count);
+ EXPECT_EQ(0, count) << "Destruction shouldn't have happened yet";
}
- EXPECT_EQ(1, count);
+ EXPECT_EQ(1, count) << "Destruction should happen exactly once";
}
TEST(FatVector, destructorExternal) {
@@ -92,7 +113,7 @@ TEST(FatVector, destructorExternal) {
v.emplace_back(&count);
EXPECT_TRUE(allocationIsInternal(v));
}
- EXPECT_EQ(0, count);
+ EXPECT_EQ(0, count) << "Destruction shouldn't have happened yet";
}
- EXPECT_EQ(10, count);
+ EXPECT_EQ(10, count) << "Destruction should happen exactly once";
}
diff --git a/libs/hwui/unit_tests/LayerUpdateQueueTests.cpp b/libs/hwui/unit_tests/LayerUpdateQueueTests.cpp
new file mode 100644
index 000000000000..9d625bc62696
--- /dev/null
+++ b/libs/hwui/unit_tests/LayerUpdateQueueTests.cpp
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+
+#include <LayerUpdateQueue.h>
+#include <RenderNode.h>
+
+#include <unit_tests/TestUtils.h>
+
+namespace android {
+namespace uirenderer {
+
+TEST(LayerUpdateQueue, construct) {
+ LayerUpdateQueue queue;
+ EXPECT_TRUE(queue.entries().empty());
+}
+
+// sync node properties, so properties() reflects correct width and height
+static sp<RenderNode> createSyncedNode(uint32_t width, uint32_t height) {
+ sp<RenderNode> node = TestUtils::createNode(0, 0, width, height);
+ TestUtils::syncNodePropertiesAndDisplayList(node);
+ return node;
+}
+
+TEST(LayerUpdateQueue, enqueueSimple) {
+ sp<RenderNode> a = createSyncedNode(100, 100);
+ sp<RenderNode> b = createSyncedNode(200, 200);
+
+ LayerUpdateQueue queue;
+ queue.enqueueLayerWithDamage(a.get(), Rect(25, 25, 75, 75));
+ queue.enqueueLayerWithDamage(b.get(), Rect(100, 100, 300, 300));
+
+ EXPECT_EQ(2u, queue.entries().size());
+
+ EXPECT_EQ(a.get(), queue.entries()[0].renderNode);
+ EXPECT_EQ(Rect(25, 25, 75, 75), queue.entries()[0].damage);
+ EXPECT_EQ(b.get(), queue.entries()[1].renderNode);
+ EXPECT_EQ(Rect(100, 100, 200, 200), queue.entries()[1].damage); // clipped to bounds
+}
+
+TEST(LayerUpdateQueue, enqueueUnion) {
+ sp<RenderNode> a = createSyncedNode(100, 100);
+
+ LayerUpdateQueue queue;
+ queue.enqueueLayerWithDamage(a.get(), Rect(10, 10, 20, 20));
+ queue.enqueueLayerWithDamage(a.get(), Rect(30, 30, 40, 40));
+
+ EXPECT_EQ(1u, queue.entries().size());
+
+ EXPECT_EQ(a.get(), queue.entries()[0].renderNode);
+ EXPECT_EQ(Rect(10, 10, 40, 40), queue.entries()[0].damage);
+}
+
+TEST(LayerUpdateQueue, clear) {
+ sp<RenderNode> a = createSyncedNode(100, 100);
+
+ LayerUpdateQueue queue;
+ queue.enqueueLayerWithDamage(a.get(), Rect(100, 100));
+
+ EXPECT_FALSE(queue.entries().empty());
+
+ queue.clear();
+
+ EXPECT_TRUE(queue.entries().empty());
+}
+
+};
+};
diff --git a/libs/hwui/unit_tests/OpReordererTests.cpp b/libs/hwui/unit_tests/OpReordererTests.cpp
index ffb575f2ddd9..09b10c3af4f9 100644
--- a/libs/hwui/unit_tests/OpReordererTests.cpp
+++ b/libs/hwui/unit_tests/OpReordererTests.cpp
@@ -20,6 +20,7 @@
#include <OpReorderer.h>
#include <RecordedOp.h>
#include <RecordingCanvas.h>
+#include <renderthread/CanvasContext.h> // todo: remove
#include <unit_tests/TestUtils.h>
#include <unordered_map>
@@ -27,6 +28,7 @@
namespace android {
namespace uirenderer {
+LayerUpdateQueue sEmptyLayerUpdateQueue;
/**
* Virtual class implemented by each test to redirect static operation / state transitions to
@@ -42,14 +44,24 @@ namespace uirenderer {
class TestRendererBase {
public:
virtual ~TestRendererBase() {}
- virtual OffscreenBuffer* startLayer(uint32_t width, uint32_t height) { ADD_FAILURE(); return nullptr; }
- virtual void endLayer() { ADD_FAILURE(); }
+ virtual OffscreenBuffer* createLayer(uint32_t, uint32_t) {
+ ADD_FAILURE() << "Layer creation not expected in this test";
+ return nullptr;
+ }
+ virtual void startLayer(OffscreenBuffer*) {
+ ADD_FAILURE() << "Layer repaint not expected in this test";
+ }
+ virtual void endLayer() {
+ ADD_FAILURE() << "Layer updates not expected in this test";
+ }
virtual void startFrame(uint32_t width, uint32_t height) {}
virtual void endFrame() {}
// define virtual defaults for direct
#define BASE_OP_METHOD(Type) \
- virtual void on##Type(const Type&, const BakedOpState&) { ADD_FAILURE(); }
+ virtual void on##Type(const Type&, const BakedOpState&) { \
+ ADD_FAILURE() << #Type " not expected in this test"; \
+ }
MAP_OPS(BASE_OP_METHOD)
int getIndex() { return mIndex; }
@@ -192,7 +204,8 @@ TEST(OpReorderer, renderNode) {
std::vector< sp<RenderNode> > nodes;
nodes.push_back(parent.get());
- OpReorderer reorderer(SkRect::MakeWH(200, 200), 200, 200, nodes);
+ OpReorderer reorderer(sEmptyLayerUpdateQueue,
+ SkRect::MakeWH(200, 200), 200, 200, nodes);
RenderNodeTestRenderer renderer;
reorderer.replayBakedOps<TestDispatcher>(renderer);
@@ -216,7 +229,8 @@ TEST(OpReorderer, clipped) {
std::vector< sp<RenderNode> > nodes;
nodes.push_back(node.get());
- OpReorderer reorderer(SkRect::MakeLTRB(10, 20, 30, 40), // clip to small area, should see in receiver
+ OpReorderer reorderer(sEmptyLayerUpdateQueue,
+ SkRect::MakeLTRB(10, 20, 30, 40), // clip to small area, should see in receiver
200, 200, nodes);
ClippedTestRenderer renderer;
@@ -226,7 +240,7 @@ TEST(OpReorderer, clipped) {
class SaveLayerSimpleTestRenderer : public TestRendererBase {
public:
- OffscreenBuffer* startLayer(uint32_t width, uint32_t height) override {
+ OffscreenBuffer* createLayer(uint32_t width, uint32_t height) override {
EXPECT_EQ(0, mIndex++);
EXPECT_EQ(180u, width);
EXPECT_EQ(180u, height);
@@ -268,13 +282,13 @@ TEST(OpReorderer, saveLayerSimple) {
/* saveLayer1 {rect1, saveLayer2 { rect2 } } will play back as:
- * - startLayer2, rect2 endLayer2
- * - startLayer1, rect1, drawLayer2, endLayer1
+ * - createLayer2, rect2 endLayer2
+ * - createLayer1, rect1, drawLayer2, endLayer1
* - startFrame, layerOp1, endFrame
*/
class SaveLayerNestedTestRenderer : public TestRendererBase {
public:
- OffscreenBuffer* startLayer(uint32_t width, uint32_t height) override {
+ OffscreenBuffer* createLayer(uint32_t width, uint32_t height) override {
const int index = mIndex++;
if (index == 0) {
EXPECT_EQ(400u, width);
@@ -356,5 +370,162 @@ TEST(OpReorderer, saveLayerContentRejection) {
reorderer.replayBakedOps<TestDispatcher>(renderer);
}
+class HwLayerSimpleTestRenderer : public TestRendererBase {
+public:
+ void startLayer(OffscreenBuffer* offscreenBuffer) override {
+ EXPECT_EQ(0, mIndex++);
+ EXPECT_EQ(offscreenBuffer, (OffscreenBuffer*) 0x0124);
+ }
+ void onRectOp(const RectOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(1, mIndex++);
+
+ EXPECT_TRUE(state.computedState.transform.isIdentity())
+ << "Transform should be reset within layer";
+
+ EXPECT_EQ(state.computedState.clipRect, Rect(25, 25, 75, 75))
+ << "Damage rect should be used to clip layer content";
+ }
+ void endLayer() override {
+ EXPECT_EQ(2, mIndex++);
+ }
+ void startFrame(uint32_t width, uint32_t height) override {
+ EXPECT_EQ(3, mIndex++);
+ }
+ void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(4, mIndex++);
+ }
+ void endFrame() override {
+ EXPECT_EQ(5, mIndex++);
+ }
+};
+TEST(OpReorderer, hwLayerSimple) {
+ sp<RenderNode> node = TestUtils::createNode<RecordingCanvas>(10, 10, 110, 110, [](RecordingCanvas& canvas) {
+ SkPaint paint;
+ paint.setColor(SK_ColorWHITE);
+ canvas.drawRect(0, 0, 100, 100, paint);
+ });
+ node->mutateStagingProperties().mutateLayerProperties().setType(LayerType::RenderLayer);
+ node->setPropertyFieldsDirty(RenderNode::GENERIC);
+ OffscreenBuffer** bufferHandle = node->getLayerHandle();
+ *bufferHandle = (OffscreenBuffer*) 0x0124;
+
+ TestUtils::syncNodePropertiesAndDisplayList(node);
+
+ std::vector< sp<RenderNode> > nodes;
+ nodes.push_back(node.get());
+
+ // only enqueue partial damage
+ LayerUpdateQueue layerUpdateQueue;
+ layerUpdateQueue.enqueueLayerWithDamage(node.get(), Rect(25, 25, 75, 75));
+
+ OpReorderer reorderer(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, nodes);
+
+ HwLayerSimpleTestRenderer renderer;
+ reorderer.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(6, renderer.getIndex());
+
+ // clean up layer pointer, so we can safely destruct RenderNode
+ *bufferHandle = nullptr;
+}
+
+
+/* parentLayer { greyRect, saveLayer { childLayer { whiteRect } } } will play back as:
+ * - startLayer(child), rect(grey), endLayer
+ * - createLayer, drawLayer(child), endLayer
+ * - startLayer(parent), rect(white), drawLayer(saveLayer), endLayer
+ * - startFrame, drawLayer(parent), endLayerb
+ */
+class HwLayerComplexTestRenderer : public TestRendererBase {
+public:
+ OffscreenBuffer* createLayer(uint32_t width, uint32_t height) {
+ EXPECT_EQ(3, mIndex++); // savelayer first
+ return (OffscreenBuffer*)0xabcd;
+ }
+ void startLayer(OffscreenBuffer* offscreenBuffer) override {
+ int index = mIndex++;
+ if (index == 0) {
+ // starting inner layer
+ EXPECT_EQ((OffscreenBuffer*)0x4567, offscreenBuffer);
+ } else if (index == 6) {
+ // starting outer layer
+ EXPECT_EQ((OffscreenBuffer*)0x0123, offscreenBuffer);
+ } else { ADD_FAILURE(); }
+ }
+ void onRectOp(const RectOp& op, const BakedOpState& state) override {
+ int index = mIndex++;
+ if (index == 1) {
+ // inner layer's rect (white)
+ EXPECT_EQ(SK_ColorWHITE, op.paint->getColor());
+ } else if (index == 7) {
+ // outer layer's rect (grey)
+ EXPECT_EQ(SK_ColorDKGRAY, op.paint->getColor());
+ } else { ADD_FAILURE(); }
+ }
+ void endLayer() override {
+ int index = mIndex++;
+ EXPECT_TRUE(index == 2 || index == 5 || index == 9);
+ }
+ void startFrame(uint32_t width, uint32_t height) override {
+ EXPECT_EQ(10, mIndex++);
+ }
+ void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
+ int index = mIndex++;
+ if (index == 4) {
+ EXPECT_EQ((OffscreenBuffer*)0x4567, *op.layerHandle);
+ } else if (index == 8) {
+ EXPECT_EQ((OffscreenBuffer*)0xabcd, *op.layerHandle);
+ } else if (index == 11) {
+ EXPECT_EQ((OffscreenBuffer*)0x0123, *op.layerHandle);
+ } else { ADD_FAILURE(); }
+ }
+ void endFrame() override {
+ EXPECT_EQ(12, mIndex++);
+ }
+};
+TEST(OpReorderer, hwLayerComplex) {
+ sp<RenderNode> child = TestUtils::createNode<RecordingCanvas>(50, 50, 150, 150, [](RecordingCanvas& canvas) {
+ SkPaint paint;
+ paint.setColor(SK_ColorWHITE);
+ canvas.drawRect(0, 0, 100, 100, paint);
+ });
+ child->mutateStagingProperties().mutateLayerProperties().setType(LayerType::RenderLayer);
+ child->setPropertyFieldsDirty(RenderNode::GENERIC);
+ *(child->getLayerHandle()) = (OffscreenBuffer*) 0x4567;
+
+ RenderNode* childPtr = child.get();
+ sp<RenderNode> parent = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200, [childPtr](RecordingCanvas& canvas) {
+ SkPaint paint;
+ paint.setColor(SK_ColorDKGRAY);
+ canvas.drawRect(0, 0, 200, 200, paint);
+
+ canvas.saveLayerAlpha(50, 50, 150, 150, 128, SkCanvas::kClipToLayer_SaveFlag);
+ canvas.drawRenderNode(childPtr);
+ canvas.restore();
+ });
+ parent->mutateStagingProperties().mutateLayerProperties().setType(LayerType::RenderLayer);
+ parent->setPropertyFieldsDirty(RenderNode::GENERIC);
+ *(parent->getLayerHandle()) = (OffscreenBuffer*) 0x0123;
+
+ TestUtils::syncNodePropertiesAndDisplayList(child);
+ TestUtils::syncNodePropertiesAndDisplayList(parent);
+
+ std::vector< sp<RenderNode> > nodes;
+ nodes.push_back(parent.get());
+
+ LayerUpdateQueue layerUpdateQueue;
+ layerUpdateQueue.enqueueLayerWithDamage(child.get(), Rect(100, 100));
+ layerUpdateQueue.enqueueLayerWithDamage(parent.get(), Rect(200, 200));
+
+ OpReorderer reorderer(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, nodes);
+
+ HwLayerComplexTestRenderer renderer;
+ reorderer.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(13, renderer.getIndex());
+
+ // clean up layer pointers, so we can safely destruct RenderNodes
+ *(child->getLayerHandle()) = nullptr;
+ *(parent->getLayerHandle()) = nullptr;
+}
+
} // namespace uirenderer
} // namespace android
diff --git a/libs/hwui/unit_tests/RecordingCanvasTests.cpp b/libs/hwui/unit_tests/RecordingCanvasTests.cpp
index e8cdf461c783..dcf1f648a9b5 100644
--- a/libs/hwui/unit_tests/RecordingCanvasTests.cpp
+++ b/libs/hwui/unit_tests/RecordingCanvasTests.cpp
@@ -53,7 +53,7 @@ TEST(RecordingCanvas, testSimpleRectRecord) {
ASSERT_EQ(Rect(0, 0, 100, 200), op.localClipRect);
ASSERT_EQ(Rect(10, 20, 90, 180), op.unmappedBounds);
});
- ASSERT_EQ(1, count); // only one observed
+ ASSERT_EQ(1, count);
}
TEST(RecordingCanvas, backgroundAndImage) {
@@ -106,7 +106,7 @@ TEST(RecordingCanvas, backgroundAndImage) {
}
count++;
});
- ASSERT_EQ(2, count); // two draws observed
+ ASSERT_EQ(2, count);
}
TEST(RecordingCanvas, saveLayerSimple) {
@@ -121,7 +121,9 @@ TEST(RecordingCanvas, saveLayerSimple) {
switch(count++) {
case 0:
EXPECT_EQ(RecordedOpId::BeginLayerOp, op.opId);
- // TODO: add asserts
+ EXPECT_EQ(Rect(10, 20, 190, 180), op.unmappedBounds);
+ EXPECT_EQ(Rect(0, 0, 200, 200), op.localClipRect);
+ EXPECT_TRUE(op.localMatrix.isIdentity());
break;
case 1:
EXPECT_EQ(RecordedOpId::RectOp, op.opId);
@@ -132,7 +134,7 @@ TEST(RecordingCanvas, saveLayerSimple) {
break;
case 2:
EXPECT_EQ(RecordedOpId::EndLayerOp, op.opId);
- // TODO: add asserts
+ // Don't bother asserting recording state data - it's not used
break;
default:
ADD_FAILURE();
@@ -155,10 +157,8 @@ TEST(RecordingCanvas, saveLayerViewportCrop) {
if (count++ == 1) {
Matrix4 expectedMatrix;
EXPECT_EQ(RecordedOpId::RectOp, op.opId);
-
- // recorded clip rect should be intersection of
- // viewport and saveLayer bounds, in layer space
- EXPECT_EQ(Rect(0, 0, 100, 100), op.localClipRect);
+ EXPECT_EQ(Rect(0, 0, 100, 100), op.localClipRect) << "Recorded clip rect should be"
+ " intersection of viewport and saveLayer bounds, in layer space";
EXPECT_EQ(Rect(0, 0, 400, 400), op.unmappedBounds);
expectedMatrix.loadTranslate(-100, -100, 0);
EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix);
@@ -183,14 +183,11 @@ TEST(RecordingCanvas, saveLayerRotateUnclipped) {
int count = 0;
playbackOps(*dl, [&count](const RecordedOp& op) {
if (count++ == 1) {
- Matrix4 expectedMatrix;
EXPECT_EQ(RecordedOpId::RectOp, op.opId);
-
- // recorded rect doesn't see rotate, since recorded relative to saveLayer bounds
EXPECT_EQ(Rect(0, 0, 100, 100), op.localClipRect);
EXPECT_EQ(Rect(0, 0, 100, 100), op.unmappedBounds);
- expectedMatrix.loadIdentity();
- EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix);
+ EXPECT_MATRIX_APPROX_EQ(Matrix4::identity(), op.localMatrix)
+ << "Recorded op shouldn't see any canvas transform before the saveLayer";
}
});
EXPECT_EQ(3, count);
diff --git a/libs/hwui/unit_tests/TestUtils.h b/libs/hwui/unit_tests/TestUtils.h
index 5b09fdac4ff2..770f413352c2 100644
--- a/libs/hwui/unit_tests/TestUtils.h
+++ b/libs/hwui/unit_tests/TestUtils.h
@@ -89,15 +89,24 @@ public:
return std::unique_ptr<DisplayList>(canvas.finishRecording());
}
- template<class CanvasType>
- static sp<RenderNode> createNode(int left, int top, int right, int bottom,
- std::function<void(CanvasType& canvas)> canvasCallback) {
+ static sp<RenderNode> createNode(int left, int top, int right, int bottom, bool onLayer = false) {
sp<RenderNode> node = new RenderNode();
node->mutateStagingProperties().setLeftTopRightBottom(left, top, right, bottom);
node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+ if (onLayer) {
+ node->mutateStagingProperties().mutateLayerProperties().setType(LayerType::RenderLayer);
+ node->setPropertyFieldsDirty(RenderNode::GENERIC);
+ }
+ return node;
+ }
+
+ template<class CanvasType>
+ static sp<RenderNode> createNode(int left, int top, int right, int bottom,
+ std::function<void(CanvasType& canvas)> canvasCallback, bool onLayer = false) {
+ sp<RenderNode> node = createNode(left, top, right, bottom, onLayer);
- CanvasType canvas(
- node->stagingProperties().getWidth(), node->stagingProperties().getHeight());
+ auto&& props = node->stagingProperties(); // staging, since not sync'd yet
+ CanvasType canvas(props.getWidth(), props.getHeight());
canvasCallback(canvas);
node->setStagingDisplayList(canvas.finishRecording());
return node;
@@ -108,7 +117,7 @@ public:
node->syncDisplayList();
}
- typedef std::function<void(RenderState& state, Caches& caches)> RtCallback;
+ typedef std::function<void(renderthread::RenderThread& thread)> RtCallback;
class TestTask : public renderthread::RenderTask {
public:
@@ -120,7 +129,7 @@ public:
RenderState& renderState = renderthread::RenderThread::getInstance().renderState();
renderState.onGLContextCreated();
- rtCallback(renderState, Caches::getInstance());
+ rtCallback(renderthread::RenderThread::getInstance());
renderState.onGLContextDestroyed();
};
RtCallback rtCallback;
diff --git a/libs/hwui/utils/FatVector.h b/libs/hwui/utils/FatVector.h
index c3c16c5ae27d..315c24978a1f 100644
--- a/libs/hwui/utils/FatVector.h
+++ b/libs/hwui/utils/FatVector.h
@@ -91,6 +91,10 @@ public:
InlineStdAllocator<T, SIZE>(mAllocation)) {
this->reserve(SIZE);
}
+
+ FatVector(size_t capacity) : FatVector() {
+ this->resize(capacity);
+ }
private:
typename InlineStdAllocator<T, SIZE>::Allocation mAllocation;
};
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 875e7165fa81..50df55630a6a 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -3199,8 +3199,10 @@ public class AudioManager {
/**
* Returns the value of the property with the specified key.
* @param key One of the strings corresponding to a property key: either
- * {@link #PROPERTY_OUTPUT_SAMPLE_RATE} or
- * {@link #PROPERTY_OUTPUT_FRAMES_PER_BUFFER}
+ * {@link #PROPERTY_OUTPUT_SAMPLE_RATE},
+ * {@link #PROPERTY_OUTPUT_FRAMES_PER_BUFFER},
+ * {@link #PROPERTY_SUPPORT_MIC_NEAR_ULTRASOUND}, or
+ * {@link #PROPERTY_SUPPORT_SPEAKER_NEAR_ULTRASOUND}.
* @return A string representing the associated value for that property key,
* or null if there is no value for that key.
*/
diff --git a/packages/DocumentsUI/res/layout/fragment_directory.xml b/packages/DocumentsUI/res/layout/fragment_directory.xml
index 4019d02da330..ada7f49dbd1f 100644
--- a/packages/DocumentsUI/res/layout/fragment_directory.xml
+++ b/packages/DocumentsUI/res/layout/fragment_directory.xml
@@ -72,10 +72,10 @@
android:scrollbars="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:paddingStart="@dimen/grid_padding_horiz"
- android:paddingEnd="@dimen/grid_padding_horiz"
- android:paddingTop="@dimen/grid_padding_vert"
- android:paddingBottom="@dimen/grid_padding_vert"
+ android:paddingStart="0dp"
+ android:paddingEnd="0dp"
+ android:paddingTop="0dp"
+ android:paddingBottom="0dp"
android:clipToPadding="false"
android:scrollbarStyle="outsideOverlay"
android:drawSelectorOnTop="true"
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
index a09a22a19037..3b7da78ccb8d 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
@@ -66,7 +66,6 @@ import android.support.v7.widget.RecyclerView.LayoutManager;
import android.support.v7.widget.RecyclerView.OnItemTouchListener;
import android.support.v7.widget.RecyclerView.RecyclerListener;
import android.support.v7.widget.RecyclerView.ViewHolder;
-import android.support.v7.widget.SimpleItemAnimator;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.text.format.Formatter;
@@ -162,6 +161,9 @@ public class DirectoryFragment extends Fragment {
private MessageBar mMessageBar;
private View mProgressBar;
+ private int mSelectedItemColor;
+ private int mDefaultItemColor;
+
public static void showNormal(FragmentManager fm, RootInfo root, DocumentInfo doc, int anim) {
show(fm, TYPE_NORMAL, root, doc, null, anim);
}
@@ -254,8 +256,7 @@ public class DirectoryFragment extends Fragment {
}
});
- // TODO: Restore transition animations. See b/24802917.
- ((SimpleItemAnimator) mRecView.getItemAnimator()).setSupportsChangeAnimations(false);
+ mRecView.setItemAnimator(new DirectoryItemAnimator(getActivity()));
// TODO: Add a divider between views (which might use RecyclerView.ItemDecoration).
if (DEBUG_ENABLE_DND) {
@@ -293,6 +294,13 @@ public class DirectoryFragment extends Fragment {
mAdapter = new DocumentsAdapter(context);
mRecView.setAdapter(mAdapter);
+ mDefaultItemColor = context.getResources().getColor(android.R.color.transparent);
+ // Get the accent color.
+ TypedValue selColor = new TypedValue();
+ context.getTheme().resolveAttribute(android.R.attr.colorAccent, selColor, true);
+ // Set the opacity to 10%.
+ mSelectedItemColor = (selColor.data & 0x00ffffff) | 0x16000000;
+
GestureDetector.SimpleOnGestureListener listener =
new GestureDetector.SimpleOnGestureListener() {
@Override
@@ -669,7 +677,7 @@ public class DirectoryFragment extends Fragment {
getActivity().getWindow().setStatusBarColor(color.data);
if (mActionMode != null) {
- mActionMode.setTitle(TextUtils.formatSelectedCount(mSelected.size()));
+ mActionMode.setTitle(String.valueOf(mSelected.size()));
}
}
@@ -897,24 +905,26 @@ public class DirectoryFragment extends Fragment {
// Provide a reference to the views for each data item
// Complex data items may need more than one view per item, and
// you provide access to all the views for a data item in a view holder
- private static final class DocumentHolder
+ private final class DocumentHolder
extends RecyclerView.ViewHolder
implements View.OnKeyListener
{
- // each data item is just a string in this case
- public View view;
public String docId; // The stable document id.
private ClickListener mClickListener;
private View.OnKeyListener mKeyListener;
public DocumentHolder(View view) {
super(view);
- this.view = view;
// Setting this using android:focusable in the item layouts doesn't work for list items.
// So we set it here. Note that touch mode focus is a separate issue - see
// View.setFocusableInTouchMode and View.isInTouchMode for more info.
- this.view.setFocusable(true);
- this.view.setOnKeyListener(this);
+ view.setFocusable(true);
+ view.setOnKeyListener(this);
+ }
+
+ public void setSelected(boolean selected) {
+ itemView.setActivated(selected);
+ itemView.setBackgroundColor(selected ? mSelectedItemColor : mDefaultItemColor);
}
@Override
@@ -943,10 +953,10 @@ public class DirectoryFragment extends Fragment {
checkState(mKeyListener == null);
mKeyListener = listener;
}
+ }
- interface ClickListener {
- public void onClick(DocumentHolder doc);
- }
+ interface ClickListener {
+ public void onClick(DocumentHolder doc);
}
void showEmptyView() {
@@ -1005,6 +1015,24 @@ public class DirectoryFragment extends Fragment {
return holder;
}
+ /**
+ * Deal with selection changed events by using a custom ItemAnimator that just changes the
+ * background color. This works around focus issues (otherwise items lose focus when their
+ * selection state changes) but also optimizes change animations for selection.
+ */
+ @Override
+ public void onBindViewHolder(DocumentHolder holder, int position, List<Object> payload) {
+ final View itemView = holder.itemView;
+
+ if (payload.contains(MultiSelectManager.SELECTION_CHANGED_MARKER)) {
+ final boolean selected = isSelected(position);
+ itemView.setActivated(selected);
+ return;
+ } else {
+ onBindViewHolder(holder, position);
+ }
+ }
+
@Override
public void onBindViewHolder(DocumentHolder holder, int position) {
@@ -1030,8 +1058,9 @@ public class DirectoryFragment extends Fragment {
final long docSize = getCursorLong(cursor, Document.COLUMN_SIZE);
holder.docId = docId;
- final View itemView = holder.view;
- itemView.setActivated(isSelected(position));
+ final View itemView = holder.itemView;
+
+ holder.setSelected(isSelected(position));
final View line1 = itemView.findViewById(R.id.line1);
final View line2 = itemView.findViewById(R.id.line2);
@@ -1959,7 +1988,7 @@ public class DirectoryFragment extends Fragment {
}
}
- private class ItemClickListener implements DocumentHolder.ClickListener {
+ private class ItemClickListener implements ClickListener {
@Override
public void onClick(DocumentHolder doc) {
final int position = doc.getAdapterPosition();
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryItemAnimator.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryItemAnimator.java
new file mode 100644
index 000000000000..0eb1ea55895e
--- /dev/null
+++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryItemAnimator.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.documentsui;
+
+import android.animation.Animator;
+import android.animation.ArgbEvaluator;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.support.v4.util.ArrayMap;
+import android.support.v7.widget.DefaultItemAnimator;
+import android.support.v7.widget.RecyclerView;
+import android.util.TypedValue;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Performs change animations on Items in DirectoryFragment's RecyclerView. This class overrides
+ * the way selection animations are normally performed - instead of cross fading the old Item with a
+ * new Item, this class manually animates a background color change. This enables selected Items to
+ * correctly maintain focus.
+ */
+class DirectoryItemAnimator extends DefaultItemAnimator {
+ private final List<ColorAnimation> mPendingAnimations = new ArrayList<>();
+ private final Map<RecyclerView.ViewHolder, ColorAnimation> mRunningAnimations =
+ new ArrayMap<>();
+ private final Integer mDefaultColor;
+ private final Integer mSelectedColor;
+
+ public DirectoryItemAnimator(Context context) {
+ mDefaultColor = context.getResources().getColor(android.R.color.transparent);
+ // Get the accent color.
+ TypedValue selColor = new TypedValue();
+ context.getTheme().resolveAttribute(android.R.attr.colorAccent, selColor, true);
+ // Set the opacity to 10%.
+ mSelectedColor = (selColor.data & 0x00ffffff) | 0x16000000;
+ }
+
+ @Override
+ public void runPendingAnimations() {
+ super.runPendingAnimations();
+ for (ColorAnimation anim: mPendingAnimations) {
+ anim.start();
+ mRunningAnimations.put(anim.viewHolder, anim);
+ }
+ mPendingAnimations.clear();
+ }
+
+ @Override
+ public void endAnimation(RecyclerView.ViewHolder vh) {
+ super.endAnimation(vh);
+
+ for (int i = mPendingAnimations.size() - 1; i >= 0; --i) {
+ ColorAnimation anim = mPendingAnimations.get(i);
+ if (anim.viewHolder == vh) {
+ mPendingAnimations.remove(i);
+ anim.end();
+ }
+ }
+
+ ColorAnimation anim = mRunningAnimations.get(vh);
+ if (anim != null) {
+ anim.cancel();
+ }
+ }
+
+ @Override
+ public ItemHolderInfo recordPreLayoutInformation(
+ RecyclerView.State state,
+ RecyclerView.ViewHolder viewHolder,
+ @AdapterChanges int changeFlags,
+ List<Object> payloads) {
+ ItemInfo info = (ItemInfo) super.recordPreLayoutInformation(state,
+ viewHolder, changeFlags, payloads);
+ info.isActivated = viewHolder.itemView.isActivated();
+ return info;
+ }
+
+
+ @Override
+ public ItemHolderInfo recordPostLayoutInformation(
+ RecyclerView.State state, RecyclerView.ViewHolder viewHolder) {
+ ItemInfo info = (ItemInfo) super.recordPostLayoutInformation(state,
+ viewHolder);
+ info.isActivated = viewHolder.itemView.isActivated();
+ return info;
+ }
+
+ @Override
+ public boolean animateChange(final RecyclerView.ViewHolder oldHolder,
+ RecyclerView.ViewHolder newHolder, ItemHolderInfo preInfo,
+ ItemHolderInfo postInfo) {
+ if (oldHolder != newHolder) {
+ return super.animateChange(oldHolder, newHolder, preInfo, postInfo);
+ }
+
+ ItemInfo pre = (ItemInfo)preInfo;
+ ItemInfo post = (ItemInfo)postInfo;
+
+ if (pre.isActivated == post.isActivated) {
+ dispatchAnimationFinished(oldHolder);
+ return false;
+ } else {
+ Integer startColor = pre.isActivated ? mSelectedColor : mDefaultColor;
+ Integer endColor = post.isActivated ? mSelectedColor : mDefaultColor;
+ oldHolder.itemView.setBackgroundColor(startColor);
+ mPendingAnimations.add(new ColorAnimation(oldHolder, startColor, endColor));
+ }
+ return true;
+ }
+
+ @Override
+ public ItemHolderInfo obtainHolderInfo() {
+ return new ItemInfo();
+ }
+
+ @Override
+ public boolean canReuseUpdatedViewHolder(RecyclerView.ViewHolder vh) {
+ return true;
+ }
+
+ class ItemInfo extends DefaultItemAnimator.ItemHolderInfo {
+ boolean isActivated;
+ };
+
+ /**
+ * Animates changes in background color.
+ */
+ class ColorAnimation
+ implements ValueAnimator.AnimatorUpdateListener, Animator.AnimatorListener {
+ ValueAnimator mValueAnimator;
+ final RecyclerView.ViewHolder viewHolder;
+ int mEndColor;
+
+ public ColorAnimation(RecyclerView.ViewHolder vh, int startColor, int endColor)
+ {
+ viewHolder = vh;
+ mValueAnimator = ValueAnimator.ofObject(new ArgbEvaluator(), startColor, endColor);
+ mValueAnimator.addUpdateListener(this);
+ mValueAnimator.addListener(this);
+
+ mEndColor = endColor;
+ }
+
+ public void start() {
+ mValueAnimator.start();
+ }
+
+ public void cancel() {
+ mValueAnimator.cancel();
+ }
+
+ public void end() {
+ mValueAnimator.end();
+ }
+
+ @Override
+ public void onAnimationUpdate(ValueAnimator animator) {
+ viewHolder.itemView.setBackgroundColor((Integer)animator.getAnimatedValue());
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ viewHolder.itemView.setBackgroundColor(mEndColor);
+ mRunningAnimations.remove(viewHolder);
+ dispatchAnimationFinished(viewHolder);
+ }
+
+ @Override
+ public void onAnimationStart(Animator animation) {
+ dispatchAnimationStarted(viewHolder);
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {}
+
+ @Override
+ public void onAnimationRepeat(Animator animation) {}
+ };
+};
diff --git a/packages/DocumentsUI/src/com/android/documentsui/ManageRootActivity.java b/packages/DocumentsUI/src/com/android/documentsui/ManageRootActivity.java
index 14a33f9c1cc1..26a373403d80 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/ManageRootActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/ManageRootActivity.java
@@ -34,6 +34,7 @@ import android.provider.DocumentsContract;
import android.support.design.widget.Snackbar;
import android.util.Log;
import android.view.Menu;
+import android.view.MenuItem;
import android.view.View;
import android.widget.BaseAdapter;
import android.widget.Spinner;
@@ -49,7 +50,6 @@ import java.util.Arrays;
import java.util.List;
public class ManageRootActivity extends BaseActivity {
- private static final int CODE_FORWARD = 42;
private static final String TAG = "ManageRootsActivity";
private Toolbar mToolbar;
@@ -140,6 +140,21 @@ public class ManageRootActivity extends BaseActivity {
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
+
+ final MenuItem advanced = menu.findItem(R.id.menu_advanced);
+ final MenuItem createDir = menu.findItem(R.id.menu_create_dir);
+ final MenuItem newWindow = menu.findItem(R.id.menu_new_window);
+ final MenuItem pasteFromCb = menu.findItem(R.id.menu_paste_from_clipboard);
+ final MenuItem fileSize = menu.findItem(R.id.menu_file_size);
+ final MenuItem search = menu.findItem(R.id.menu_search);
+
+ advanced.setVisible(false);
+ createDir.setVisible(false);
+ pasteFromCb.setEnabled(false);
+ newWindow.setEnabled(false);
+ fileSize.setVisible(false);
+ search.setVisible(false);
+
Menus.disableHiddenItems(menu);
return true;
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/MultiSelectManager.java b/packages/DocumentsUI/src/com/android/documentsui/MultiSelectManager.java
index ef53d53c2f78..4fde6ff74fea 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/MultiSelectManager.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/MultiSelectManager.java
@@ -76,6 +76,9 @@ public final class MultiSelectManager implements View.OnKeyListener {
private Adapter<?> mAdapter;
private boolean mSingleSelect;
+ // Payloads for notifyItemChange to distinguish between selection and other events.
+ public static final String SELECTION_CHANGED_MARKER = "Selection-Changed";
+
@Nullable private BandController mBandManager;
/**
@@ -460,7 +463,7 @@ public final class MultiSelectManager implements View.OnKeyListener {
for (int i = lastListener; i > -1; i--) {
mCallbacks.get(i).onItemStateChanged(position, selected);
}
- mAdapter.notifyItemChanged(position);
+ mAdapter.notifyItemChanged(position, SELECTION_CHANGED_MARKER);
}
/**
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
index d75b6fdf77bc..beff196509b2 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
@@ -16,6 +16,8 @@
package com.android.documentsui;
+import static com.android.documentsui.Shared.DEBUG;
+
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
@@ -30,6 +32,7 @@ import android.os.Bundle;
import android.provider.Settings;
import android.text.TextUtils;
import android.text.format.Formatter;
+import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -56,12 +59,13 @@ import java.util.Objects;
*/
public class RootsFragment extends Fragment {
+ private static final String TAG = "RootsFragment";
+ private static final String EXTRA_INCLUDE_APPS = "includeApps";
+
private ListView mList;
private RootsAdapter mAdapter;
-
private LoaderCallbacks<Collection<RootInfo>> mCallbacks;
- private static final String EXTRA_INCLUDE_APPS = "includeApps";
public static void show(FragmentManager fm, Intent includeApps) {
final Bundle args = new Bundle();
@@ -180,6 +184,8 @@ public class RootsFragment extends Fragment {
} else if (item instanceof AppItem) {
DocumentsActivity activity = DocumentsActivity.get(RootsFragment.this);
activity.onAppPicked(((AppItem) item).info);
+ } else if (item instanceof SpacerItem) {
+ if (DEBUG) Log.d(TAG, "Ignoring click on spacer item.");
} else {
throw new IllegalStateException("Unknown root: " + item);
}
diff --git a/packages/DocumentsUI/tests/Android.mk b/packages/DocumentsUI/tests/Android.mk
index 2a540d4d77e9..b65ac98d0193 100644
--- a/packages/DocumentsUI/tests/Android.mk
+++ b/packages/DocumentsUI/tests/Android.mk
@@ -8,7 +8,7 @@ LOCAL_MODULE_TAGS := tests
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_JAVA_LIBRARIES := android.test.runner
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4 mockito-target guava ub-uiautomator
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4 mockito-target ub-uiautomator
LOCAL_PACKAGE_NAME := DocumentsUITests
LOCAL_INSTRUMENTATION_FOR := DocumentsUI
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/CopyTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/CopyTest.java
index fc42c3b12545..6f1a89baf525 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/CopyTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/CopyTest.java
@@ -28,6 +28,7 @@ import android.net.Uri;
import android.os.Parcelable;
import android.os.RemoteException;
import android.provider.DocumentsContract;
+import android.provider.DocumentsContract.Document;
import android.test.MoreAsserts;
import android.test.ServiceTestCase;
import android.test.mock.MockContentResolver;
@@ -36,6 +37,7 @@ import android.util.Log;
import com.android.documentsui.model.DocumentInfo;
import com.android.documentsui.model.DocumentStack;
import com.android.documentsui.model.RootInfo;
+
import com.google.common.collect.Lists;
import libcore.io.IoUtils;
@@ -52,70 +54,19 @@ import java.util.concurrent.TimeoutException;
public class CopyTest extends ServiceTestCase<CopyService> {
- /**
- * A test resolver that enables this test suite to listen for notifications that mark when copy
- * operations are done.
- */
- class TestContentResolver extends MockContentResolver {
- private CountDownLatch mReadySignal;
- private CountDownLatch mNotificationSignal;
-
- public TestContentResolver() {
- mReadySignal = new CountDownLatch(1);
- }
-
- /**
- * Wait for the given number of files to be copied to destination. Times out after 1 sec.
- */
- public void waitForChanges(int count) throws Exception {
- // Wait for no more than 1 second by default.
- waitForChanges(count, 1000);
- }
-
- /**
- * Wait for files to be copied to destination.
- *
- * @param count Number of files to wait for.
- * @param timeOut Timeout in ms. TimeoutException will be thrown if this function times out.
- */
- public void waitForChanges(int count, int timeOut) throws Exception {
- mNotificationSignal = new CountDownLatch(count);
- // Signal that the test is now waiting for files.
- mReadySignal.countDown();
- if (!mNotificationSignal.await(timeOut, TimeUnit.MILLISECONDS)) {
- throw new TimeoutException("Timed out waiting for file operations to complete.");
- }
- }
-
- @Override
- public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork) {
- // Wait until the test is ready to receive file notifications.
- try {
- mReadySignal.await();
- } catch (InterruptedException e) {
- Log.d(TAG, "Interrupted while waiting for file copy readiness");
- Thread.currentThread().interrupt();
- }
- if (DocumentsContract.isDocumentUri(mContext, uri)) {
- Log.d(TAG, "Notification: " + uri);
- // Watch for document URI change notifications - this signifies the end of a copy.
- mNotificationSignal.countDown();
- }
- }
- };
-
public CopyTest() {
super(CopyService.class);
}
private static String AUTHORITY = "com.android.documentsui.stubprovider";
- private static String DST = "sd1";
- private static String SRC = "sd0";
+ private static String SRC_ROOT = StubProvider.ROOT_0_ID;
+ private static String DST_ROOT = StubProvider.ROOT_1_ID;
private static String TAG = "CopyTest";
- private List<RootInfo> mRoots;
+
private Context mContext;
private TestContentResolver mResolver;
private ContentProviderClient mClient;
+ private DocumentsProviderHelper mDocHelper;
private StubProvider mStorage;
private Context mSystemContext;
@@ -129,18 +80,7 @@ public class CopyTest extends ServiceTestCase<CopyService> {
// Reset the stub provider's storage.
mStorage.clearCacheAndBuildRoots();
- mRoots = Lists.newArrayList();
- Uri queryUri = DocumentsContract.buildRootsUri(AUTHORITY);
- Cursor cursor = null;
- try {
- cursor = mClient.query(queryUri, null, null, null, null);
- while (cursor.moveToNext()) {
- mRoots.add(RootInfo.fromRootsCursor(AUTHORITY, cursor));
- }
- } finally {
- IoUtils.closeQuietly(cursor);
- }
-
+ mDocHelper = new DocumentsProviderHelper(AUTHORITY, mClient);
}
@Override
@@ -154,7 +94,7 @@ public class CopyTest extends ServiceTestCase<CopyService> {
*/
public void testCopyFile() throws Exception {
String srcPath = "/test0.txt";
- Uri testFile = mStorage.createFile(SRC, srcPath, "text/plain",
+ Uri testFile = mStorage.createFile(SRC_ROOT, srcPath, "text/plain",
"The five boxing wizards jump quickly".getBytes());
assertDstFileCountEquals(0);
@@ -172,7 +112,7 @@ public class CopyTest extends ServiceTestCase<CopyService> {
public void testMoveFile() throws Exception {
String srcPath = "/test0.txt";
String testContent = "The five boxing wizards jump quickly";
- Uri testFile = mStorage.createFile(SRC, srcPath, "text/plain", testContent.getBytes());
+ Uri testFile = mStorage.createFile(SRC_ROOT, srcPath, "text/plain", testContent.getBytes());
assertDstFileCountEquals(0);
@@ -185,9 +125,9 @@ public class CopyTest extends ServiceTestCase<CopyService> {
// Verify that one file was moved; check file contents.
assertDstFileCountEquals(1);
- assertDoesNotExist(SRC, srcPath);
+ assertDoesNotExist(SRC_ROOT, srcPath);
- byte[] dstContent = readFile(DST, srcPath);
+ byte[] dstContent = readFile(DST_ROOT, srcPath);
MoreAsserts.assertEquals("Moved file contents differ", testContent.getBytes(), dstContent);
}
@@ -206,9 +146,9 @@ public class CopyTest extends ServiceTestCase<CopyService> {
"/test2.txt"
};
List<Uri> testFiles = Lists.newArrayList(
- mStorage.createFile(SRC, srcPaths[0], "text/plain", testContent[0].getBytes()),
- mStorage.createFile(SRC, srcPaths[1], "text/plain", testContent[1].getBytes()),
- mStorage.createFile(SRC, srcPaths[2], "text/plain", testContent[2].getBytes()));
+ mStorage.createFile(SRC_ROOT, srcPaths[0], "text/plain", testContent[0].getBytes()),
+ mStorage.createFile(SRC_ROOT, srcPaths[1], "text/plain", testContent[1].getBytes()),
+ mStorage.createFile(SRC_ROOT, srcPaths[2], "text/plain", testContent[2].getBytes()));
assertDstFileCountEquals(0);
@@ -226,7 +166,7 @@ public class CopyTest extends ServiceTestCase<CopyService> {
public void testCopyEmptyDir() throws Exception {
String srcPath = "/emptyDir";
- Uri testDir = mStorage.createFile(SRC, srcPath, DocumentsContract.Document.MIME_TYPE_DIR,
+ Uri testDir = mStorage.createFile(SRC_ROOT, srcPath, DocumentsContract.Document.MIME_TYPE_DIR,
null);
assertDstFileCountEquals(0);
@@ -239,13 +179,13 @@ public class CopyTest extends ServiceTestCase<CopyService> {
assertDstFileCountEquals(1);
// Verify that the dst exists and is a directory.
- File dst = mStorage.getFile(DST, srcPath);
+ File dst = mStorage.getFile(DST_ROOT, srcPath);
assertTrue(dst.isDirectory());
}
public void testMoveEmptyDir() throws Exception {
String srcPath = "/emptyDir";
- Uri testDir = mStorage.createFile(SRC, srcPath, DocumentsContract.Document.MIME_TYPE_DIR,
+ Uri testDir = mStorage.createFile(SRC_ROOT, srcPath, DocumentsContract.Document.MIME_TYPE_DIR,
null);
assertDstFileCountEquals(0);
@@ -260,11 +200,11 @@ public class CopyTest extends ServiceTestCase<CopyService> {
assertDstFileCountEquals(1);
// Verify that the dst exists and is a directory.
- File dst = mStorage.getFile(DST, srcPath);
+ File dst = mStorage.getFile(DST_ROOT, srcPath);
assertTrue(dst.isDirectory());
// Verify that the src was cleaned up.
- assertDoesNotExist(SRC, srcPath);
+ assertDoesNotExist(SRC_ROOT, srcPath);
}
public void testMovePopulatedDir() throws Exception {
@@ -280,11 +220,11 @@ public class CopyTest extends ServiceTestCase<CopyService> {
srcDir + "/test2.txt"
};
// Create test dir; put some files in it.
- Uri testDir = mStorage.createFile(SRC, srcDir, DocumentsContract.Document.MIME_TYPE_DIR,
+ Uri testDir = mStorage.createFile(SRC_ROOT, srcDir, DocumentsContract.Document.MIME_TYPE_DIR,
null);
- mStorage.createFile(SRC, srcFiles[0], "text/plain", testContent[0].getBytes());
- mStorage.createFile(SRC, srcFiles[1], "text/plain", testContent[1].getBytes());
- mStorage.createFile(SRC, srcFiles[2], "text/plain", testContent[2].getBytes());
+ mStorage.createFile(SRC_ROOT, srcFiles[0], "text/plain", testContent[0].getBytes());
+ mStorage.createFile(SRC_ROOT, srcFiles[1], "text/plain", testContent[1].getBytes());
+ mStorage.createFile(SRC_ROOT, srcFiles[2], "text/plain", testContent[2].getBytes());
Intent moveIntent = createCopyIntent(Lists.newArrayList(testDir));
moveIntent.putExtra(CopyService.EXTRA_TRANSFER_MODE, CopyService.TRANSFER_MODE_MOVE);
@@ -295,24 +235,24 @@ public class CopyTest extends ServiceTestCase<CopyService> {
mResolver.waitForChanges(11);
// Check the content of the moved files.
- File dst = mStorage.getFile(DST, srcDir);
+ File dst = mStorage.getFile(DST_ROOT, srcDir);
assertTrue(dst.isDirectory());
for (int i = 0; i < testContent.length; ++i) {
- byte[] dstContent = readFile(DST, srcFiles[i]);
+ byte[] dstContent = readFile(DST_ROOT, srcFiles[i]);
MoreAsserts.assertEquals("Copied file contents differ", testContent[i].getBytes(),
dstContent);
}
// Check that the src files were removed.
- assertDoesNotExist(SRC, srcDir);
+ assertDoesNotExist(SRC_ROOT, srcDir);
for (String srcFile : srcFiles) {
- assertDoesNotExist(SRC, srcFile);
+ assertDoesNotExist(SRC_ROOT, srcFile);
}
}
public void testCopyFileWithReadErrors() throws Exception {
String srcPath = "/test0.txt";
- Uri testFile = mStorage.createFile(SRC, srcPath, "text/plain",
+ Uri testFile = mStorage.createFile(SRC_ROOT, srcPath, "text/plain",
"The five boxing wizards jump quickly".getBytes());
assertDstFileCountEquals(0);
@@ -330,7 +270,7 @@ public class CopyTest extends ServiceTestCase<CopyService> {
public void testMoveFileWithReadErrors() throws Exception {
String srcPath = "/test0.txt";
- Uri testFile = mStorage.createFile(SRC, srcPath, "text/plain",
+ Uri testFile = mStorage.createFile(SRC_ROOT, srcPath, "text/plain",
"The five boxing wizards jump quickly".getBytes());
assertDstFileCountEquals(0);
@@ -352,7 +292,7 @@ public class CopyTest extends ServiceTestCase<CopyService> {
} finally {
// Verify that the failed copy was cleaned up, and the src file wasn't removed.
assertDstFileCountEquals(0);
- assertExists(SRC, srcPath);
+ assertExists(SRC_ROOT, srcPath);
}
// The asserts above didn't fail, but the CopyService did something unexpected.
fail("Extra file operations were detected");
@@ -371,12 +311,12 @@ public class CopyTest extends ServiceTestCase<CopyService> {
srcDir + "/test2.txt"
};
// Create test dir; put some files in it.
- Uri testDir = mStorage.createFile(SRC, srcDir, DocumentsContract.Document.MIME_TYPE_DIR,
+ Uri testDir = mStorage.createFile(SRC_ROOT, srcDir, DocumentsContract.Document.MIME_TYPE_DIR,
null);
- mStorage.createFile(SRC, srcFiles[0], "text/plain", testContent[0].getBytes());
+ mStorage.createFile(SRC_ROOT, srcFiles[0], "text/plain", testContent[0].getBytes());
Uri errFile = mStorage
- .createFile(SRC, srcFiles[1], "text/plain", testContent[1].getBytes());
- mStorage.createFile(SRC, srcFiles[2], "text/plain", testContent[2].getBytes());
+ .createFile(SRC_ROOT, srcFiles[1], "text/plain", testContent[1].getBytes());
+ mStorage.createFile(SRC_ROOT, srcFiles[2], "text/plain", testContent[2].getBytes());
mStorage.simulateReadErrorsForFile(errFile);
@@ -391,22 +331,22 @@ public class CopyTest extends ServiceTestCase<CopyService> {
// Check that both the src and dst dirs exist. The src dir shouldn't have been removed,
// because it should contain the one errFile.
- assertTrue(mStorage.getFile(SRC, srcDir).isDirectory());
- assertTrue(mStorage.getFile(DST, srcDir).isDirectory());
+ assertTrue(mStorage.getFile(SRC_ROOT, srcDir).isDirectory());
+ assertTrue(mStorage.getFile(DST_ROOT, srcDir).isDirectory());
// Check the content of the moved files.
MoreAsserts.assertEquals("Copied file contents differ", testContent[0].getBytes(),
- readFile(DST, srcFiles[0]));
+ readFile(DST_ROOT, srcFiles[0]));
MoreAsserts.assertEquals("Copied file contents differ", testContent[2].getBytes(),
- readFile(DST, srcFiles[2]));
+ readFile(DST_ROOT, srcFiles[2]));
// Check that the src files were removed.
- assertDoesNotExist(SRC, srcFiles[0]);
- assertDoesNotExist(SRC, srcFiles[2]);
+ assertDoesNotExist(SRC_ROOT, srcFiles[0]);
+ assertDoesNotExist(SRC_ROOT, srcFiles[2]);
// Check that the error file was not copied over.
- assertDoesNotExist(DST, srcFiles[1]);
- assertExists(SRC, srcFiles[1]);
+ assertDoesNotExist(DST_ROOT, srcFiles[1]);
+ assertExists(SRC_ROOT, srcFiles[1]);
}
/**
@@ -414,13 +354,14 @@ public class CopyTest extends ServiceTestCase<CopyService> {
*
* @throws FileNotFoundException
*/
- private Intent createCopyIntent(List<Uri> srcs) throws FileNotFoundException {
+ private Intent createCopyIntent(List<Uri> srcs) throws Exception {
final ArrayList<DocumentInfo> srcDocs = Lists.newArrayList();
for (Uri src : srcs) {
srcDocs.add(DocumentInfo.fromUri(mResolver, src));
}
- final Uri dst = DocumentsContract.buildDocumentUri(AUTHORITY, mRoots.get(1).documentId);
+ RootInfo root = mDocHelper.getRoot(DST_ROOT);
+ final Uri dst = DocumentsContract.buildDocumentUri(AUTHORITY, root.documentId);
DocumentStack stack = new DocumentStack();
stack.push(DocumentInfo.fromUri(mResolver, dst));
final Intent copyIntent = new Intent(mContext, CopyService.class);
@@ -435,8 +376,9 @@ public class CopyTest extends ServiceTestCase<CopyService> {
* Returns a count of the files in the given directory.
*/
private void assertDstFileCountEquals(int expected) throws RemoteException {
+ RootInfo dest = mDocHelper.getRoot(DST_ROOT);
final Uri queryUri = DocumentsContract.buildChildDocumentsUri(AUTHORITY,
- mRoots.get(1).documentId);
+ dest.documentId);
Cursor c = null;
int count = 0;
try {
@@ -474,8 +416,8 @@ public class CopyTest extends ServiceTestCase<CopyService> {
}
private void assertCopied(String path) throws Exception {
- MoreAsserts.assertEquals("Copied file contents differ", readFile(SRC, path),
- readFile(DST, path));
+ MoreAsserts.assertEquals("Copied file contents differ", readFile(SRC_ROOT, path),
+ readFile(DST_ROOT, path));
}
/**
@@ -509,4 +451,56 @@ public class CopyTest extends ServiceTestCase<CopyService> {
mStorage.attachInfo(mContext, info);
mResolver.addProvider(AUTHORITY, mStorage);
}
+
+ /**
+ * A test resolver that enables this test suite to listen for notifications that mark when copy
+ * operations are done.
+ */
+ class TestContentResolver extends MockContentResolver {
+ private CountDownLatch mReadySignal;
+ private CountDownLatch mNotificationSignal;
+
+ public TestContentResolver() {
+ mReadySignal = new CountDownLatch(1);
+ }
+
+ /**
+ * Wait for the given number of files to be copied to destination. Times out after 1 sec.
+ */
+ public void waitForChanges(int count) throws Exception {
+ // Wait for no more than 1 second by default.
+ waitForChanges(count, 1000);
+ }
+
+ /**
+ * Wait for files to be copied to destination.
+ *
+ * @param count Number of files to wait for.
+ * @param timeOut Timeout in ms. TimeoutException will be thrown if this function times out.
+ */
+ public void waitForChanges(int count, int timeOut) throws Exception {
+ mNotificationSignal = new CountDownLatch(count);
+ // Signal that the test is now waiting for files.
+ mReadySignal.countDown();
+ if (!mNotificationSignal.await(timeOut, TimeUnit.MILLISECONDS)) {
+ throw new TimeoutException("Timed out waiting for file operations to complete.");
+ }
+ }
+
+ @Override
+ public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork) {
+ // Wait until the test is ready to receive file notifications.
+ try {
+ mReadySignal.await();
+ } catch (InterruptedException e) {
+ Log.d(TAG, "Interrupted while waiting for file copy readiness");
+ Thread.currentThread().interrupt();
+ }
+ if (DocumentsContract.isDocumentUri(mContext, uri)) {
+ Log.d(TAG, "Notification: " + uri);
+ // Watch for document URI change notifications - this signifies the end of a copy.
+ mNotificationSignal.countDown();
+ }
+ }
+ };
}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/DocumentsProviderHelper.java b/packages/DocumentsUI/tests/src/com/android/documentsui/DocumentsProviderHelper.java
new file mode 100644
index 000000000000..7abc99c99a5a
--- /dev/null
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/DocumentsProviderHelper.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.documentsui;
+
+import static com.android.documentsui.model.DocumentInfo.getCursorString;
+
+import android.content.ContentProviderClient;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.RemoteException;
+import android.provider.DocumentsContract;
+import android.provider.DocumentsContract.Document;
+import android.provider.DocumentsContract.Root;
+
+import com.android.documentsui.model.RootInfo;
+
+import libcore.io.IoUtils;
+
+/**
+ * Provides support for creation of documents in a test settings.
+ */
+public class DocumentsProviderHelper {
+
+ private final ContentProviderClient mClient;
+ private final String mAuthority;
+
+ public DocumentsProviderHelper(String authority, ContentProviderClient client) {
+ mClient = client;
+ mAuthority = authority;
+ }
+
+ public RootInfo getRoot(String id) throws RemoteException {
+ final Uri rootsUri = DocumentsContract.buildRootsUri(mAuthority);
+
+ Cursor cursor = null;
+ try {
+ cursor = mClient.query(rootsUri, null, null, null, null);
+ while (cursor.moveToNext()) {
+ if (id.equals(getCursorString(cursor, Root.COLUMN_ROOT_ID))) {
+ return RootInfo.fromRootsCursor(mAuthority, cursor);
+ }
+ }
+ throw new IllegalArgumentException("Can't find matching root for id=" + id);
+ } catch (Exception e) {
+ throw new RuntimeException("Can't load root for id=" + id , e);
+ } finally {
+ IoUtils.closeQuietly(cursor);
+ }
+ }
+
+ public Uri createDocument(Uri parentUri, String mimeType, String name) {
+ if (name.contains("/")) {
+ throw new IllegalArgumentException("Name and mimetype probably interposed.");
+ }
+ try {
+ return DocumentsContract.createDocument(mClient, parentUri, mimeType, name);
+ } catch (RemoteException e) {
+ throw new RuntimeException("Couldn't create document: " + name + " with mimetype " + mimeType, e);
+ }
+ }
+
+ public Uri createFolder(Uri parentUri, String name) {
+ return createDocument(parentUri, Document.MIME_TYPE_DIR, name);
+ }
+
+ public Uri createDocument(RootInfo root, String mimeType, String name) {
+ Uri rootUri = DocumentsContract.buildDocumentUri(mAuthority, root.documentId);
+ return createDocument(rootUri, mimeType, name);
+ }
+
+ public Uri createFolder(RootInfo root, String name) {
+ return createDocument(root, Document.MIME_TYPE_DIR, name);
+ }
+}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
index 1f4b751abc85..906051640c6a 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
@@ -16,83 +16,158 @@
package com.android.documentsui;
+import static com.android.documentsui.StubProvider.DEFAULT_AUTHORITY;
+import static com.android.documentsui.StubProvider.ROOT_0_ID;
+import static com.android.documentsui.StubProvider.ROOT_1_ID;
+
+import android.app.Instrumentation;
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
+import android.os.RemoteException;
import android.support.test.uiautomator.By;
-import android.support.test.uiautomator.BySelector;
import android.support.test.uiautomator.Configurator;
import android.support.test.uiautomator.UiDevice;
-import android.support.test.uiautomator.UiObject2;
import android.support.test.uiautomator.Until;
import android.test.InstrumentationTestCase;
+import android.util.Log;
import android.view.MotionEvent;
-import java.util.concurrent.TimeoutException;
+import com.android.documentsui.model.RootInfo;
public class FilesActivityUiTest extends InstrumentationTestCase {
+ private static final int TIMEOUT = 5000;
private static final String TAG = "FilesActivityUiTest";
private static final String TARGET_PKG = "com.android.documentsui";
private static final String LAUNCHER_PKG = "com.android.launcher";
- private static final int ONE_SECOND = 1000;
- private static final int FIVE_SECONDS = 5 * ONE_SECOND;
- private ActionBar mBar;
+ private UiBot mBot;
private UiDevice mDevice;
private Context mContext;
+ private ContentResolver mResolver;
+ private DocumentsProviderHelper mDocsHelper;
+ private ContentProviderClient mClient;
+ private RootInfo mRoot_0;
+ private RootInfo mRoot_1;
- public void setUp() throws TimeoutException {
+ public void setUp() throws Exception {
// Initialize UiDevice instance.
- mDevice = UiDevice.getInstance(getInstrumentation());
+ Instrumentation instrumentation = getInstrumentation();
+
+ mDevice = UiDevice.getInstance(instrumentation);
Configurator.getInstance().setToolType(MotionEvent.TOOL_TYPE_MOUSE);
// Start from the home screen.
mDevice.pressHome();
- mDevice.wait(Until.hasObject(By.pkg(LAUNCHER_PKG).depth(0)), FIVE_SECONDS);
+ mDevice.wait(Until.hasObject(By.pkg(LAUNCHER_PKG).depth(0)), TIMEOUT);
+
+ // NOTE: Must be the "target" context, else security checks in content provider will fail.
+ mContext = instrumentation.getTargetContext();
+ mResolver = mContext.getContentResolver();
+
+ mClient = mResolver.acquireUnstableContentProviderClient(DEFAULT_AUTHORITY);
+ mDocsHelper = new DocumentsProviderHelper(DEFAULT_AUTHORITY, mClient);
// Launch app.
- mContext = getInstrumentation().getContext();
Intent intent = mContext.getPackageManager().getLaunchIntentForPackage(TARGET_PKG);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
mContext.startActivity(intent);
// Wait for the app to appear.
- mDevice.wait(Until.hasObject(By.pkg(TARGET_PKG).depth(0)), FIVE_SECONDS);
+ mDevice.wait(Until.hasObject(By.pkg(TARGET_PKG).depth(0)), TIMEOUT);
+ mDevice.waitForIdle();
+
+ mBot = new UiBot(mDevice, TIMEOUT);
+
+ resetStorage(); // Just incase a test failed and tearDown didn't happen.
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ Log.d(TAG, "Resetting storage from setUp");
+ resetStorage();
+ mClient.release();
+ }
+
+ private void resetStorage() throws RemoteException {
+ mClient.call("clear", null, null);
+ // TODO: Would be nice to have an event to wait on here.
mDevice.waitForIdle();
+ }
+
+ private void initTestFiles() throws RemoteException {
+ mRoot_0 = mDocsHelper.getRoot(ROOT_0_ID);
+ mRoot_1 = mDocsHelper.getRoot(ROOT_1_ID);
+
+ mDocsHelper.createDocument(mRoot_0, "text/plain", "file0.log");
+ mDocsHelper.createDocument(mRoot_0, "image/png", "file1.png");
+ mDocsHelper.createDocument(mRoot_0, "text/csv", "file2.csv");
+
+ mDocsHelper.createDocument(mRoot_1, "text/plain", "anotherFile0.log");
+ mDocsHelper.createDocument(mRoot_1, "text/plain", "poodles.text");
+ }
- mBar = new ActionBar();
+ public void testRootsListed() throws Exception {
+ initTestFiles();
+
+ mBot.openRoot(ROOT_0_ID);
+
+ // Should also have Drive, but that requires pre-configuration of devices
+ // We omit for now.
+ mBot.assertHasRoots(
+ "Images",
+ "Videos",
+ "Audio",
+ "Downloads",
+ ROOT_0_ID,
+ ROOT_1_ID);
+ }
+
+ public void testFilesListed() throws Exception {
+ initTestFiles();
+
+ mBot.openRoot(ROOT_0_ID);
+ mBot.assertHasDocuments("file0.log", "file1.png", "file2.csv");
}
- public void testSwitchMode() throws Exception {
- UiObject2 mode = mBar.gridMode(100);
- if (mode != null) {
- mode.click();
- assertNotNull(mBar.listMode(ONE_SECOND));
- } else {
- mBar.listMode(100).click();
- assertNotNull(mBar.gridMode(ONE_SECOND));
- }
+ public void testFilesList_LiveUpdate() throws Exception {
+ initTestFiles();
+
+ mBot.openRoot(ROOT_0_ID);
+ mDocsHelper.createDocument(mRoot_0, "yummers/sandwich", "Ham & Cheese.sandwich");
+ mBot.assertHasDocuments("file0.log", "file1.png", "file2.csv", "Ham & Cheese.sandwich");
}
- private class ActionBar {
-
- public UiObject2 gridMode(int timeout) {
- // Note that we're using By.desc rather than By.res, because of b/25285770
- BySelector selector = By.desc("Grid view");
- if (timeout > 0) {
- mDevice.wait(Until.findObject(selector), timeout);
- }
- return mDevice.findObject(selector);
- }
-
- public UiObject2 listMode(int timeout) {
- // Note that we're using By.desc rather than By.res, because of b/25285770
- BySelector selector = By.desc("List view");
- if (timeout > 0) {
- mDevice.wait(Until.findObject(selector), timeout);
- }
- return mDevice.findObject(selector);
- }
+ public void testDeleteDocument() throws Exception {
+ initTestFiles();
+
+ mBot.openRoot(ROOT_0_ID);
+
+ mBot.clickDocument("file1.png");
+ mDevice.waitForIdle();
+ mBot.menuDelete().click();
+
+ mBot.waitForDeleteSnackbar();
+ assertFalse(mBot.hasDocuments("file1.png"));
+
+ mBot.waitForDeleteSnackbarGone();
+ assertFalse(mBot.hasDocuments("file1.png"));
+
+ // Now delete from another root.
+ mBot.openRoot(ROOT_1_ID);
+
+ mBot.clickDocument("poodles.text");
+ mDevice.waitForIdle();
+ mBot.menuDelete().click();
+
+ mBot.waitForDeleteSnackbar();
+ assertFalse(mBot.hasDocuments("poodles.text"));
+
+ mBot.waitForDeleteSnackbarGone();
+ assertFalse(mBot.hasDocuments("poodles.text"));
}
}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java b/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java
index 6a2e03a3b809..2d42ddc4e6b5 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java
@@ -50,12 +50,17 @@ import java.util.HashMap;
import java.util.Map;
public class StubProvider extends DocumentsProvider {
+
+ public static final String DEFAULT_AUTHORITY = "com.android.documentsui.stubprovider";
+ public static final String ROOT_0_ID = "TEST_ROOT_0";
+ public static final String ROOT_1_ID = "TEST_ROOT_1";
+
+ private static final String TAG = "StubProvider";
private static final String EXTRA_SIZE = "com.android.documentsui.stubprovider.SIZE";
private static final String EXTRA_ROOT = "com.android.documentsui.stubprovider.ROOT";
private static final String STORAGE_SIZE_KEY = "documentsui.stubprovider.size";
- private static int DEFAULT_SIZE = 1024 * 1024; // 1 MB.
- private static final String TAG = "StubProvider";
- private static final String MY_ROOT_ID = "sd0";
+ private static int DEFAULT_ROOT_SIZE = 1024 * 1024 * 100; // 100 MB.
+
private static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_TITLE, Root.COLUMN_DOCUMENT_ID,
Root.COLUMN_AVAILABLE_BYTES
@@ -65,11 +70,12 @@ public class StubProvider extends DocumentsProvider {
Document.COLUMN_LAST_MODIFIED, Document.COLUMN_FLAGS, Document.COLUMN_SIZE,
};
- private HashMap<String, StubDocument> mStorage = new HashMap<String, StubDocument>();
- private Object mWriteLock = new Object();
- private String mAuthority;
+ private final Map<String, StubDocument> mStorage = new HashMap<>();
+ private final Map<String, RootInfo> mRoots = new HashMap<>();
+ private final Object mWriteLock = new Object();
+
+ private String mAuthority = DEFAULT_AUTHORITY;
private SharedPreferences mPrefs;
- private Map<String, RootInfo> mRoots;
private String mSimulateReadErrors;
@Override
@@ -86,20 +92,18 @@ public class StubProvider extends DocumentsProvider {
@VisibleForTesting
public void clearCacheAndBuildRoots() {
- final File cacheDir = getContext().getCacheDir();
- removeRecursively(cacheDir);
+ Log.d(TAG, "Resetting storage.");
+ removeChildrenRecursively(getContext().getCacheDir());
mStorage.clear();
mPrefs = getContext().getSharedPreferences(
"com.android.documentsui.stubprovider.preferences", Context.MODE_PRIVATE);
Collection<String> rootIds = mPrefs.getStringSet("roots", null);
if (rootIds == null) {
- rootIds = Arrays.asList(new String[] {
- "sd0", "sd1"
- });
+ rootIds = Arrays.asList(new String[] { ROOT_0_ID, ROOT_1_ID });
}
- // Create new roots.
- mRoots = new HashMap<>();
+
+ mRoots.clear();
for (String rootId : rootIds) {
final RootInfo rootInfo = new RootInfo(rootId, getSize(rootId));
mRoots.put(rootId, rootInfo);
@@ -111,7 +115,7 @@ public class StubProvider extends DocumentsProvider {
*/
private long getSize(String rootId) {
final String key = STORAGE_SIZE_KEY + "." + rootId;
- return mPrefs.getLong(key, DEFAULT_SIZE);
+ return mPrefs.getLong(key, DEFAULT_ROOT_SIZE);
}
@Override
@@ -125,7 +129,7 @@ public class StubProvider extends DocumentsProvider {
row.add(Root.COLUMN_ROOT_ID, id);
row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_IS_CHILD);
row.add(Root.COLUMN_TITLE, id);
- row.add(Root.COLUMN_DOCUMENT_ID, info.rootDocument.documentId);
+ row.add(Root.COLUMN_DOCUMENT_ID, info.document.documentId);
row.add(Root.COLUMN_AVAILABLE_BYTES, info.getRemainingCapacity());
}
return result;
@@ -152,28 +156,48 @@ public class StubProvider extends DocumentsProvider {
}
@Override
- public String createDocument(String parentDocumentId, String mimeType, String displayName)
+ public String createDocument(String parentId, String mimeType, String displayName)
throws FileNotFoundException {
- final StubDocument parentDocument = mStorage.get(parentDocumentId);
- if (parentDocument == null || !parentDocument.file.isDirectory()) {
- throw new FileNotFoundException();
+
+ final StubDocument parent = mStorage.get(parentId);
+ if (parent == null) {
+ throw new IllegalArgumentException(
+ "Can't create file " + displayName + " in null parent.");
+ }
+ if (!parent.file.isDirectory()) {
+ throw new IllegalArgumentException(
+ "Can't create file " + displayName + " inside non-directory parent "
+ + parent.file.getName());
}
- final File file = new File(parentDocument.file, displayName);
+
+ final File file = new File(parent.file, displayName);
+ if (file.exists()) {
+ throw new FileNotFoundException(
+ "Duplicate file names not supported for " + file);
+ }
+
if (mimeType.equals(Document.MIME_TYPE_DIR)) {
if (!file.mkdirs()) {
- throw new FileNotFoundException();
+ throw new FileNotFoundException(
+ "Failed to create directory(s): " + file);
}
+ Log.i(TAG, "Created new directory: " + file);
} else {
+ boolean created = false;
try {
- if (!file.createNewFile()) {
- throw new IllegalStateException("The file " + file.getPath() + " already exists");
- }
+ created = file.createNewFile();
} catch (IOException e) {
- throw new FileNotFoundException();
+ // We'll throw an FNF exception later :)
+ Log.e(TAG, "createnewFile operation failed for file: " + file, e);
+ }
+ if (!created) {
+ throw new FileNotFoundException(
+ "createNewFile operation failed for: " + file);
}
+ Log.i(TAG, "Created new file: " + file);
}
- final StubDocument document = new StubDocument(file, mimeType, parentDocument);
+ final StubDocument document = new StubDocument(file, mimeType, parent);
Log.d(TAG, "Created document " + document.documentId);
notifyParentChanged(document.parentId);
getContext().getContentResolver().notifyChange(
@@ -349,7 +373,7 @@ public class StubProvider extends DocumentsProvider {
private void configure(String arg, Bundle extras) {
Log.d(TAG, "Configure " + arg);
- String rootName = extras.getString(EXTRA_ROOT, MY_ROOT_ID);
+ String rootName = extras.getString(EXTRA_ROOT, ROOT_0_ID);
long rootSize = extras.getLong(EXTRA_SIZE, 1) * 1024 * 1024;
setSize(rootName, rootSize);
}
@@ -379,10 +403,10 @@ public class StubProvider extends DocumentsProvider {
row.add(Document.COLUMN_LAST_MODIFIED, document.file.lastModified());
}
- private void removeRecursively(File file) {
+ private void removeChildrenRecursively(File file) {
for (File childFile : file.listFiles()) {
if (childFile.isDirectory()) {
- removeRecursively(childFile);
+ removeChildrenRecursively(childFile);
}
childFile.delete();
}
@@ -411,8 +435,8 @@ public class StubProvider extends DocumentsProvider {
@VisibleForTesting
public Uri createFile(String rootId, String path, String mimeType, byte[] content)
throws FileNotFoundException, IOException {
- Log.d(TAG, "Creating file " + rootId + ":" + path);
- StubDocument root = mRoots.get(rootId).rootDocument;
+ Log.d(TAG, "Creating test file " + rootId + ":" + path);
+ StubDocument root = mRoots.get(rootId).document;
if (root == null) {
throw new FileNotFoundException("No roots with the ID " + rootId + " were found");
}
@@ -445,7 +469,7 @@ public class StubProvider extends DocumentsProvider {
@VisibleForTesting
public File getFile(String rootId, String path) throws FileNotFoundException {
- StubDocument root = mRoots.get(rootId).rootDocument;
+ StubDocument root = mRoots.get(rootId).document;
if (root == null) {
throw new FileNotFoundException("No roots with the ID " + rootId + " were found");
}
@@ -461,7 +485,7 @@ public class StubProvider extends DocumentsProvider {
final class RootInfo {
public final String name;
- public final StubDocument rootDocument;
+ public final StubDocument document;
public long capacity;
public long size;
@@ -469,9 +493,11 @@ public class StubProvider extends DocumentsProvider {
this.name = name;
this.capacity = 1024 * 1024;
// Make a subdir in the cache dir for each root.
- File rootDir = new File(getContext().getCacheDir(), name);
- rootDir.mkdir();
- this.rootDocument = new StubDocument(rootDir, Document.MIME_TYPE_DIR, this);
+ File file = new File(getContext().getCacheDir(), name);
+ if (file.mkdir()) {
+ Log.i(TAG, "Created new root directory @ " + file.getPath());
+ }
+ this.document = new StubDocument(file, Document.MIME_TYPE_DIR, this);
this.capacity = capacity;
this.size = 0;
}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java b/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java
new file mode 100644
index 000000000000..5c09794a3e28
--- /dev/null
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.documentsui;
+
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.UiObjectNotFoundException;
+import android.support.test.uiautomator.UiScrollable;
+import android.support.test.uiautomator.UiSelector;
+import android.support.test.uiautomator.Until;
+import android.util.Log;
+
+import junit.framework.Assert;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.Pattern;
+
+/**
+ * A test helper class that provides support for controlling DocumentsUI activities
+ * programmatically, and making assertions against the state of the UI.
+ */
+class UiBot {
+
+ private static final String TAG = "UiBot";
+
+ private static final BySelector SNACK_DELETE =
+ By.desc(Pattern.compile("^Deleting [0-9]+ file.+"));
+
+ private UiDevice mDevice;
+ private int mTimeout;
+
+ public UiBot(UiDevice device, int timeout) {
+ mDevice = device;
+ mTimeout = timeout;
+ }
+
+ UiObject findRoot(String label) throws UiObjectNotFoundException {
+ final UiSelector rootsList = new UiSelector().resourceId(
+ "com.android.documentsui:id/container_roots").childSelector(
+ new UiSelector().resourceId("android:id/list"));
+
+ // We might need to expand drawer if not visible
+ if (!new UiObject(rootsList).waitForExists(mTimeout)) {
+ Log.d(TAG, "Failed to find roots list; trying to expand");
+ final UiSelector hamburger = new UiSelector().resourceId(
+ "com.android.documentsui:id/toolbar").childSelector(
+ new UiSelector().className("android.widget.ImageButton").clickable(true));
+ new UiObject(hamburger).click();
+ }
+
+ // Wait for the first list item to appear
+ new UiObject(rootsList.childSelector(new UiSelector())).waitForExists(mTimeout);
+
+ // Now scroll around to find our item
+ new UiScrollable(rootsList).scrollIntoView(new UiSelector().text(label));
+ return new UiObject(rootsList.childSelector(new UiSelector().text(label)));
+ }
+
+ void openRoot(String label) throws UiObjectNotFoundException {
+ findRoot(label).click();
+ mDevice.waitForIdle();
+ }
+
+ void assertHasRoots(String... labels) throws UiObjectNotFoundException {
+ List<String> missing = new ArrayList<>();
+ for (String label : labels) {
+ if (!findRoot(label).exists()) {
+ missing.add(label);
+ }
+ }
+ if (!missing.isEmpty()) {
+ Assert.fail(
+ "Expected roots " + Arrays.asList(labels) + ", but missing " + missing);
+ }
+ }
+
+ UiObject findDocument(String label) throws UiObjectNotFoundException {
+ final UiSelector docList = new UiSelector().resourceId(
+ "com.android.documentsui:id/container_directory").childSelector(
+ new UiSelector().resourceId("com.android.documentsui:id/list"));
+
+ // Wait for the first list item to appear
+ new UiObject(docList.childSelector(new UiSelector())).waitForExists(mTimeout);
+
+ // new UiScrollable(docList).scrollIntoView(new UiSelector().text(label));
+ return mDevice.findObject(docList.childSelector(new UiSelector().text(label)));
+ }
+
+ boolean hasDocuments(String... labels) throws UiObjectNotFoundException {
+ for (String label : labels) {
+ if (!findDocument(label).exists()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ void assertHasDocuments(String... labels) throws UiObjectNotFoundException {
+ List<String> missing = new ArrayList<>();
+ for (String label : labels) {
+ if (!findDocument(label).exists()) {
+ missing.add(label);
+ }
+ }
+ if (!missing.isEmpty()) {
+ Assert.fail(
+ "Expected documents " + Arrays.asList(labels) + ", but missing " + missing);
+ }
+ }
+
+ void clickDocument(String label) throws UiObjectNotFoundException {
+ findDocument(label).click();
+ }
+
+ void waitForDeleteSnackbar() {
+ mDevice.wait(Until.findObject(SNACK_DELETE), mTimeout);
+ }
+
+ void waitForDeleteSnackbarGone() {
+ // wait a little longer for snackbar to go away, as it disappears after a timeout.
+ mDevice.wait(Until.gone(SNACK_DELETE), mTimeout * 2);
+ }
+
+ void switchViewMode() {
+ UiObject2 mode = menuGridMode();
+ if (mode != null) {
+ mode.click();
+ } else {
+ menuListMode().click();
+ }
+ }
+
+ UiObject2 menuGridMode() {
+ // Note that we're using By.desc rather than By.res, because of b/25285770
+ return find(By.desc("Grid view"));
+ }
+
+ UiObject2 menuListMode() {
+ // Note that we're using By.desc rather than By.res, because of b/25285770
+ return find(By.desc("List view"));
+ }
+
+ UiObject2 menuDelete() {
+ return find(By.res("com.android.documentsui:id/menu_delete"));
+ }
+
+ private UiObject2 find(BySelector selector) {
+ mDevice.wait(Until.findObject(selector), mTimeout);
+ return mDevice.findObject(selector);
+ }
+}
diff --git a/packages/Shell/src/com/android/shell/BugreportReceiver.java b/packages/Shell/src/com/android/shell/BugreportReceiver.java
index 0d1ad197e3fa..e90a3b5a4824 100644
--- a/packages/Shell/src/com/android/shell/BugreportReceiver.java
+++ b/packages/Shell/src/com/android/shell/BugreportReceiver.java
@@ -136,9 +136,10 @@ public class BugreportReceiver extends BroadcastReceiver {
// EXTRA_TEXT should be an ArrayList, but some clients are expecting a single String.
// So, to avoid an exception on Intent.migrateExtraStreamToClipData(), we need to manually
// create the ClipData object with the attachments URIs.
- intent.putExtra(Intent.EXTRA_TEXT, SystemProperties.get("ro.build.description"));
- final ClipData clipData = new ClipData(
- null, new String[] { mimeType },
+ String messageBody = String.format("Build info: %s\nSerial number:%s",
+ SystemProperties.get("ro.build.description"), SystemProperties.get("ro.serialno"));
+ intent.putExtra(Intent.EXTRA_TEXT, messageBody);
+ final ClipData clipData = new ClipData(null, new String[] { mimeType },
new ClipData.Item(null, null, null, bugreportUri));
clipData.addItem(new ClipData.Item(null, null, null, screenshotUri));
intent.setClipData(clipData);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 794e9005fa31..049754ebaaef 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -19,12 +19,14 @@ package com.android.systemui.qs;
import android.animation.Animator;
import android.animation.Animator.AnimatorListener;
import android.animation.AnimatorListenerAdapter;
+import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Handler;
import android.os.Message;
+import android.provider.Settings;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
@@ -34,6 +36,7 @@ import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
+
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.FontSizeUtils;
import com.android.systemui.R;
@@ -620,4 +623,9 @@ public class QSPanel extends FrameLayout implements Tunable {
int getOffsetTop(TileRecord tile);
void updateResources();
}
+
+ public static boolean isTheNewQS(Context context) {
+ return Settings.Secure.getIntForUser(context.getContentResolver(), QS_THE_NEW_QS,
+ ActivityManager.getCurrentUser(), 0) != 0;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
index 61695b2db2d7..48b74a4566a5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -28,6 +28,7 @@ import com.android.internal.logging.MetricsLogger;
import com.android.systemui.R;
import com.android.systemui.qs.QSDetailItems;
import com.android.systemui.qs.QSDetailItems.Item;
+import com.android.systemui.qs.QSPanel;
import com.android.systemui.qs.QSTile;
import com.android.systemui.statusbar.policy.CastController;
import com.android.systemui.statusbar.policy.CastController.CastDevice;
@@ -93,7 +94,7 @@ public class CastTile extends QSTile<QSTile.BooleanState> {
@Override
protected void handleUpdateState(BooleanState state, Object arg) {
state.visible = !mKeyguard.isSecure() || !mKeyguard.isShowing()
- || mKeyguard.canSkipBouncer();
+ || mKeyguard.canSkipBouncer() || QSPanel.isTheNewQS(mContext);
state.label = mContext.getString(R.string.quick_settings_cast_title);
state.value = false;
state.autoMirrorDrawable = false;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
index c6fc6ffcb4d7..2f9a496d9602 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
@@ -21,6 +21,7 @@ import android.provider.Settings.Secure;
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.Prefs;
import com.android.systemui.R;
+import com.android.systemui.qs.QSPanel;
import com.android.systemui.qs.QSTile;
import com.android.systemui.qs.SecureSetting;
import com.android.systemui.qs.UsageTracker;
@@ -110,7 +111,7 @@ public class ColorInversionTile extends QSTile<QSTile.BooleanState> {
protected void handleUpdateState(BooleanState state, Object arg) {
final int value = arg instanceof Integer ? (Integer) arg : mSetting.getValue();
final boolean enabled = value != 0;
- state.visible = enabled || mUsageTracker.isRecentlyUsed();
+ state.visible = enabled || mUsageTracker.isRecentlyUsed() || QSPanel.isTheNewQS(mContext);
state.value = enabled;
state.label = mContext.getString(R.string.quick_settings_inversion_label);
state.icon = enabled ? mEnable : mDisable;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
index 7b83e6a9ca25..79084ae73e3c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
@@ -23,6 +23,7 @@ import android.content.Intent;
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.Prefs;
import com.android.systemui.R;
+import com.android.systemui.qs.QSPanel;
import com.android.systemui.qs.QSTile;
import com.android.systemui.qs.UsageTracker;
import com.android.systemui.statusbar.policy.HotspotController;
@@ -88,7 +89,8 @@ public class HotspotTile extends QSTile<QSTile.BooleanState> {
@Override
protected void handleUpdateState(BooleanState state, Object arg) {
- state.visible = mController.isHotspotSupported() && mUsageTracker.isRecentlyUsed();
+ state.visible = (mController.isHotspotSupported() && mUsageTracker.isRecentlyUsed())
+ || QSPanel.isTheNewQS(mContext);
state.label = mContext.getString(R.string.quick_settings_hotspot_label);
if (arg instanceof Boolean) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
index e6fade455444..0e2672ca4da1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
@@ -18,6 +18,7 @@ package com.android.systemui.qs.tiles;
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.R;
+import com.android.systemui.qs.QSPanel;
import com.android.systemui.qs.QSTile;
import com.android.systemui.statusbar.policy.KeyguardMonitor;
import com.android.systemui.statusbar.policy.LocationController;
@@ -73,7 +74,7 @@ public class LocationTile extends QSTile<QSTile.BooleanState> {
// Work around for bug 15916487: don't show location tile on top of lock screen. After the
// bug is fixed, this should be reverted to only hiding it on secure lock screens:
// state.visible = !(mKeyguard.isSecure() && mKeyguard.isShowing());
- state.visible = !mKeyguard.isShowing();
+ state.visible = !mKeyguard.isShowing() || QSPanel.isTheNewQS(mContext);
state.value = locationEnabled;
if (locationEnabled) {
state.icon = mEnable;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index 4d40cb7072f7..a58bc5882f2a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -21,9 +21,11 @@ import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.res.Configuration;
+import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.util.Log;
import android.view.Display;
@@ -52,10 +54,21 @@ public class Recents extends SystemUI
public final static int EVENT_BUS_PRIORITY = 1;
public final static int BIND_TO_SYSTEM_USER_RETRY_DELAY = 5000;
+ // Purely for experimentation
+ private final static String RECENTS_OVERRIDE_SYSPROP_KEY = "persist.recents_override_pkg";
+ private final static String ACTION_SHOW_RECENTS = "com.android.systemui.recents.ACTION_SHOW";
+ private final static String ACTION_HIDE_RECENTS = "com.android.systemui.recents.ACTION_HIDE";
+ private final static String ACTION_TOGGLE_RECENTS = "com.android.systemui.recents.ACTION_TOGGLE";
+
private static SystemServicesProxy sSystemServicesProxy;
private static RecentsTaskLoader sTaskLoader;
private static RecentsConfiguration sConfiguration;
+ // For experiments only, allows another package to handle recents if it is defined in the system
+ // properties. This is limited to show/toggle/hide, and does not tie into the ActivityManager,
+ // and does not reside in the home stack.
+ private String mOverrideRecentsPackageName;
+
private Handler mHandler;
private RecentsImpl mImpl;
@@ -142,6 +155,14 @@ public class Recents extends SystemUI
mHandler = new Handler();
mImpl = new RecentsImpl(mContext);
+ // Check if there is a recents override package
+ if ("userdebug".equals(Build.TYPE) || "eng".equals(Build.TYPE)) {
+ String cnStr = SystemProperties.get(RECENTS_OVERRIDE_SYSPROP_KEY);
+ if (!cnStr.isEmpty()) {
+ mOverrideRecentsPackageName = cnStr;
+ }
+ }
+
// Register with the event bus
EventBus.getDefault().register(this, EVENT_BUS_PRIORITY);
EventBus.getDefault().register(sTaskLoader, EVENT_BUS_PRIORITY);
@@ -172,6 +193,10 @@ public class Recents extends SystemUI
*/
@Override
public void showRecents(boolean triggeredFromAltTab, View statusBarView) {
+ if (proxyToOverridePackage(ACTION_SHOW_RECENTS)) {
+ return;
+ }
+
int currentUser = sSystemServicesProxy.getCurrentUser();
if (sSystemServicesProxy.isSystemUser(currentUser)) {
mImpl.showRecents(triggeredFromAltTab);
@@ -197,6 +222,10 @@ public class Recents extends SystemUI
*/
@Override
public void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
+ if (proxyToOverridePackage(ACTION_HIDE_RECENTS)) {
+ return;
+ }
+
int currentUser = sSystemServicesProxy.getCurrentUser();
if (sSystemServicesProxy.isSystemUser(currentUser)) {
mImpl.hideRecents(triggeredFromAltTab, triggeredFromHomeKey);
@@ -222,6 +251,10 @@ public class Recents extends SystemUI
*/
@Override
public void toggleRecents(Display display, int layoutDirection, View statusBarView) {
+ if (proxyToOverridePackage(ACTION_TOGGLE_RECENTS)) {
+ return;
+ }
+
int currentUser = sSystemServicesProxy.getCurrentUser();
if (sSystemServicesProxy.isSystemUser(currentUser)) {
mImpl.toggleRecents();
@@ -421,4 +454,19 @@ public class Recents extends SystemUI
}
mOnConnectRunnables.clear();
}
+
+ /**
+ * Attempts to proxy the following action to the override recents package.
+ * @return whether the proxying was successful
+ */
+ private boolean proxyToOverridePackage(String action) {
+ if (mOverrideRecentsPackageName != null) {
+ Intent intent = new Intent(action);
+ intent.setPackage(mOverrideRecentsPackageName);
+ intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ mContext.sendBroadcast(intent);
+ return true;
+ }
+ return false;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index 243c0e1dbbf3..0e11f02a34ec 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -16,7 +16,7 @@
package com.android.systemui.recents;
-import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
import android.app.ActivityManager;
import android.app.ActivityOptions;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsResizeTaskDialog.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsResizeTaskDialog.java
index 28299d3217fb..d4158451b54b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsResizeTaskDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsResizeTaskDialog.java
@@ -33,8 +33,8 @@ import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.views.RecentsView;
-import static android.app.ActivityManager.DOCKED_STACK_ID;
-import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
+import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
/**
* A helper for the dialogs that show when task debugging is on.
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/ParametricCurve.java b/packages/SystemUI/src/com/android/systemui/recents/misc/ParametricCurve.java
index ea6821fe1696..515c3bda3f9c 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/ParametricCurve.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/ParametricCurve.java
@@ -189,6 +189,7 @@ public class ParametricCurve {
public float computePOffsetForScaledHeight(int height, Rect bounds) {
int top = bounds.top;
int bottom = bounds.bottom;
+ height = Math.min(height, bottom - top);
if (bounds.height() == 0) {
return 0;
@@ -231,6 +232,7 @@ public class ParametricCurve {
public float computePOffsetForHeight(int height, Rect bounds) {
int top = bounds.top;
int bottom = bounds.bottom;
+ height = Math.min(height, bottom - top);
if (bounds.height() == 0) {
return 0;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index 81a949d1b7cf..2fe5e988b3e6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -72,8 +72,10 @@ import java.util.Iterator;
import java.util.List;
import java.util.Random;
-import static android.app.ActivityManager.DOCKED_STACK_ID;
-import static android.app.ActivityManager.INVALID_STACK_ID;
+import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
+import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.HOME_STACK_ID;
+import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
/**
* Acts as a shim around the real system services that we need to access data from, and provides
@@ -233,6 +235,15 @@ public class SystemServicesProxy {
return null;
}
+ /**
+ * Returns whether this device has freeform workspaces.
+ */
+ public boolean hasFreeformWorkspaceSupport() {
+ if (mPm == null) return false;
+
+ return mPm.hasSystemFeature(PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT);
+ }
+
/** Returns whether the recents is currently running */
public boolean isRecentsTopMost(ActivityManager.RunningTaskInfo topTask,
MutableBoolean isHomeTopMost) {
@@ -311,14 +322,14 @@ public class SystemServicesProxy {
* Returns whether the given stack id is the home stack id.
*/
public static boolean isHomeStack(int stackId) {
- return stackId == ActivityManager.HOME_STACK_ID;
+ return stackId == HOME_STACK_ID;
}
/**
* Returns whether the given stack id is the freeform workspace stack id.
*/
public static boolean isFreeformStack(int stackId) {
- return stackId == ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
+ return stackId == FREEFORM_WORKSPACE_STACK_ID;
}
/**
@@ -728,7 +739,7 @@ public class SystemServicesProxy {
try {
// Use the home stack bounds
- ActivityManager.StackInfo stackInfo = mIam.getStackInfo(ActivityManager.HOME_STACK_ID);
+ ActivityManager.StackInfo stackInfo = mIam.getStackInfo(HOME_STACK_ID);
if (stackInfo != null) {
windowRect.set(stackInfo.bounds);
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java
index ce993c537e76..a97a2a800ac4 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java
@@ -17,10 +17,11 @@
package com.android.systemui.recents.views;
import android.util.Log;
+import com.android.systemui.recents.misc.Utilities;
import com.android.systemui.recents.model.Task;
-import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
/**
* The layout logic for the contents of the freeform workspace.
@@ -33,6 +34,7 @@ public class FreeformWorkspaceLayoutAlgorithm {
// The number of cells in the freeform workspace
private int mFreeformCellXCount;
private int mFreeformCellYCount;
+
// The width and height of the cells in the freeform workspace
private int mFreeformCellWidth;
private int mFreeformCellHeight;
@@ -44,22 +46,26 @@ public class FreeformWorkspaceLayoutAlgorithm {
* Updates the layout for each of the freeform workspace tasks. This is called after the stack
* layout is updated.
*/
- public void update(ArrayList<Task> freeformTasks, TaskStackLayoutAlgorithm stackLayout) {
+ public void update(List<Task> freeformTasks, TaskStackLayoutAlgorithm stackLayout) {
+ mTaskIndexMap.clear();
+
int numFreeformTasks = stackLayout.mNumFreeformTasks;
if (!freeformTasks.isEmpty()) {
// Calculate the cell width/height depending on the number of freeform tasks
mFreeformCellXCount = Math.max(2, (int) Math.ceil(Math.sqrt(numFreeformTasks)));
- mFreeformCellYCount = Math.max(2, (int) Math.ceil((float) numFreeformTasks / mFreeformCellXCount));
- mFreeformCellWidth = stackLayout.mFreeformRect.width() / mFreeformCellXCount;
+ mFreeformCellYCount = Math.max(2, (int) Math.ceil((float) numFreeformTasks /
+ mFreeformCellXCount));
// For now, make the cells square
+ mFreeformCellWidth = Math.min(stackLayout.mFreeformRect.width() / mFreeformCellXCount,
+ stackLayout.mFreeformRect.height() / mFreeformCellYCount);
mFreeformCellHeight = mFreeformCellWidth;
// Put each of the tasks in the progress map at a fixed index (does not need to actually
// map to a scroll position, just by index)
int taskCount = freeformTasks.size();
- for (int i = 0; i < taskCount; i++) {
+ for (int i = taskCount - 1; i >= 0; i--) {
Task task = freeformTasks.get(i);
- mTaskIndexMap.put(task.key, i);
+ mTaskIndexMap.put(task.key, taskCount - i - 1);
}
if (DEBUG) {
@@ -74,24 +80,23 @@ public class FreeformWorkspaceLayoutAlgorithm {
/**
* Returns whether the transform is available for the given task.
*/
- public boolean isTransformAvailable(Task task, float stackScroll,
- TaskStackLayoutAlgorithm stackLayout) {
- if (stackLayout.mNumFreeformTasks == 0 || task == null ||
- !mTaskIndexMap.containsKey(task.key)) {
+ public boolean isTransformAvailable(Task task, TaskStackLayoutAlgorithm stackLayout) {
+ if (stackLayout.mNumFreeformTasks == 0 || task == null) {
return false;
}
- return stackScroll > stackLayout.mStackEndScrollP;
+ return mTaskIndexMap.containsKey(task.key);
}
/**
* Returns the transform for the given task. Any rect returned will be offset by the actual
* transform for the freeform workspace.
*/
- public TaskViewTransform getTransform(Task task, float stackScroll,
- TaskViewTransform transformOut, TaskStackLayoutAlgorithm stackLayout) {
- if (Float.compare(stackScroll, stackLayout.mStackEndScrollP) > 0) {
+ public TaskViewTransform getTransform(Task task, TaskViewTransform transformOut,
+ TaskStackLayoutAlgorithm stackLayout) {
+ if (mTaskIndexMap.containsKey(task.key)) {
// This is a freeform task, so lay it out in the freeform workspace
int taskIndex = mTaskIndexMap.get(task.key);
+ int topOffset = (stackLayout.mFreeformRect.top - stackLayout.mTaskRect.top);
int x = taskIndex % mFreeformCellXCount;
int y = taskIndex / mFreeformCellXCount;
float scale = (float) mFreeformCellWidth / stackLayout.mTaskRect.width();
@@ -99,8 +104,13 @@ public class FreeformWorkspaceLayoutAlgorithm {
int scaleYOffset = (int) (((1f - scale) * stackLayout.mTaskRect.height()) / 2);
transformOut.scale = scale * 0.9f;
transformOut.translationX = x * mFreeformCellWidth - scaleXOffset;
- transformOut.translationY = y * mFreeformCellHeight - scaleYOffset;
+ transformOut.translationY = topOffset + y * mFreeformCellHeight - scaleYOffset;
+ transformOut.translationZ = stackLayout.mMaxTranslationZ;
+ transformOut.rect.set(stackLayout.mTaskRect);
+ transformOut.rect.offset(transformOut.translationX, transformOut.translationY);
+ Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
transformOut.visible = true;
+ transformOut.p = 0;
return transformOut;
}
return null;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index a5b7aaf3036a..d72e50e0c5dc 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -61,7 +61,9 @@ import com.android.systemui.recents.model.TaskStack;
import java.util.ArrayList;
import java.util.List;
-import static android.app.ActivityManager.INVALID_STACK_ID;
+import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
/**
* This view is the the top level layout that contains TaskStacks (which are laid out according
@@ -489,14 +491,14 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
int destinationStack) {
final int targetStackId = destinationStack != INVALID_STACK_ID ?
destinationStack : clickedTask.getTask().key.stackId;
- if (targetStackId != ActivityManager.FREEFORM_WORKSPACE_STACK_ID
- && targetStackId != ActivityManager.FULLSCREEN_WORKSPACE_STACK_ID) {
+ if (targetStackId != FREEFORM_WORKSPACE_STACK_ID
+ && targetStackId != FULLSCREEN_WORKSPACE_STACK_ID) {
return null;
}
// If this is a full screen stack, the transition will be towards the single, full screen
// task. We only need the transition spec for this task.
List<AppTransitionAnimationSpec> specs = new ArrayList<>();
- if (targetStackId == ActivityManager.FULLSCREEN_WORKSPACE_STACK_ID) {
+ if (targetStackId == FULLSCREEN_WORKSPACE_STACK_ID) {
specs.add(createThumbnailHeaderAnimationSpec(
stackView, offsetX, offsetY, stackScroll, clickedTask,
clickedTask.getTask().key.id, ADD_HEADER_BITMAP));
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
index 45d626e15327..a0713d7defc0 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
@@ -24,12 +24,14 @@ import com.android.systemui.R;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsConfiguration;
import com.android.systemui.recents.misc.ParametricCurve;
+import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.misc.Utilities;
import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.model.TaskStack;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.LinkedList;
/**
@@ -44,14 +46,8 @@ public class TaskStackLayoutAlgorithm {
private static final float STACK_PEEK_MIN_SCALE = 0.85f;
// The scale of the last task
private static final float SINGLE_TASK_SCALE = 0.95f;
- // The percentage of the height of the stack that we want to show the last task at
- private static final float VISIBLE_LAST_TASK_HEIGHT_PCT = 0.45f;
// The percentage of height of task to show between tasks
private static final float VISIBLE_TASK_HEIGHT_BETWEEN_TASKS = 0.5f;
- // The percentage between the maxStackScroll and the maxScroll where a given scroll will still
- // snap back to the maxStackScroll instead of to the maxScroll (which shows the freeform
- // workspace)
- private static final float SNAP_TO_MAX_STACK_SCROLL_FACTOR = 0.3f;
// A report of the visibility state of the stack
public class VisibilityReport {
@@ -67,14 +63,36 @@ public class TaskStackLayoutAlgorithm {
Context mContext;
- // This is the view bounds inset exactly by the search bar, but without the bottom inset
- // see RecentsConfiguration.getTaskStackBounds()
- public Rect mStackRect = new Rect();
- // This is the task view bounds for layout (untransformed), the rect is top-aligned to the top
- // of the stack rect
+ /*
+ +-------------------+
+ | SEARCH |
+ +-------------------+
+ |+-----------------+|
+ || FREEFORM ||
+ || ||
+ || ||
+ |+-----------------+|
+ | +-----------+ |
+ | +---------------+ |
+ | | | |
+ |+-----------------+|
+ || STACK ||
+ +-------------------+
+ */
+
+ // The task bounds (untransformed) for layout. This rect is anchored at mTaskRoot.
public Rect mTaskRect = new Rect();
- // The bounds of the freeform workspace, the rect is top-aligned to the top of the stack rect
+ // The freeform workspace bounds, inset from the top by the search bar, and is a fixed height
public Rect mFreeformRect = new Rect();
+ // The freeform stack bounds, inset from the top by the search bar and freeform workspace, and
+ // runs to the bottom of the screen
+ private Rect mFreeformStackRect = new Rect();
+ // The stack bounds, inset from the top by the search bar, and runs to
+ // the bottom of the screen
+ private Rect mStackRect = new Rect();
+ // The current stack rect, can either by mFreeformStackRect or mStackRect depending on whether
+ // there is a freeform workspace
+ public Rect mCurrentStackRect;
// This is the current system insets
public Rect mSystemInsets = new Rect();
@@ -83,13 +101,6 @@ public class TaskStackLayoutAlgorithm {
// The largest scroll progress, at this value, the front most task will be visible above the
// navigation bar
float mMaxScrollP;
- // The scroll progress at which bottom of the first task of the stack is aligned with the bottom
- // of the stack
- float mStackEndScrollP;
- // The scroll progress that we actually want to scroll the user to when they want to go to the
- // end of the stack (it accounts for the nav bar, so that the bottom of the task is offset from
- // the bottom of the stack)
- float mPreferredStackEndScrollP;
// The initial progress that the scroller is set when you first enter recents
float mInitialScrollP;
// The task progress for the front-most task in the stack
@@ -109,13 +120,6 @@ public class TaskStackLayoutAlgorithm {
// The relative progress to ensure that the offset from the bottom of the stack to the bottom
// of the task is respected
float mStackBottomPOffset;
- // The freeform workspace gap
- int mFreeformWorkspaceGapOffset;
- float mFreeformWorkspaceGapPOffset;
- // The relative progress to ensure that the freeform workspace height + gap + stack bottom
- // padding is respected
- int mFreeformWorkspaceOffset;
- float mFreeformWorkspacePOffset;
// The last computed task counts
int mNumStackTasks;
@@ -130,9 +134,6 @@ public class TaskStackLayoutAlgorithm {
// The freeform workspace layout
FreeformWorkspaceLayoutAlgorithm mFreeformLayoutAlgorithm;
- // Temporary task view transform
- TaskViewTransform mTmpTransform = new TaskViewTransform();
-
// Log function
static ParametricCurve sCurve;
@@ -190,53 +191,57 @@ public class TaskStackLayoutAlgorithm {
}
/**
- * Computes the stack and task rects.
+ * Computes the stack and task rects. The given task stack bounds is the whole bounds not
+ * including the search bar.
*/
public void initialize(Rect taskStackBounds) {
+ SystemServicesProxy ssp = Recents.getSystemServices();
RecentsConfiguration config = Recents.getConfiguration();
int widthPadding = (int) (config.taskStackWidthPaddingPct * taskStackBounds.width());
int heightPadding = mContext.getResources().getDimensionPixelSize(
R.dimen.recents_stack_top_padding);
- // Compute the stack rect, inset from the given task stack bounds
- mStackRect.set(taskStackBounds.left + widthPadding, taskStackBounds.top + heightPadding,
- taskStackBounds.right - widthPadding, taskStackBounds.bottom);
+ // The freeform height is the visible height (not including system insets) - padding above
+ // freeform and below stack - gap between the freeform and stack
mStackBottomOffset = mSystemInsets.bottom + heightPadding;
-
- // Compute the task rect, align it to the top-center square in the stack rect
+ int ffHeight = (taskStackBounds.height() - 2 * heightPadding - mStackBottomOffset) / 2;
+ mFreeformRect.set(taskStackBounds.left + widthPadding,
+ taskStackBounds.top + heightPadding,
+ taskStackBounds.right - widthPadding,
+ taskStackBounds.top + heightPadding + ffHeight);
+ mFreeformStackRect.set(taskStackBounds.left + widthPadding,
+ taskStackBounds.top + heightPadding + ffHeight + heightPadding,
+ taskStackBounds.right - widthPadding,
+ taskStackBounds.bottom);
+ mStackRect.set(taskStackBounds.left + widthPadding,
+ taskStackBounds.top + heightPadding,
+ taskStackBounds.right - widthPadding,
+ taskStackBounds.bottom);
+ // Anchor the task rect to the top-center of the non-freeform stack rect
int size = Math.min(mStackRect.width(), mStackRect.height() - mStackBottomOffset);
- int xOffset = (mStackRect.width() - size) / 2;
- mTaskRect.set(mStackRect.left + xOffset, mStackRect.top,
- mStackRect.right - xOffset, mStackRect.top + size);
-
- // Compute the freeform rect, align it to the top-left of the stack rect
- mFreeformRect.set(mStackRect);
- mFreeformRect.bottom = taskStackBounds.bottom - mStackBottomOffset;
+ mTaskRect.set(mStackRect.left, mStackRect.top,
+ mStackRect.left + size, mStackRect.top + size);
+ mCurrentStackRect = ssp.hasFreeformWorkspaceSupport() ? mFreeformStackRect : mStackRect;
// Compute the progress offsets
int withinAffiliationOffset = mContext.getResources().getDimensionPixelSize(
R.dimen.recents_task_bar_height);
int betweenAffiliationOffset = (int) (VISIBLE_TASK_HEIGHT_BETWEEN_TASKS * mTaskRect.height());
mWithinAffiliationPOffset = sCurve.computePOffsetForScaledHeight(withinAffiliationOffset,
- mStackRect);
+ mCurrentStackRect);
mBetweenAffiliationPOffset = sCurve.computePOffsetForScaledHeight(betweenAffiliationOffset,
- mStackRect);
+ mCurrentStackRect);
mTaskHeightPOffset = sCurve.computePOffsetForScaledHeight(mTaskRect.height(),
- mStackRect);
+ mCurrentStackRect);
mTaskHalfHeightPOffset = sCurve.computePOffsetForScaledHeight(mTaskRect.height() / 2,
- mStackRect);
- mStackBottomPOffset = sCurve.computePOffsetForHeight(mStackBottomOffset, mStackRect);
- mFreeformWorkspaceGapOffset = mStackBottomOffset;
- mFreeformWorkspaceGapPOffset = sCurve.computePOffsetForHeight(mFreeformWorkspaceGapOffset,
- mStackRect);
- mFreeformWorkspaceOffset = mFreeformWorkspaceGapOffset + mFreeformRect.height() +
- mStackBottomOffset;
- mFreeformWorkspacePOffset = sCurve.computePOffsetForHeight(mFreeformWorkspaceOffset,
- mStackRect);
+ mCurrentStackRect);
+ mStackBottomPOffset = sCurve.computePOffsetForHeight(mStackBottomOffset, mCurrentStackRect);
if (DEBUG) {
Log.d(TAG, "initialize");
Log.d(TAG, "\tarclength: " + sCurve.getArcLength());
+ Log.d(TAG, "\tmFreeformRect: " + mFreeformRect);
+ Log.d(TAG, "\tmFreeformStackRect: " + mFreeformStackRect);
Log.d(TAG, "\tmStackRect: " + mStackRect);
Log.d(TAG, "\tmTaskRect: " + mTaskRect);
Log.d(TAG, "\tmSystemInsets: " + mSystemInsets);
@@ -246,20 +251,9 @@ public class TaskStackLayoutAlgorithm {
Log.d(TAG, "\tmTaskHeightPOffset: " + mTaskHeightPOffset);
Log.d(TAG, "\tmTaskHalfHeightPOffset: " + mTaskHalfHeightPOffset);
Log.d(TAG, "\tmStackBottomPOffset: " + mStackBottomPOffset);
- Log.d(TAG, "\tmFreeformWorkspacePOffset: " + mFreeformWorkspacePOffset);
- Log.d(TAG, "\tmFreeformWorkspaceGapPOffset: " + mFreeformWorkspaceGapPOffset);
-
- Log.d(TAG, "\ty at p=0: " + sCurve.pToX(0f, mStackRect));
- Log.d(TAG, "\ty at p=1: " + sCurve.pToX(1f, mStackRect));
-
- for (int height = 0; height <= 2000; height += 50) {
- float p = sCurve.computePOffsetForScaledHeight(height, mStackRect);
- float p2 = sCurve.computePOffsetForHeight(height, mStackRect);
- Log.d(TAG, "offset: " + height + ", " +
- p + " => " + (mStackRect.bottom - sCurve.pToX(1f - p, mStackRect)) /
- sCurve.pToScale(1f - p) + ", " +
- p2 + " => " + (mStackRect.bottom - sCurve.pToX(1f - p2, mStackRect)));
- }
+
+ Log.d(TAG, "\ty at p=0: " + sCurve.pToX(0f, mCurrentStackRect));
+ Log.d(TAG, "\ty at p=1: " + sCurve.pToX(1f, mCurrentStackRect));
}
}
@@ -279,7 +273,7 @@ public class TaskStackLayoutAlgorithm {
ArrayList<Task> tasks = stack.getTasks();
if (tasks.isEmpty()) {
mFrontMostTaskP = 0;
- mMinScrollP = mMaxScrollP = mStackEndScrollP = mPreferredStackEndScrollP = 0;
+ mMinScrollP = mMaxScrollP = 0;
mNumStackTasks = mNumFreeformTasks = 0;
return;
}
@@ -307,6 +301,10 @@ public class TaskStackLayoutAlgorithm {
Task task = stackTasks.get(i);
mTaskProgressMap.put(task.key, pAtFrontMostTaskTop);
+ if (DEBUG) {
+ Log.d(TAG, "Update: " + task.activityLabel + " p: " + pAtFrontMostTaskTop);
+ }
+
if (i < (taskCount - 1)) {
// Increment the peek height
float pPeek = task.group.isFrontMostTask(task) ?
@@ -316,37 +314,24 @@ public class TaskStackLayoutAlgorithm {
}
mFrontMostTaskP = pAtFrontMostTaskTop;
- // Set the stack end scroll progress to the point at which the bottom of the front-most
- // task is aligned to the bottom of the stack
- mStackEndScrollP = alignToStackBottom(pAtFrontMostTaskTop, mTaskHeightPOffset);
if (mNumStackTasks > 1) {
- // Set the preferred stack end scroll progress to the point where the bottom of the
- // front-most task is offset by the navbar and padding from the bottom of the stack
- mPreferredStackEndScrollP = mStackEndScrollP + mStackBottomPOffset;
-
+ // Set the stack end scroll progress to the point at which the bottom of the front-most
+ // task is aligned to the bottom of the stack
+ mMaxScrollP = alignToStackBottom(pAtFrontMostTaskTop,
+ mStackBottomPOffset + mTaskHeightPOffset);
// Basically align the back-most task such that the last two tasks would be visible
- mMinScrollP = alignToStackBottom(pAtBackMostTaskTop, 2 *
- mBetweenAffiliationPOffset);
+ mMinScrollP = alignToStackBottom(pAtBackMostTaskTop,
+ mStackBottomPOffset + mTaskHeightPOffset);
} else {
// When there is a single item, then just make all the stack progresses the same
- mPreferredStackEndScrollP = mStackEndScrollP;
- mMinScrollP = mStackEndScrollP;
+ mMinScrollP = mMaxScrollP = 0;
}
- } else {
- // TODO: In the case where there is only freeform tasks, then the scrolls should be
- // set to zero
}
if (!freeformTasks.isEmpty()) {
- // The max scroll includes the freeform workspace offset. As the scroll progress exceeds
- // mStackEndScrollP up to mMaxScrollP, the stack will translate upwards and the freeform
- // workspace will be visible
mFreeformLayoutAlgorithm.update(freeformTasks, this);
- mMaxScrollP = mStackEndScrollP + mFreeformWorkspacePOffset;
- mInitialScrollP = isInitialStateFreeform(stack) ?
- mMaxScrollP : mPreferredStackEndScrollP;
+ mInitialScrollP = mMaxScrollP;
} else {
- mMaxScrollP = mPreferredStackEndScrollP;
mInitialScrollP = Math.max(mMinScrollP, mMaxScrollP - mTaskHalfHeightPOffset);
}
@@ -354,7 +339,6 @@ public class TaskStackLayoutAlgorithm {
Log.d(TAG, "mNumStackTasks: " + mNumStackTasks);
Log.d(TAG, "mNumFreeformTasks: " + mNumFreeformTasks);
Log.d(TAG, "mMinScrollP: " + mMinScrollP);
- Log.d(TAG, "mStackEndScrollP: " + mStackEndScrollP);
Log.d(TAG, "mMaxScrollP: " + mMaxScrollP);
}
}
@@ -369,28 +353,28 @@ public class TaskStackLayoutAlgorithm {
return new VisibilityReport(1, 1);
}
- // If there are freeform tasks, then they will be the only ones visible
- int freeformTaskCount = 0;
- for (Task t : tasks) {
- if (t.isFreeformTask()) {
- freeformTaskCount++;
- }
- }
- if (freeformTaskCount > 0) {
- return new VisibilityReport(freeformTaskCount, freeformTaskCount);
+ // Quick return when there are no stack tasks
+ if (mNumStackTasks == 0) {
+ return new VisibilityReport(Math.max(mNumFreeformTasks, 1),
+ Math.max(mNumFreeformTasks, 1));
}
// Otherwise, walk backwards in the stack and count the number of tasks and visible
- // thumbnails
+ // thumbnails and add that to the total freeform task count
int taskHeight = mTaskRect.height();
int taskBarHeight = mContext.getResources().getDimensionPixelSize(
R.dimen.recents_task_bar_height);
- int numVisibleTasks = 1;
- int numVisibleThumbnails = 1;
- float progress = mTaskProgressMap.get(tasks.get(tasks.size() - 1).key) - mInitialScrollP;
- int prevScreenY = sCurve.pToX(progress, mStackRect);
+ int numVisibleTasks = Math.max(mNumFreeformTasks, 1);
+ int numVisibleThumbnails = Math.max(mNumFreeformTasks, 1);
+ Task firstNonFreeformTask = tasks.get(tasks.size() - mNumFreeformTasks - 1);
+ float progress = mTaskProgressMap.get(firstNonFreeformTask.key) - mInitialScrollP;
+ int prevScreenY = sCurve.pToX(progress, mCurrentStackRect);
for (int i = tasks.size() - 2; i >= 0; i--) {
Task task = tasks.get(i);
+ if (task.isFreeformTask()) {
+ continue;
+ }
+
progress = mTaskProgressMap.get(task.key) - mInitialScrollP;
if (progress < 0) {
break;
@@ -399,7 +383,7 @@ public class TaskStackLayoutAlgorithm {
if (isFrontMostTaskInGroup) {
float scaleAtP = sCurve.pToScale(progress);
int scaleYOffsetAtP = (int) (((1f - scaleAtP) * taskHeight) / 2);
- int screenY = sCurve.pToX(progress, mStackRect) + scaleYOffsetAtP;
+ int screenY = sCurve.pToX(progress, mCurrentStackRect) + scaleYOffsetAtP;
boolean hasVisibleThumbnail = (prevScreenY - screenY) > taskBarHeight;
if (hasVisibleThumbnail) {
numVisibleThumbnails++;
@@ -431,17 +415,8 @@ public class TaskStackLayoutAlgorithm {
*/
public TaskViewTransform getStackTransform(Task task, float stackScroll,
TaskViewTransform transformOut, TaskViewTransform prevTransform) {
- if (mFreeformLayoutAlgorithm.isTransformAvailable(task, stackScroll, this)) {
- mFreeformLayoutAlgorithm.getTransform(task, stackScroll, transformOut, this);
- if (transformOut.visible) {
- getFreeformWorkspaceBounds(stackScroll, mTmpTransform);
- transformOut.translationY += mTmpTransform.translationY;
- transformOut.translationZ = mMaxTranslationZ;
- transformOut.rect.set(mTaskRect);
- transformOut.rect.offset(0, transformOut.translationY);
- Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
- transformOut.p = 0;
- }
+ if (mFreeformLayoutAlgorithm.isTransformAvailable(task, this)) {
+ mFreeformLayoutAlgorithm.getTransform(task, transformOut, this);
return transformOut;
} else {
// Return early if we have an invalid index
@@ -463,32 +438,25 @@ public class TaskStackLayoutAlgorithm {
// modulate some values directly
float pTaskRelative = mMinScrollP - stackScroll;
float scale = (mNumFreeformTasks > 0) ? 1f : SINGLE_TASK_SCALE;
- int topOffset = (mStackRect.height() - mTaskRect.height()) / 2;
+ int topOffset = (mCurrentStackRect.top - mTaskRect.top) +
+ (mCurrentStackRect.height() - mTaskRect.height()) / 2;
transformOut.scale = scale;
- transformOut.translationX = 0;
- transformOut.translationY = (int) (topOffset + (pTaskRelative * mStackRect.height()));
+ transformOut.translationX = (mStackRect.width() - mTaskRect.width()) / 2;
+ transformOut.translationY = (int) (topOffset + (pTaskRelative * mCurrentStackRect.height()));
transformOut.translationZ = mMaxTranslationZ;
transformOut.rect.set(mTaskRect);
- transformOut.rect.offset(0, transformOut.translationY);
+ transformOut.rect.offset(transformOut.translationX, transformOut.translationY);
Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
transformOut.visible = true;
transformOut.p = pTaskRelative;
return transformOut;
} else {
- // Once we scroll past the preferred stack end scroll, then we should start translating
- // the cards in screen space and lock their final state at the end stack progress
- int overscrollYOffset = 0;
- if (mNumFreeformTasks > 0 && stackScroll > mStackEndScrollP) {
- float stackOverscroll = (stackScroll - mPreferredStackEndScrollP) /
- (mFreeformWorkspacePOffset - mFreeformWorkspaceGapPOffset);
- overscrollYOffset = (int) (Math.max(0, stackOverscroll) *
- (mFreeformWorkspaceOffset - mFreeformWorkspaceGapPOffset));
- stackScroll = Math.min(mPreferredStackEndScrollP, stackScroll);
- }
-
float pTaskRelative = taskProgress - stackScroll;
float pBounded = Math.max(0, Math.min(pTaskRelative, 1f));
+ if (DEBUG) {
+ Log.d(TAG, "getStackTransform (normal): " + taskProgress + ", " + stackScroll);
+ }
// If the task top is outside of the bounds below the screen, then immediately reset it
if (pTaskRelative > 1f) {
@@ -508,18 +476,18 @@ public class TaskStackLayoutAlgorithm {
float scale = sCurve.pToScale(pBounded);
int scaleYOffset = (int) (((1f - scale) * mTaskRect.height()) / 2);
transformOut.scale = scale;
- transformOut.translationX = 0;
- transformOut.translationY = sCurve.pToX(pBounded, mStackRect) - mStackRect.top -
- scaleYOffset - overscrollYOffset;
+ transformOut.translationX = (mStackRect.width() - mTaskRect.width()) / 2;
+ transformOut.translationY = (mCurrentStackRect.top - mTaskRect.top) +
+ (sCurve.pToX(pBounded, mCurrentStackRect) - mCurrentStackRect.top) -
+ scaleYOffset;
transformOut.translationZ = Math.max(mMinTranslationZ,
mMinTranslationZ + (pBounded * (mMaxTranslationZ - mMinTranslationZ)));
transformOut.rect.set(mTaskRect);
- transformOut.rect.offset(0, transformOut.translationY);
+ transformOut.rect.offset(transformOut.translationX, transformOut.translationY);
Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
transformOut.visible = true;
transformOut.p = pTaskRelative;
if (DEBUG) {
- Log.d(TAG, "getStackTransform (normal): " + taskProgress + ", " + stackScroll);
Log.d(TAG, "\t" + transformOut);
}
@@ -528,61 +496,6 @@ public class TaskStackLayoutAlgorithm {
}
/**
- * Returns whether this stack should be initialized to show the freeform workspace or not.
- */
- public boolean isInitialStateFreeform(TaskStack stack) {
- Task launchTarget = stack.getLaunchTarget();
- if (launchTarget != null) {
- return launchTarget.isFreeformTask();
- }
- Task frontTask = stack.getFrontMostTask();
- if (frontTask != null) {
- return frontTask.isFreeformTask();
- }
- return false;
- }
-
- /**
- * Update/get the transform
- */
- public TaskViewTransform getFreeformWorkspaceBounds(float stackScroll,
- TaskViewTransform transformOut) {
- transformOut.reset();
- if (mNumFreeformTasks == 0) {
- return transformOut;
- }
-
- if (stackScroll > mStackEndScrollP) {
- // mStackEndScroll is the point at which the first stack task is bottom aligned with the
- // stack, so we offset from on the stack rect height.
- float stackOverscroll = (Math.max(0, stackScroll - mStackEndScrollP)) /
- mFreeformWorkspacePOffset;
- int overscrollYOffset = (int) (stackOverscroll * mFreeformWorkspaceOffset);
- transformOut.scale = 1f;
- transformOut.alpha = 1f;
- transformOut.translationY = mStackRect.height() + mFreeformWorkspaceGapOffset -
- overscrollYOffset;
- transformOut.rect.set(mFreeformRect);
- transformOut.rect.offset(0, transformOut.translationY);
- Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
- transformOut.visible = true;
- }
- return transformOut;
- }
-
- /**
- * Returns the preferred maximum scroll position for a stack at the given {@param scroll}.
- */
- public float getPreferredMaxScrollPosition(float scroll) {
- float maxStackScrollBounds = mStackEndScrollP + SNAP_TO_MAX_STACK_SCROLL_FACTOR *
- (mMaxScrollP - mStackEndScrollP);
- if (scroll < maxStackScrollBounds) {
- return mPreferredStackEndScrollP;
- }
- return mMaxScrollP;
- }
-
- /**
* Returns the untransformed task view bounds.
*/
public Rect getUntransformedTaskViewBounds() {
@@ -604,7 +517,7 @@ public class TaskStackLayoutAlgorithm {
* screen along the arc-length proportionally (1/arclength).
*/
public float getDeltaPForY(int downY, int y) {
- float deltaP = (float) (y - downY) / mStackRect.height() * (1f / sCurve.getArcLength());
+ float deltaP = (float) (y - downY) / mCurrentStackRect.height() * (1f / sCurve.getArcLength());
return -deltaP;
}
@@ -613,7 +526,7 @@ public class TaskStackLayoutAlgorithm {
* of the curve, map back to the screen y.
*/
public int getYForDeltaP(float downScrollP, float p) {
- int y = (int) ((p - downScrollP) * mStackRect.height() * sCurve.getArcLength());
+ int y = (int) ((p - downScrollP) * mCurrentStackRect.height() * sCurve.getArcLength());
return -y;
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index b266eaacae10..14d75a85000d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -58,7 +58,7 @@ import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
-import static android.app.ActivityManager.INVALID_STACK_ID;
+import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
/* The visual representation of a task stack view */
@@ -281,6 +281,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
/**
* Gets the stack transforms of a list of tasks, and returns the visible range of tasks.
+ * This call ignores freeform tasks.
*/
private boolean updateStackTransforms(ArrayList<TaskViewTransform> taskTransforms,
ArrayList<Task> tasks,
@@ -306,8 +307,16 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
// Update the stack transforms
TaskViewTransform prevTransform = null;
for (int i = taskCount - 1; i >= 0; i--) {
- TaskViewTransform transform = mLayoutAlgorithm.getStackTransform(tasks.get(i),
- stackScroll, taskTransforms.get(i), prevTransform);
+ Task task = tasks.get(i);
+ if (task.isFreeformTask()) {
+ continue;
+ }
+
+ TaskViewTransform transform = mLayoutAlgorithm.getStackTransform(task, stackScroll,
+ taskTransforms.get(i), prevTransform);
+ if (DEBUG) {
+ Log.d(TAG, "updateStackTransform: " + i + ", " + transform.visible);
+ }
if (transform.visible) {
if (frontMostVisibleIndex < 0) {
frontMostVisibleIndex = i;
@@ -327,7 +336,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
if (boundTranslationsToRect) {
transform.translationY = Math.min(transform.translationY,
- mLayoutAlgorithm.mStackRect.bottom);
+ mLayoutAlgorithm.mCurrentStackRect.bottom);
}
prevTransform = transform;
}
@@ -344,13 +353,13 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
// Get all the task transforms
ArrayList<Task> tasks = mStack.getTasks();
float stackScroll = mStackScroller.getStackScroll();
- int[] visibleRange = mTmpVisibleRange;
- boolean isValidVisibleRange = updateStackTransforms(mCurrentTaskTransforms, tasks,
- stackScroll, visibleRange, false);
+ int[] visibleStackRange = mTmpVisibleRange;
+ boolean isValidVisibleStackRange = updateStackTransforms(mCurrentTaskTransforms, tasks,
+ stackScroll, visibleStackRange, false);
boolean hasStackBackTransform = false;
boolean hasStackFrontTransform = false;
if (DEBUG) {
- Log.d(TAG, "visibleRange: " + visibleRange[0] + " to " + visibleRange[1]);
+ Log.d(TAG, "visibleRange: " + visibleStackRange[0] + " to " + visibleStackRange[1]);
}
// Return all the invisible children to the pool
@@ -363,7 +372,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
TaskView tv = taskViews.get(i);
Task task = tv.getTask();
int taskIndex = mStack.indexOfTask(task);
- if (visibleRange[1] <= taskIndex && taskIndex <= visibleRange[0]) {
+ if (task.isFreeformTask() ||
+ visibleStackRange[1] <= taskIndex && taskIndex <= visibleStackRange[0]) {
mTmpTaskViewMap.put(task, tv);
} else {
if (tv.isFocusedTask()) {
@@ -371,16 +381,43 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
lastFocusedTaskIndex = taskIndex;
resetFocusedTask();
}
+ if (DEBUG) {
+ Log.d(TAG, "returning to pool: " + task.key);
+ }
mViewPool.returnViewToPool(tv);
}
}
+ // Pick up all the freeform tasks
+ int firstVisStackIndex = isValidVisibleStackRange ? visibleStackRange[0] : 0;
+ for (int i = mStack.getTaskCount() - 1; i > firstVisStackIndex; i--) {
+ Task task = tasks.get(i);
+ if (!task.isFreeformTask()) {
+ continue;
+ }
+ TaskViewTransform transform = mLayoutAlgorithm.getStackTransform(task, stackScroll,
+ mCurrentTaskTransforms.get(i), null);
+ TaskView tv = mTmpTaskViewMap.get(task);
+ if (tv == null) {
+ if (DEBUG) {
+ Log.d(TAG, "picking up from pool: " + task.key);
+ }
+ tv = mViewPool.pickUpViewFromPool(task, task);
+ if (mLayersDisabled) {
+ tv.disableLayersForOneFrame();
+ }
+ }
+
+ // Animate the task into place
+ tv.updateViewPropertiesToTaskTransform(transform,
+ mStackViewsAnimationDuration, mRequestUpdateClippingListener);
+ }
+
// Pick up all the newly visible children and update all the existing children
- for (int i = visibleRange[0]; isValidVisibleRange && i >= visibleRange[1]; i--) {
+ for (int i = visibleStackRange[0]; isValidVisibleStackRange && i >= visibleStackRange[1]; i--) {
Task task = tasks.get(i);
TaskViewTransform transform = mCurrentTaskTransforms.get(i);
TaskView tv = mTmpTaskViewMap.get(task);
- int taskIndex = mStack.indexOfTask(task);
if (tv == null) {
tv = mViewPool.pickUpViewFromPool(task, task);
@@ -409,29 +446,19 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
// Animate the task into place
- tv.updateViewPropertiesToTaskTransform(mCurrentTaskTransforms.get(taskIndex),
+ tv.updateViewPropertiesToTaskTransform(transform,
mStackViewsAnimationDuration, mRequestUpdateClippingListener);
}
// Update the focus if the previous focused task was returned to the view pool
if (lastFocusedTaskIndex != -1) {
- if (lastFocusedTaskIndex < visibleRange[1]) {
- setFocusedTask(visibleRange[1], false, wasLastFocusedTaskAnimated);
+ if (lastFocusedTaskIndex < visibleStackRange[1]) {
+ setFocusedTask(visibleStackRange[1], false, wasLastFocusedTaskAnimated);
} else {
- setFocusedTask(visibleRange[0], false, wasLastFocusedTaskAnimated);
+ setFocusedTask(visibleStackRange[0], false, wasLastFocusedTaskAnimated);
}
}
- // Update the freeform workspace
- mLayoutAlgorithm.getFreeformWorkspaceBounds(stackScroll, mTmpTransform);
- if (mTmpTransform.visible) {
- mTmpTransform.rect.roundOut(mTmpRect);
- mFreeformWorkspaceBackground.setAlpha(255);
- mFreeformWorkspaceBackground.setBounds(mTmpRect);
- } else {
- mFreeformWorkspaceBackground.setAlpha(0);
- }
-
// Reset the request-synchronize params
mStackViewsAnimationDuration = 0;
mStackViewsDirty = false;
@@ -491,6 +518,14 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
// Compute the min and max scroll values
mLayoutAlgorithm.update(mStack);
+ // Update the freeform workspace
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ if (ssp.hasFreeformWorkspaceSupport()) {
+ mTmpRect.set(mLayoutAlgorithm.mFreeformRect);
+ mFreeformWorkspaceBackground.setAlpha(255);
+ mFreeformWorkspaceBackground.setBounds(mTmpRect);
+ }
+
// Debug logging
if (boundScrollToNewMinMax) {
mStackScroller.boundScroll();
@@ -762,7 +797,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
/** Handler for the first layout. */
void onFirstLayout() {
- int offscreenY = mLayoutAlgorithm.mStackRect.bottom;
+ int offscreenY = mLayoutAlgorithm.mCurrentStackRect.bottom;
// Find the launch target task
Task launchTargetTask = mStack.getLaunchTarget();
@@ -863,7 +898,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
mStackScroller.stopScroller();
mStackScroller.stopBoundScrollAnimation();
// Animate all the task views out of view
- ctx.offscreenTranslationY = mLayoutAlgorithm.mStackRect.bottom;
+ ctx.offscreenTranslationY = mLayoutAlgorithm.mCurrentStackRect.bottom;
List<TaskView> taskViews = getTaskViews();
int taskViewCount = taskViews.size();
@@ -929,6 +964,18 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
}
+ /**
+ * Launches the freeform tasks.
+ */
+ public boolean launchFreeformTasks() {
+ Task frontTask = mStack.getFrontMostTask();
+ if (frontTask != null && frontTask.isFreeformTask()) {
+ onTaskViewClicked(getChildViewForTask(frontTask), frontTask, false);
+ return true;
+ }
+ return false;
+ }
+
/**** TaskStackCallbacks Implementation ****/
@Override
@@ -975,6 +1022,19 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
// Animate all the tasks into place
requestSynchronizeStackViewsWithModel(200);
+ } else {
+ // Remove the view associated with this task, we can't rely on updateTransforms
+ // to work here because the task is no longer in the list
+ TaskView tv = getChildViewForTask(removedTask);
+ if (tv != null) {
+ mViewPool.returnViewToPool(tv);
+ }
+
+ // Update the min/max scroll and animate other task views into their new positions
+ updateMinMaxScroll(true);
+
+ // Animate all the tasks into place
+ requestSynchronizeStackViewsWithModel(200);
}
// Update the new front most task
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
index 6b92aeda1562..3a2ed0fa7d75 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
@@ -122,7 +122,7 @@ public class TaskStackViewScroller {
/** Returns the bounded stack scroll */
float getBoundedStackScroll(float scroll) {
return Math.max(mLayoutAlgorithm.mMinScrollP,
- Math.min(mLayoutAlgorithm.getPreferredMaxScrollPosition(scroll), scroll));
+ Math.min(mLayoutAlgorithm.mMaxScrollP, scroll));
}
/** Returns the amount that the absolute value of how much the scroll is out of bounds. */
@@ -194,7 +194,7 @@ public class TaskStackViewScroller {
// TODO: Remove
@Deprecated
int progressToScrollRange(float p) {
- return (int) (p * mLayoutAlgorithm.mStackRect.height());
+ return (int) (p * mLayoutAlgorithm.mCurrentStackRect.height());
}
/** Called from the view draw, computes the next scroll. */
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
index 9e6fb7bae4b9..59c9708840e2 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
@@ -19,6 +19,7 @@ package com.android.systemui.recents.views;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.Resources;
+import android.graphics.Rect;
import android.util.Log;
import android.view.InputDevice;
import android.view.MotionEvent;
@@ -29,9 +30,11 @@ import android.view.ViewParent;
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.R;
import com.android.systemui.recents.Constants;
+import com.android.systemui.recents.Recents;
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.HideRecentsEvent;
import com.android.systemui.recents.events.ui.DismissTaskViewEvent;
+import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.misc.Utilities;
import com.android.systemui.statusbar.FlingAnimationUtils;
@@ -222,49 +225,12 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
int activePointerIndex = ev.findPointerIndex(mActivePointerId);
int y = (int) ev.getY(activePointerIndex);
int velocity = (int) mVelocityTracker.getYVelocity(mActivePointerId);
- float curScrollP = mScroller.getStackScroll();
if (mIsScrolling) {
- boolean hasFreeformTasks = mSv.mStack.hasFreeformTasks();
- if (hasFreeformTasks && velocity > 0 &&
- curScrollP > layoutAlgorithm.mStackEndScrollP) {
- // Snap to workspace
- float finalY = mDownY + layoutAlgorithm.getYForDeltaP(mDownScrollP,
- layoutAlgorithm.mPreferredStackEndScrollP);
- mScrollFlingAnimator = ValueAnimator.ofInt(y, (int) finalY);
- mScrollFlingAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- float deltaP = layoutAlgorithm.getDeltaPForY(mDownY,
- (Integer) animation.getAnimatedValue());
- float scroll = mDownScrollP + deltaP;
- mScroller.setStackScroll(scroll);
- }
- });
- mFlingAnimUtils.apply(mScrollFlingAnimator, y, finalY, velocity);
- mScrollFlingAnimator.start();
- } else if (hasFreeformTasks && velocity < 0 &&
- curScrollP > (layoutAlgorithm.mStackEndScrollP -
- layoutAlgorithm.mTaskHalfHeightPOffset)) {
- // Snap to stack
- float finalY = mDownY + layoutAlgorithm.getYForDeltaP(mDownScrollP,
- layoutAlgorithm.mMaxScrollP);
- mScrollFlingAnimator = ValueAnimator.ofInt(y, (int) finalY);
- mScrollFlingAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- float deltaP = layoutAlgorithm.getDeltaPForY(mDownY,
- (Integer) animation.getAnimatedValue());
- float scroll = mDownScrollP + deltaP;
- mScroller.setStackScroll(scroll);
- }
- });
- mFlingAnimUtils.apply(mScrollFlingAnimator, y, finalY, velocity);
- mScrollFlingAnimator.start();
- } else if (mScroller.isScrollOutOfBounds()) {
+ if (mScroller.isScrollOutOfBounds()) {
mScroller.animateBoundScroll();
} else if (Math.abs(velocity) > mMinimumVelocity) {
float minY = mDownY + layoutAlgorithm.getYForDeltaP(mDownScrollP,
- layoutAlgorithm.mPreferredStackEndScrollP);
+ layoutAlgorithm.mMaxScrollP);
float maxY = mDownY + layoutAlgorithm.getYForDeltaP(mDownScrollP,
layoutAlgorithm.mMinScrollP);
mScroller.fling(mDownScrollP, mDownY, y, velocity, (int) minY, (int) maxY,
@@ -313,6 +279,17 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
return;
}
+ // If tapping on the freeform workspace background, just launch the first freeform task
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ if (ssp.hasFreeformWorkspaceSupport()) {
+ Rect freeformRect = mSv.mLayoutAlgorithm.mFreeformRect;
+ if (freeformRect.top <= y && y <= freeformRect.bottom) {
+ if (mSv.launchFreeformTasks()) {
+ return;
+ }
+ }
+ }
+
// The user intentionally tapped on the background, which is like a tap on the "desktop".
// Hide recents and transition to the launcher.
EventBus.getDefault().send(new HideRecentsEvent(false, true));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarApps.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarApps.java
index 4ac2c3185b6d..a51f62ad0b69 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarApps.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarApps.java
@@ -16,7 +16,7 @@
package com.android.systemui.statusbar.phone;
-import static android.app.ActivityManager.INVALID_STACK_ID;
+import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
import android.animation.LayoutTransition;
import android.annotation.Nullable;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index ebe77853c1b2..fafedc33f0b1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -128,7 +128,6 @@ public abstract class PanelView extends FrameLayout {
};
protected void onExpandingFinished() {
- endClosing();
mBar.onExpandingFinished();
}
@@ -143,6 +142,7 @@ public abstract class PanelView extends FrameLayout {
}
protected final void notifyExpandingFinished() {
+ endClosing();
if (mExpanding) {
mExpanding = false;
onExpandingFinished();
diff --git a/services/core/java/com/android/server/MountService.java b/services/core/java/com/android/server/MountService.java
index 85187c776401..a7879c64bc13 100644
--- a/services/core/java/com/android/server/MountService.java
+++ b/services/core/java/com/android/server/MountService.java
@@ -562,6 +562,8 @@ class MountService extends IMountService.Stub
private static final int H_VOLUME_BROADCAST = 6;
private static final int H_INTERNAL_BROADCAST = 7;
private static final int H_VOLUME_UNMOUNT = 8;
+ private static final int H_PARTITION_FORGET = 9;
+ private static final int H_RESET = 10;
class MountServiceHandler extends Handler {
public MountServiceHandler(Looper looper) {
@@ -669,6 +671,16 @@ class MountService extends IMountService.Stub
final Intent intent = (Intent) msg.obj;
mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
android.Manifest.permission.WRITE_MEDIA_STORAGE);
+ break;
+ }
+ case H_PARTITION_FORGET: {
+ final String partGuid = (String) msg.obj;
+ forgetPartition(partGuid);
+ break;
+ }
+ case H_RESET: {
+ resetIfReadyAndConnected();
+ break;
}
}
}
@@ -753,9 +765,7 @@ class MountService extends IMountService.Stub
}
private void handleSystemReady() {
- synchronized (mLock) {
- resetIfReadyAndConnectedLocked();
- }
+ resetIfReadyAndConnected();
// Start scheduling nominally-daily fstrim operations
MountServiceIdler.scheduleIdlePass(mContext);
@@ -793,7 +803,7 @@ class MountService extends IMountService.Stub
}
}
- private void addInternalVolume() {
+ private void addInternalVolumeLocked() {
// Create a stub volume that represents internal storage
final VolumeInfo internal = new VolumeInfo(VolumeInfo.ID_PRIVATE_INTERNAL,
VolumeInfo.TYPE_PRIVATE, null, null);
@@ -802,18 +812,22 @@ class MountService extends IMountService.Stub
mVolumes.put(internal.id, internal);
}
- private void resetIfReadyAndConnectedLocked() {
+ private void resetIfReadyAndConnected() {
Slog.d(TAG, "Thinking about reset, mSystemReady=" + mSystemReady
+ ", mDaemonConnected=" + mDaemonConnected);
if (mSystemReady && mDaemonConnected) {
- final UserManager um = UserManager.get(mContext);
- final List<UserInfo> users = um.getUsers();
+ final List<UserInfo> users = mContext.getSystemService(UserManager.class).getUsers();
killMediaProvider(users);
- mDisks.clear();
- mVolumes.clear();
+ final int[] startedUsers;
+ synchronized (mLock) {
+ startedUsers = mStartedUsers;
- addInternalVolume();
+ mDisks.clear();
+ mVolumes.clear();
+
+ addInternalVolumeLocked();
+ }
try {
mConnector.execute("volume", "reset");
@@ -822,7 +836,7 @@ class MountService extends IMountService.Stub
for (UserInfo user : users) {
mConnector.execute("volume", "user_added", user.id, user.serialNumber);
}
- for (int userId : mStartedUsers) {
+ for (int userId : startedUsers) {
mConnector.execute("volume", "user_started", userId);
}
} catch (NativeDaemonConnectorException e) {
@@ -898,9 +912,7 @@ class MountService extends IMountService.Stub
}
private void handleDaemonConnected() {
- synchronized (mLock) {
- resetIfReadyAndConnectedLocked();
- }
+ resetIfReadyAndConnected();
/*
* Now that we've done our initialization, release
@@ -1428,6 +1440,7 @@ class MountService extends IMountService.Stub
mConnector = new NativeDaemonConnector(this, "vold", MAX_CONTAINERS * 2, VOLD_TAG, 25,
null);
mConnector.setDebug(true);
+ mConnector.setWarnIfHeld(mLock);
Thread thread = new Thread(mConnector, VOLD_TAG);
thread.start();
@@ -1445,7 +1458,9 @@ class MountService extends IMountService.Stub
userFilter.addAction(Intent.ACTION_USER_REMOVED);
mContext.registerReceiver(mUserReceiver, userFilter, null, mHandler);
- addInternalVolume();
+ synchronized (mLock) {
+ addInternalVolumeLocked();
+ }
// Add ourself to the Watchdog monitors if enabled.
if (WATCHDOG_ENABLE) {
@@ -1791,10 +1806,11 @@ class MountService extends IMountService.Stub
waitForReady();
Preconditions.checkNotNull(fsUuid);
+
synchronized (mLock) {
final VolumeRecord rec = mRecords.remove(fsUuid);
if (rec != null && !TextUtils.isEmpty(rec.partGuid)) {
- forgetPartition(rec.partGuid);
+ mHandler.obtainMessage(H_PARTITION_FORGET, rec.partGuid).sendToTarget();
}
mCallbacks.notifyVolumeForgotten(fsUuid);
@@ -1802,7 +1818,7 @@ class MountService extends IMountService.Stub
// reset vold so we bind into new volume into place.
if (Objects.equals(mPrimaryStorageUuid, fsUuid)) {
mPrimaryStorageUuid = getDefaultPrimaryStorageUuid();
- resetIfReadyAndConnectedLocked();
+ mHandler.obtainMessage(H_RESET).sendToTarget();
}
writeSettingsLocked();
@@ -1819,7 +1835,7 @@ class MountService extends IMountService.Stub
final String fsUuid = mRecords.keyAt(i);
final VolumeRecord rec = mRecords.valueAt(i);
if (!TextUtils.isEmpty(rec.partGuid)) {
- forgetPartition(rec.partGuid);
+ mHandler.obtainMessage(H_PARTITION_FORGET, rec.partGuid).sendToTarget();
}
mCallbacks.notifyVolumeForgotten(fsUuid);
}
@@ -1830,7 +1846,7 @@ class MountService extends IMountService.Stub
}
writeSettingsLocked();
- resetIfReadyAndConnectedLocked();
+ mHandler.obtainMessage(H_RESET).sendToTarget();
}
}
@@ -1878,7 +1894,7 @@ class MountService extends IMountService.Stub
}
writeSettingsLocked();
- resetIfReadyAndConnectedLocked();
+ mHandler.obtainMessage(H_RESET).sendToTarget();
}
}
@@ -1915,7 +1931,7 @@ class MountService extends IMountService.Stub
Slog.d(TAG, "Skipping move to/from primary physical");
onMoveStatusLocked(MOVE_STATUS_COPY_FINISHED);
onMoveStatusLocked(PackageManager.MOVE_SUCCEEDED);
- resetIfReadyAndConnectedLocked();
+ mHandler.obtainMessage(H_RESET).sendToTarget();
} else {
final VolumeInfo from = findStorageForUuid(mPrimaryStorageUuid);
diff --git a/services/core/java/com/android/server/NativeDaemonConnector.java b/services/core/java/com/android/server/NativeDaemonConnector.java
index 519a2a3f4f88..e6b6074abf9c 100644
--- a/services/core/java/com/android/server/NativeDaemonConnector.java
+++ b/services/core/java/com/android/server/NativeDaemonConnector.java
@@ -28,6 +28,7 @@ import android.util.LocalLog;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
import com.google.android.collect.Lists;
import java.io.FileDescriptor;
@@ -57,6 +58,7 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdo
private LocalLog mLocalLog;
private volatile boolean mDebug = false;
+ private volatile Object mWarnIfHeld;
private final ResponseQueue mResponseQueue;
@@ -107,6 +109,15 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdo
mDebug = debug;
}
+ /**
+ * Yell loudly if someone tries making future {@link #execute(Command)}
+ * calls while holding a lock on the given object.
+ */
+ public void setWarnIfHeld(Object warnIfHeld) {
+ Preconditions.checkState(mWarnIfHeld == null);
+ mWarnIfHeld = Preconditions.checkNotNull(warnIfHeld);
+ }
+
@Override
public void run() {
mCallbackHandler = new Handler(mLooper, this);
@@ -394,6 +405,11 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdo
*/
public NativeDaemonEvent[] executeForList(long timeoutMs, String cmd, Object... args)
throws NativeDaemonConnectorException {
+ if (mWarnIfHeld != null && Thread.holdsLock(mWarnIfHeld)) {
+ Slog.wtf(TAG, "Calling thread " + Thread.currentThread().getName() + " is holding 0x"
+ + Integer.toHexString(System.identityHashCode(mWarnIfHeld)), new Throwable());
+ }
+
final long startTime = SystemClock.elapsedRealtime();
final ArrayList<NativeDaemonEvent> events = Lists.newArrayList();
diff --git a/services/core/java/com/android/server/NetworkTimeUpdateService.java b/services/core/java/com/android/server/NetworkTimeUpdateService.java
index a0d305cdf2c6..3f0664defbf9 100644
--- a/services/core/java/com/android/server/NetworkTimeUpdateService.java
+++ b/services/core/java/com/android/server/NetworkTimeUpdateService.java
@@ -30,6 +30,7 @@ import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
+import android.os.PowerManager;
import android.provider.Settings;
import android.util.Log;
import android.util.NtpTrustedTime;
@@ -75,6 +76,7 @@ public class NetworkTimeUpdateService {
private SettingsObserver mSettingsObserver;
// The last time that we successfully fetched the NTP time.
private long mLastNtpFetchTime = NOT_SET;
+ private final PowerManager.WakeLock mWakeLock;
// Normal polling frequency
private final long mPollingIntervalMs;
@@ -104,6 +106,9 @@ public class NetworkTimeUpdateService {
com.android.internal.R.integer.config_ntpRetry);
mTimeErrorThresholdMs = mContext.getResources().getInteger(
com.android.internal.R.integer.config_ntpThreshold);
+
+ mWakeLock = ((PowerManager) context.getSystemService(Context.POWER_SERVICE)).newWakeLock(
+ PowerManager.PARTIAL_WAKE_LOCK, TAG);
}
/** Initialize the receivers and initiate the first NTP request */
@@ -148,7 +153,15 @@ public class NetworkTimeUpdateService {
private void onPollNetworkTime(int event) {
// If Automatic time is not set, don't bother.
if (!isAutomaticTimeRequested()) return;
+ mWakeLock.acquire();
+ try {
+ onPollNetworkTimeUnderWakeLock(event);
+ } finally {
+ mWakeLock.release();
+ }
+ }
+ private void onPollNetworkTimeUnderWakeLock(int event) {
final long refTime = SystemClock.elapsedRealtime();
// If NITZ time was received less than mPollingIntervalMs time ago,
// no need to sync to NTP.
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index df6b1d63bee2..befaaef54e49 100755
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -337,7 +337,7 @@ public final class ActiveServices {
ServiceRecord r = res.record;
- if (!mAm.getUserManagerLocked().exists(r.userId)) {
+ if (!mAm.mUserController.exists(r.userId)) {
Slog.d(TAG, "Trying to start service with non-existent user! " + r.userId);
return null;
}
@@ -1030,8 +1030,8 @@ public final class ActiveServices {
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "retrieveServiceLocked: " + service
+ " type=" + resolvedType + " callingUid=" + callingUid);
- userId = mAm.handleIncomingUser(callingPid, callingUid, userId,
- false, ActivityManagerService.ALLOW_NON_FULL_IN_PROFILE, "service", null);
+ userId = mAm.mUserController.handleIncomingUser(callingPid, callingUid, userId, false,
+ ActivityManagerService.ALLOW_NON_FULL_IN_PROFILE, "service", null);
ServiceMap smap = getServiceMap(userId);
final ComponentName comp = service.getComponent();
@@ -2333,7 +2333,8 @@ public final class ActiveServices {
EventLog.writeEvent(EventLogTags.AM_SERVICE_CRASHED_TOO_MUCH,
sr.userId, sr.crashCount, sr.shortName, app.pid);
bringDownServiceLocked(sr);
- } else if (!allowRestart || !mAm.isUserRunningLocked(sr.userId, false)) {
+ } else if (!allowRestart
+ || !mAm.mUserController.isUserRunningLocked(sr.userId, false)) {
bringDownServiceLocked(sr);
} else {
boolean canceled = scheduleServiceRestartLocked(sr, true);
@@ -2446,7 +2447,7 @@ public final class ActiveServices {
if (ActivityManager.checkUidPermission(
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
uid) == PackageManager.PERMISSION_GRANTED) {
- int[] users = mAm.getUsersLocked();
+ int[] users = mAm.mUserController.getUsers();
for (int ui=0; ui<users.length && res.size() < maxNum; ui++) {
ArrayMap<ComponentName, ServiceRecord> alls = getServices(users[ui]);
for (int i=0; i<alls.size() && res.size() < maxNum; i++) {
@@ -2580,7 +2581,7 @@ public final class ActiveServices {
pw.print(mLastAnrDump);
pw.println();
}
- int[] users = mAm.getUsersLocked();
+ int[] users = mAm.mUserController.getUsers();
for (int user : users) {
ServiceMap smap = getServiceMap(user);
boolean printed = false;
@@ -2817,7 +2818,7 @@ public final class ActiveServices {
ArrayList<ServiceRecord> services = new ArrayList<ServiceRecord>();
synchronized (mAm) {
- int[] users = mAm.getUsersLocked();
+ int[] users = mAm.mUserController.getUsers();
if ("all".equals(name)) {
for (int user : users) {
ServiceMap smap = mServiceMap.get(user);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 2d7298bb31a4..546db3e2d68a 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -19,12 +19,11 @@ package com.android.server.am;
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
-import static android.app.ActivityManager.DOCKED_STACK_ID;
-import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.HOME_STACK_ID;
-import static android.app.ActivityManager.INVALID_STACK_ID;
-import static android.app.ActivityManager.PINNED_STACK_ID;
+import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
+import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.HOME_STACK_ID;
+import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
import static android.app.ActivityManager.RESIZE_MODE_PRESERVE_WINDOW;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static com.android.internal.util.XmlUtils.readBooleanAttribute;
@@ -47,6 +46,7 @@ import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
import static org.xmlpull.v1.XmlPullParser.START_TAG;
import android.Manifest;
+import android.app.ActivityManager.StackId;
import android.app.AppOpsManager;
import android.app.ApplicationThreadNative;
import android.app.BroadcastOptions;
@@ -207,7 +207,6 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.IPermissionController;
import android.os.IProcessInfoService;
-import android.os.IUserManager;
import android.os.Looper;
import android.os.Message;
import android.os.Parcel;
@@ -375,6 +374,13 @@ public final class ActivityManagerService extends ActivityManagerNative
// we will consider it to be doing interaction for usage stats.
static final int SERVICE_USAGE_INTERACTION_TIME = 30*60*1000;
+ // Maximum amount of time we will allow to elapse before re-reporting usage stats
+ // interaction with foreground processes.
+ static final long USAGE_STATS_INTERACTION_INTERVAL = 24*60*60*1000L;
+
+ // Maximum number of users we allow to be running at a time.
+ static final int MAX_RUNNING_USERS = 3;
+
// How long to wait in getAssistContextExtras for the activity and foreground services
// to respond with the result.
static final int PENDING_ASSIST_EXTRAS_TIMEOUT = 500;
@@ -1271,8 +1277,6 @@ public final class ActivityManagerService extends ActivityManagerNative
final ActivityThread mSystemThread;
- private UserManagerService mUserManager;
-
private final class AppDeathRecipient implements IBinder.DeathRecipient {
final ProcessRecord mApp;
final int mPid;
@@ -1400,7 +1404,7 @@ public final class ActivityManagerService extends ActivityManagerNative
boolean isBackground = (UserHandle.getAppId(proc.uid)
>= Process.FIRST_APPLICATION_UID
&& proc.pid != MY_PID);
- for (int userId : mUserController.mCurrentProfileIds) {
+ for (int userId : mUserController.getCurrentProfileIdsLocked()) {
isBackground &= (proc.userId != userId);
}
if (isBackground && !showBackground) {
@@ -1565,7 +1569,7 @@ public final class ActivityManagerService extends ActivityManagerNative
break;
}
case START_USER_SWITCH_MSG: {
- showUserSwitchDialog(msg.arg1, (String) msg.obj);
+ mUserController.showUserSwitchDialog(msg.arg1, (String) msg.obj);
break;
}
case DISMISS_DIALOG_MSG: {
@@ -3591,8 +3595,7 @@ public final class ActivityManagerService extends ActivityManagerNative
void enforceShellRestriction(String restriction, int userHandle) {
if (Binder.getCallingUid() == Process.SHELL_UID) {
- if (userHandle < 0
- || mUserManager.hasUserRestriction(restriction, userHandle)) {
+ if (userHandle < 0 || mUserController.hasUserRestriction(restriction, userHandle)) {
throw new SecurityException("Shell does not have permission to access user "
+ userHandle);
}
@@ -3854,8 +3857,8 @@ public final class ActivityManagerService extends ActivityManagerNative
Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
int startFlags, ProfilerInfo profilerInfo, Bundle options, int userId) {
enforceNotIsolatedCaller("startActivity");
- userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
- false, ALLOW_FULL_ONLY, "startActivity", null);
+ userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+ userId, false, ALLOW_FULL_ONLY, "startActivity", null);
// TODO: Switch to user app stacks here.
return mStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent,
resolvedType, null, null, resultTo, resultWho, requestCode, startFlags,
@@ -3945,8 +3948,8 @@ public final class ActivityManagerService extends ActivityManagerNative
Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
int startFlags, ProfilerInfo profilerInfo, Bundle options, int userId) {
enforceNotIsolatedCaller("startActivityAndWait");
- userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
- false, ALLOW_FULL_ONLY, "startActivityAndWait", null);
+ userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+ userId, false, ALLOW_FULL_ONLY, "startActivityAndWait", null);
WaitResult res = new WaitResult();
// TODO: Switch to user app stacks here.
mStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent, resolvedType,
@@ -3960,8 +3963,8 @@ public final class ActivityManagerService extends ActivityManagerNative
Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
int startFlags, Configuration config, Bundle options, int userId) {
enforceNotIsolatedCaller("startActivityWithConfig");
- userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
- false, ALLOW_FULL_ONLY, "startActivityWithConfig", null);
+ userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+ userId, false, ALLOW_FULL_ONLY, "startActivityWithConfig", null);
// TODO: Switch to user app stacks here.
int ret = mStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent,
resolvedType, null, null, resultTo, resultWho, requestCode, startFlags,
@@ -4018,8 +4021,8 @@ public final class ActivityManagerService extends ActivityManagerNative
if (session == null || interactor == null) {
throw new NullPointerException("null session or interactor");
}
- userId = handleIncomingUser(callingPid, callingUid, userId,
- false, ALLOW_FULL_ONLY, "startVoiceActivity", null);
+ userId = mUserController.handleIncomingUser(callingPid, callingUid, userId, false,
+ ALLOW_FULL_ONLY, "startVoiceActivity", null);
// TODO: Switch to user app stacks here.
return mStackSupervisor.startActivityMayWait(null, callingUid, callingPackage, intent,
resolvedType, session, interactor, null, null, 0, startFlags, profilerInfo, null,
@@ -4089,8 +4092,8 @@ public final class ActivityManagerService extends ActivityManagerNative
if (debug) {
Slog.v(TAG, "Next matching activity: found current " + r.packageName
+ "/" + r.info.name);
- Slog.v(TAG, "Next matching activity: next is " + aInfo.packageName
- + "/" + aInfo.name);
+ Slog.v(TAG, "Next matching activity: next is " + ((aInfo == null)
+ ? "null" : aInfo.packageName + "/" + aInfo.name));
}
break;
}
@@ -4207,8 +4210,8 @@ public final class ActivityManagerService extends ActivityManagerNative
String resultWho, int requestCode, int startFlags, Bundle options, int userId,
IActivityContainer container, TaskRecord inTask) {
- userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
- false, ALLOW_FULL_ONLY, "startActivityInPackage", null);
+ userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+ userId, false, ALLOW_FULL_ONLY, "startActivityInPackage", null);
// TODO: Switch to user app stacks here.
int ret = mStackSupervisor.startActivityMayWait(null, uid, callingPackage, intent,
@@ -4222,8 +4225,8 @@ public final class ActivityManagerService extends ActivityManagerNative
Intent[] intents, String[] resolvedTypes, IBinder resultTo, Bundle options,
int userId) {
enforceNotIsolatedCaller("startActivities");
- userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
- false, ALLOW_FULL_ONLY, "startActivity", null);
+ userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+ userId, false, ALLOW_FULL_ONLY, "startActivity", null);
// TODO: Switch to user app stacks here.
int ret = mStackSupervisor.startActivities(caller, -1, callingPackage, intents,
resolvedTypes, resultTo, options, userId);
@@ -4234,8 +4237,8 @@ public final class ActivityManagerService extends ActivityManagerNative
Intent[] intents, String[] resolvedTypes, IBinder resultTo,
Bundle options, int userId) {
- userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
- false, ALLOW_FULL_ONLY, "startActivityInPackage", null);
+ userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+ userId, false, ALLOW_FULL_ONLY, "startActivityInPackage", null);
// TODO: Switch to user app stacks here.
int ret = mStackSupervisor.startActivities(null, uid, callingPackage, intents, resolvedTypes,
resultTo, options, userId);
@@ -5136,8 +5139,8 @@ public final class ActivityManagerService extends ActivityManagerNative
}
int uid = Binder.getCallingUid();
int pid = Binder.getCallingPid();
- userId = handleIncomingUser(pid, uid,
- userId, false, ALLOW_FULL_ONLY, "clearApplicationUserData", null);
+ userId = mUserController.handleIncomingUser(pid, uid, userId, false,
+ ALLOW_FULL_ONLY, "clearApplicationUserData", null);
long callingId = Binder.clearCallingIdentity();
try {
IPackageManager pm = AppGlobals.getPackageManager();
@@ -5216,7 +5219,7 @@ public final class ActivityManagerService extends ActivityManagerNative
throw new SecurityException(msg);
}
- userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+ userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
userId, true, ALLOW_FULL_ONLY, "killBackgroundProcesses", null);
long callingId = Binder.clearCallingIdentity();
try {
@@ -5299,14 +5302,14 @@ public final class ActivityManagerService extends ActivityManagerNative
throw new SecurityException(msg);
}
final int callingPid = Binder.getCallingPid();
- userId = handleIncomingUser(callingPid, Binder.getCallingUid(),
+ userId = mUserController.handleIncomingUser(callingPid, Binder.getCallingUid(),
userId, true, ALLOW_FULL_ONLY, "forceStopPackage", null);
long callingId = Binder.clearCallingIdentity();
try {
IPackageManager pm = AppGlobals.getPackageManager();
synchronized(this) {
int[] users = userId == UserHandle.USER_ALL
- ? getUsersLocked() : new int[] { userId };
+ ? mUserController.getUsers() : new int[] { userId };
for (int user : users) {
int pkgUid = -1;
try {
@@ -5324,7 +5327,7 @@ public final class ActivityManagerService extends ActivityManagerNative
Slog.w(TAG, "Failed trying to unstop package "
+ packageName + ": " + e);
}
- if (isUserRunningLocked(user, false)) {
+ if (mUserController.isUserRunningLocked(user, false)) {
forceStopPackageLocked(packageName, pkgUid, "from pid " + callingPid);
}
}
@@ -6662,7 +6665,7 @@ public final class ActivityManagerService extends ActivityManagerNative
synchronized(this) {
int callingUid = Binder.getCallingUid();
int origUserId = userId;
- userId = handleIncomingUser(Binder.getCallingPid(), callingUid, userId,
+ userId = mUserController.handleIncomingUser(Binder.getCallingPid(), callingUid, userId,
type == ActivityManager.INTENT_SENDER_BROADCAST,
ALLOW_NON_FULL, "getIntentSender", null);
if (origUserId == UserHandle.USER_CURRENT) {
@@ -7910,8 +7913,9 @@ public final class ActivityManagerService extends ActivityManagerNative
@Override
public void grantUriPermissionFromOwner(IBinder token, int fromUid, String targetPkg, Uri uri,
final int modeFlags, int sourceUserId, int targetUserId) {
- targetUserId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
- targetUserId, false, ALLOW_FULL_ONLY, "grantUriPermissionFromOwner", null);
+ targetUserId = mUserController.handleIncomingUser(Binder.getCallingPid(),
+ Binder.getCallingUid(), targetUserId, false, ALLOW_FULL_ONLY,
+ "grantUriPermissionFromOwner", null);
synchronized(this) {
UriPermissionOwner owner = UriPermissionOwner.fromExternalToken(token);
if (owner == null) {
@@ -8418,7 +8422,7 @@ public final class ActivityManagerService extends ActivityManagerNative
@Override
public List<ActivityManager.RecentTaskInfo> getRecentTasks(int maxNum, int flags, int userId) {
final int callingUid = Binder.getCallingUid();
- userId = handleIncomingUser(Binder.getCallingPid(), callingUid, userId,
+ userId = mUserController.handleIncomingUser(Binder.getCallingPid(), callingUid, userId,
false, ALLOW_FULL_ONLY, "getRecentTasks", null);
final boolean includeProfiles = (flags & ActivityManager.RECENT_INCLUDE_PROFILES) != 0;
@@ -8436,7 +8440,7 @@ public final class ActivityManagerService extends ActivityManagerNative
final Set<Integer> includedUsers;
if (includeProfiles) {
- includedUsers = getProfileIdsLocked(userId);
+ includedUsers = mUserController.getProfileIds(userId);
} else {
includedUsers = new HashSet<>();
}
@@ -8654,12 +8658,9 @@ public final class ActivityManagerService extends ActivityManagerNative
// - a non-null bounds on a non-freeform (fullscreen OR docked) task moves
// that task to freeform
// - otherwise the task is not moved
- // Note it's not allowed to resize a home, docked, or pinned stack task.
int stackId = task.stack.mStackId;
- if (stackId == HOME_STACK_ID || stackId == DOCKED_STACK_ID
- || stackId == PINNED_STACK_ID) {
- throw new IllegalArgumentException("trying to resizeTask on a "
- + "home or docked task");
+ if (!StackId.isTaskResizeAllowed(stackId)) {
+ throw new IllegalArgumentException("resizeTask not allowed on task=" + task);
}
if (bounds == null && stackId == FREEFORM_WORKSPACE_STACK_ID) {
stackId = FULLSCREEN_WORKSPACE_STACK_ID;
@@ -9461,16 +9462,15 @@ public final class ActivityManagerService extends ActivityManagerNative
boolean checkedGrants = false;
if (checkUser) {
// Looking for cross-user grants before enforcing the typical cross-users permissions
- int tmpTargetUserId = unsafeConvertIncomingUserLocked(userId);
+ int tmpTargetUserId = mUserController.unsafeConvertIncomingUserLocked(userId);
if (tmpTargetUserId != UserHandle.getUserId(callingUid)) {
if (checkAuthorityGrants(callingUid, cpi, tmpTargetUserId, checkUser)) {
return null;
}
checkedGrants = true;
}
- userId = handleIncomingUser(callingPid, callingUid, userId,
- false, ALLOW_NON_FULL,
- "checkContentProviderPermissionLocked " + cpi.authority, null);
+ userId = mUserController.handleIncomingUser(callingPid, callingUid, userId, false,
+ ALLOW_NON_FULL, "checkContentProviderPermissionLocked " + cpi.authority, null);
if (userId != tmpTargetUserId) {
// When we actually went to determine the final targer user ID, this ended
// up different than our initial check for the authority. This is because
@@ -9809,7 +9809,7 @@ public final class ActivityManagerService extends ActivityManagerNative
// Make sure that the user who owns this provider is running. If not,
// we don't want to allow it to run.
- if (!isUserRunningLocked(userId, false)) {
+ if (!mUserController.isUserRunningLocked(userId, false)) {
Slog.w(TAG, "Unable to launch app "
+ cpi.applicationInfo.packageName + "/"
+ cpi.applicationInfo.uid + " for provider "
@@ -9995,8 +9995,8 @@ public final class ActivityManagerService extends ActivityManagerNative
String name, int userId, IBinder token) {
enforceCallingPermission(android.Manifest.permission.ACCESS_CONTENT_PROVIDERS_EXTERNALLY,
"Do not have permission in call getContentProviderExternal()");
- userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
- false, ALLOW_FULL_ONLY, "getContentProvider", null);
+ userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+ userId, false, ALLOW_FULL_ONLY, "getContentProvider", null);
return getContentProviderExternalUnchecked(name, token, userId);
}
@@ -10308,7 +10308,7 @@ public final class ActivityManagerService extends ActivityManagerNative
long ident = 0;
boolean clearedIdentity = false;
synchronized (this) {
- userId = unsafeConvertIncomingUserLocked(userId);
+ userId = mUserController.unsafeConvertIncomingUserLocked(userId);
}
if (canClearIdentity(callingPid, callingUid, userId)) {
clearedIdentity = true;
@@ -10978,7 +10978,7 @@ public final class ActivityManagerService extends ActivityManagerNative
public boolean isAssistDataAllowedOnCurrentActivity() {
int userId;
synchronized (this) {
- userId = mUserController.mCurrentUserId;
+ userId = mUserController.getCurrentUserIdLocked();
ActivityRecord activity = getFocusedStack().topActivity();
if (activity == null) {
return false;
@@ -11893,7 +11893,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
// TODO: can we still do this with per user encryption?
- final int[] users = getUsersLocked();
+ final int[] users = mUserController.getUsers();
if (users.length <= 0) {
return false;
}
@@ -11923,8 +11923,7 @@ public final class ActivityManagerService extends ActivityManagerNative
mUserController.updateCurrentProfileIdsLocked();
mRecentTasks.clear();
- mRecentTasks.addAll(mTaskPersister.restoreTasksLocked(
- getUserManagerLocked().getUserIds()));
+ mRecentTasks.addAll(mTaskPersister.restoreTasksLocked(mUserController.getUserIds()));
mRecentTasks.cleanupLocked(UserHandle.USER_ALL);
mTaskPersister.startPersisting();
@@ -12029,7 +12028,7 @@ public final class ActivityManagerService extends ActivityManagerNative
loadResourcesOnSystemReady();
final int currentUserId;
synchronized (this) {
- currentUserId = mUserController.mCurrentUserId;
+ currentUserId = mUserController.getCurrentUserIdLocked();
readGrantedUriPermissionsLocked();
}
@@ -12113,7 +12112,7 @@ public final class ActivityManagerService extends ActivityManagerNative
Binder.restoreCallingIdentity(ident);
}
mStackSupervisor.resumeTopActivitiesLocked();
- sendUserSwitchBroadcastsLocked(-1, currentUserId);
+ mUserController.sendUserSwitchBroadcastsLocked(-1, currentUserId);
}
}
@@ -12313,7 +12312,7 @@ public final class ActivityManagerService extends ActivityManagerNative
// launching the report UI under a different user.
app.errorReportReceiver = null;
- for (int userId : mUserController.mCurrentProfileIds) {
+ for (int userId : mUserController.getCurrentProfileIdsLocked()) {
if (app.userId == userId) {
app.errorReportReceiver = ApplicationErrorReport.getErrorReportReceiver(
mContext, app.info.packageName, app.info.flags);
@@ -16034,97 +16033,10 @@ public final class ActivityManagerService extends ActivityManagerNative
@Override
public int handleIncomingUser(int callingPid, int callingUid, int userId, boolean allowAll,
boolean requireFull, String name, String callerPackage) {
- return handleIncomingUser(callingPid, callingUid, userId, allowAll,
+ return mUserController.handleIncomingUser(callingPid, callingUid, userId, allowAll,
requireFull ? ALLOW_FULL_ONLY : ALLOW_NON_FULL, name, callerPackage);
}
- int unsafeConvertIncomingUserLocked(int userId) {
- return (userId == UserHandle.USER_CURRENT || userId == UserHandle.USER_CURRENT_OR_SELF)
- ? mUserController.mCurrentUserId : userId;
- }
-
- int handleIncomingUser(int callingPid, int callingUid, int userId, boolean allowAll,
- int allowMode, String name, String callerPackage) {
- final int callingUserId = UserHandle.getUserId(callingUid);
- if (callingUserId == userId) {
- return userId;
- }
-
- // Note that we may be accessing mCurrentUserId outside of a lock...
- // shouldn't be a big deal, if this is being called outside
- // of a locked context there is intrinsically a race with
- // the value the caller will receive and someone else changing it.
- // We assume that USER_CURRENT_OR_SELF will use the current user; later
- // we will switch to the calling user if access to the current user fails.
- int targetUserId = unsafeConvertIncomingUserLocked(userId);
-
- if (callingUid != 0 && callingUid != Process.SYSTEM_UID) {
- final boolean allow;
- if (checkComponentPermission(INTERACT_ACROSS_USERS_FULL, callingPid,
- callingUid, -1, true) == PackageManager.PERMISSION_GRANTED) {
- // If the caller has this permission, they always pass go. And collect $200.
- allow = true;
- } else if (allowMode == ALLOW_FULL_ONLY) {
- // We require full access, sucks to be you.
- allow = false;
- } else if (checkComponentPermission(INTERACT_ACROSS_USERS, callingPid,
- callingUid, -1, true) != PackageManager.PERMISSION_GRANTED) {
- // If the caller does not have either permission, they are always doomed.
- allow = false;
- } else if (allowMode == ALLOW_NON_FULL) {
- // We are blanket allowing non-full access, you lucky caller!
- allow = true;
- } else if (allowMode == ALLOW_NON_FULL_IN_PROFILE) {
- // We may or may not allow this depending on whether the two users are
- // in the same profile.
- allow = mUserController.isSameProfileGroup(callingUserId, targetUserId);
- } else {
- throw new IllegalArgumentException("Unknown mode: " + allowMode);
- }
- if (!allow) {
- if (userId == UserHandle.USER_CURRENT_OR_SELF) {
- // In this case, they would like to just execute as their
- // owner user instead of failing.
- targetUserId = callingUserId;
- } else {
- StringBuilder builder = new StringBuilder(128);
- builder.append("Permission Denial: ");
- builder.append(name);
- if (callerPackage != null) {
- builder.append(" from ");
- builder.append(callerPackage);
- }
- builder.append(" asks to run as user ");
- builder.append(userId);
- builder.append(" but is calling from user ");
- builder.append(UserHandle.getUserId(callingUid));
- builder.append("; this requires ");
- builder.append(INTERACT_ACROSS_USERS_FULL);
- if (allowMode != ALLOW_FULL_ONLY) {
- builder.append(" or ");
- builder.append(INTERACT_ACROSS_USERS);
- }
- String msg = builder.toString();
- Slog.w(TAG, msg);
- throw new SecurityException(msg);
- }
- }
- }
- if (!allowAll && targetUserId < 0) {
- throw new IllegalArgumentException(
- "Call does not support special user #" + targetUserId);
- }
- // Check shell permission
- if (callingUid == Process.SHELL_UID && targetUserId >= UserHandle.USER_SYSTEM) {
- if (mUserManager.hasUserRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES,
- targetUserId)) {
- throw new SecurityException("Shell does not have permission to access user "
- + targetUserId + "\n " + Debug.getCallers(3));
- }
- }
- return targetUserId;
- }
-
boolean isSingleton(String componentProcessName, ApplicationInfo aInfo,
String className, int flags) {
boolean result = false;
@@ -16437,8 +16349,8 @@ public final class ActivityManagerService extends ActivityManagerNative
callingPid = Binder.getCallingPid();
}
- userId = handleIncomingUser(callingPid, callingUid, userId,
- true, ALLOW_FULL_ONLY, "registerReceiver", callerPackage);
+ userId = mUserController.handleIncomingUser(callingPid, callingUid, userId, true,
+ ALLOW_FULL_ONLY, "registerReceiver", callerPackage);
Iterator<String> actions = filter.actionsIterator();
if (actions == null) {
@@ -16628,8 +16540,8 @@ public final class ActivityManagerService extends ActivityManagerNative
for (int user : users) {
// Skip users that have Shell restrictions
if (callingUid == Process.SHELL_UID
- && getUserManagerLocked().hasUserRestriction(
- UserManager.DISALLOW_DEBUGGING_FEATURES, user)) {
+ && mUserController.hasUserRestriction(
+ UserManager.DISALLOW_DEBUGGING_FEATURES, user)) {
continue;
}
List<ResolveInfo> newReceivers = AppGlobals.getPackageManager()
@@ -16718,14 +16630,14 @@ public final class ActivityManagerService extends ActivityManagerNative
Slog.w(TAG, "Broadcast " + intent + " not ordered but result callback requested!");
}
- userId = handleIncomingUser(callingPid, callingUid, userId,
- true, ALLOW_NON_FULL, "broadcast", callerPackage);
+ userId = mUserController.handleIncomingUser(callingPid, callingUid, userId, true,
+ ALLOW_NON_FULL, "broadcast", callerPackage);
// Make sure that the user who is receiving this broadcast is running.
// If not, we will just skip it. Make an exception for shutdown broadcasts
// and upgrade steps.
- if (userId != UserHandle.USER_ALL && !isUserRunningLocked(userId, false)) {
+ if (userId != UserHandle.USER_ALL && !mUserController.isUserRunningLocked(userId, false)) {
if ((callingUid != Process.SYSTEM_UID
|| (intent.getFlags() & Intent.FLAG_RECEIVER_BOOT_UPGRADE) == 0)
&& !Intent.ACTION_SHUTDOWN.equals(intent.getAction())) {
@@ -17030,9 +16942,8 @@ public final class ActivityManagerService extends ActivityManagerNative
if (intent.getComponent() == null) {
if (userId == UserHandle.USER_ALL && callingUid == Process.SHELL_UID) {
// Query one target user at a time, excluding shell-restricted users
- UserManagerService ums = getUserManagerLocked();
for (int i = 0; i < users.length; i++) {
- if (ums.hasUserRestriction(
+ if (mUserController.hasUserRestriction(
UserManager.DISALLOW_DEBUGGING_FEATURES, users[i])) {
continue;
}
@@ -17250,7 +17161,7 @@ public final class ActivityManagerService extends ActivityManagerNative
throw new IllegalArgumentException("File descriptors passed in Intent");
}
- userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+ userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
userId, true, ALLOW_NON_FULL, "removeStickyBroadcast", null);
synchronized(this) {
@@ -17334,7 +17245,7 @@ public final class ActivityManagerService extends ActivityManagerNative
IInstrumentationWatcher watcher, IUiAutomationConnection uiAutomationConnection,
int userId, String abiOverride) {
enforceNotIsolatedCaller("startInstrumentation");
- userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+ userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
userId, false, ALLOW_FULL_ONLY, "startInstrumentation", null);
// Refuse possible leaked file descriptors
if (arguments != null && arguments.hasFileDescriptors()) {
@@ -17600,10 +17511,11 @@ public final class ActivityManagerService extends ActivityManagerNative
Binder.restoreCallingIdentity(origId);
}
}
+
void updateUserConfigurationLocked() {
Configuration configuration = new Configuration(mConfiguration);
Settings.System.getConfigurationForUser(mContext.getContentResolver(), configuration,
- mUserController.mCurrentUserId);
+ mUserController.getCurrentUserIdLocked());
updateConfigurationLocked(configuration, null, false);
}
@@ -17652,7 +17564,7 @@ public final class ActivityManagerService extends ActivityManagerNative
mConfiguration = newConfig;
Slog.i(TAG, "Config changes=" + Integer.toHexString(changes) + " " + newConfig);
mUsageStatsService.reportConfigurationChange(newConfig,
- mUserController.mCurrentUserId);
+ mUserController.getCurrentUserIdLocked());
//mUsageStatsService.noteStartConfig(newConfig);
final Configuration configCopy = new Configuration(mConfiguration);
@@ -18921,7 +18833,8 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
- private final boolean applyOomAdjLocked(ProcessRecord app, boolean doingAll, long now) {
+ private final boolean applyOomAdjLocked(ProcessRecord app, boolean doingAll, long now,
+ long nowElapsed) {
boolean success = true;
if (app.curRawAdj != app.setRawAdj) {
@@ -19025,7 +18938,7 @@ public final class ActivityManagerService extends ActivityManagerNative
if (app.setProcState != app.curProcState) {
if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG_OOM_ADJ,
"Proc state change of " + app.processName
- + " to " + app.curProcState);
+ + " to " + app.curProcState);
boolean setImportant = app.setProcState < ActivityManager.PROCESS_STATE_SERVICE;
boolean curImportant = app.curProcState < ActivityManager.PROCESS_STATE_SERVICE;
if (setImportant && !curImportant) {
@@ -19036,14 +18949,14 @@ public final class ActivityManagerService extends ActivityManagerNative
BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();
synchronized (stats) {
app.lastWakeTime = stats.getProcessWakeTime(app.info.uid,
- app.pid, SystemClock.elapsedRealtime());
+ app.pid, nowElapsed);
}
app.lastCpuTime = app.curCpuTime;
}
// Inform UsageStats of important process state change
// Must be called before updating setProcState
- maybeUpdateUsageStatsLocked(app);
+ maybeUpdateUsageStatsLocked(app, nowElapsed);
app.setProcState = app.curProcState;
if (app.setProcState >= ActivityManager.PROCESS_STATE_HOME) {
@@ -19054,6 +18967,11 @@ public final class ActivityManagerService extends ActivityManagerNative
} else {
app.procStateChanged = true;
}
+ } else if (app.reportedInteraction && (nowElapsed-app.interactionEventTime)
+ > USAGE_STATS_INTERACTION_INTERVAL) {
+ // For apps that sit around for a long time in the interactive state, we need
+ // to report this at least once a day so they don't go idle.
+ maybeUpdateUsageStatsLocked(app, nowElapsed);
}
if (changes != 0) {
@@ -19136,7 +19054,7 @@ public final class ActivityManagerService extends ActivityManagerNative
String authority) {
if (app == null) return;
if (app.curProcState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
- UserState userState = mUserController.getStartedUserState(app.userId);
+ UserState userState = mUserController.getStartedUserStateLocked(app.userId);
if (userState == null) return;
final long now = SystemClock.elapsedRealtime();
Long lastReported = userState.mProviderLastReportedFg.get(authority);
@@ -19148,7 +19066,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
- private void maybeUpdateUsageStatsLocked(ProcessRecord app) {
+ private void maybeUpdateUsageStatsLocked(ProcessRecord app, long nowElapsed) {
if (DEBUG_USAGE_STATS) {
Slog.d(TAG, "Checking proc [" + Arrays.toString(app.getPackageList())
+ "] state changes: old = " + app.setProcState + ", new = "
@@ -19165,19 +19083,20 @@ public final class ActivityManagerService extends ActivityManagerNative
isInteraction = true;
app.fgInteractionTime = 0;
} else if (app.curProcState <= ActivityManager.PROCESS_STATE_TOP_SLEEPING) {
- final long now = SystemClock.elapsedRealtime();
if (app.fgInteractionTime == 0) {
- app.fgInteractionTime = now;
+ app.fgInteractionTime = nowElapsed;
isInteraction = false;
} else {
- isInteraction = now > app.fgInteractionTime + SERVICE_USAGE_INTERACTION_TIME;
+ isInteraction = nowElapsed > app.fgInteractionTime + SERVICE_USAGE_INTERACTION_TIME;
}
} else {
isInteraction = app.curProcState
<= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
app.fgInteractionTime = 0;
}
- if (isInteraction && !app.reportedInteraction) {
+ if (isInteraction && (!app.reportedInteraction
+ || (nowElapsed-app.interactionEventTime) > USAGE_STATS_INTERACTION_INTERVAL)) {
+ app.interactionEventTime = nowElapsed;
String[] packages = app.getPackageList();
if (packages != null) {
for (int i = 0; i < packages.length; i++) {
@@ -19187,6 +19106,9 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
app.reportedInteraction = isInteraction;
+ if (!isInteraction) {
+ app.interactionEventTime = 0;
+ }
}
private final void setProcessTrackerStateLocked(ProcessRecord proc, int memFactor, long now) {
@@ -19209,7 +19131,7 @@ public final class ActivityManagerService extends ActivityManagerNative
computeOomAdjLocked(app, cachedAdj, TOP_APP, doingAll, now);
- return applyOomAdjLocked(app, doingAll, now);
+ return applyOomAdjLocked(app, doingAll, now, SystemClock.elapsedRealtime());
}
final void updateProcessForegroundLocked(ProcessRecord proc, boolean isForeground,
@@ -19301,6 +19223,7 @@ public final class ActivityManagerService extends ActivityManagerNative
final ActivityRecord TOP_ACT = resumedAppLocked();
final ProcessRecord TOP_APP = TOP_ACT != null ? TOP_ACT.app : null;
final long now = SystemClock.uptimeMillis();
+ final long nowElapsed = SystemClock.elapsedRealtime();
final long oldTime = now - ProcessList.MAX_EMPTY_TIME;
final int N = mLruProcesses.size();
@@ -19429,7 +19352,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
- applyOomAdjLocked(app, true, now);
+ applyOomAdjLocked(app, true, now, nowElapsed);
// Count the number of process types.
switch (app.curProcState) {
@@ -19858,7 +19781,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
private ProcessRecord findProcessLocked(String process, int userId, String callName) {
- userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+ userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
userId, true, ALLOW_FULL_ONLY, callName, null);
ProcessRecord proc = null;
try {
@@ -20019,31 +19942,12 @@ public final class ActivityManagerService extends ActivityManagerNative
return mUserController.startUser(userId, /* foreground */ false);
}
- /**
- * Start user, if its not already running, and bring it to foreground.
- */
- boolean startUserInForeground(final int userId, Dialog dlg) {
- boolean result = mUserController.startUser(userId, /* foreground */ true);
- dlg.dismiss();
- return result;
- }
-
- private Set<Integer> getProfileIdsLocked(int userId) {
- Set<Integer> userIds = new HashSet<Integer>();
- final List<UserInfo> profiles = getUserManagerLocked().getProfiles(
- userId, false /* enabledOnly */);
- for (UserInfo user : profiles) {
- userIds.add(Integer.valueOf(user.id));
- }
- return userIds;
- }
-
@Override
public boolean switchUser(final int userId) {
enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, userId);
String userName;
synchronized (this) {
- UserInfo userInfo = getUserManagerLocked().getUserInfo(userId);
+ UserInfo userInfo = mUserController.getUserInfo(userId);
if (userInfo == null) {
Slog.w(TAG, "No user info for user #" + userId);
return false;
@@ -20053,68 +19957,13 @@ public final class ActivityManagerService extends ActivityManagerNative
return false;
}
userName = userInfo.name;
- mUserController.mTargetUserId = userId;
+ mUserController.setTargetUserIdLocked(userId);
}
mUiHandler.removeMessages(START_USER_SWITCH_MSG);
mUiHandler.sendMessage(mUiHandler.obtainMessage(START_USER_SWITCH_MSG, userId, 0, userName));
return true;
}
- private void showUserSwitchDialog(int userId, String userName) {
- // The dialog will show and then initiate the user switch by calling startUserInForeground
- Dialog d = new UserSwitchingDialog(this, mContext, userId, userName,
- true /* above system */);
- d.show();
- }
-
- void sendUserSwitchBroadcastsLocked(int oldUserId, int newUserId) {
- long ident = Binder.clearCallingIdentity();
- try {
- Intent intent;
- if (oldUserId >= 0) {
- // Send USER_BACKGROUND broadcast to all profiles of the outgoing user
- List<UserInfo> profiles = mUserManager.getProfiles(oldUserId, false);
- int count = profiles.size();
- for (int i = 0; i < count; i++) {
- int profileUserId = profiles.get(i).id;
- intent = new Intent(Intent.ACTION_USER_BACKGROUND);
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
- | Intent.FLAG_RECEIVER_FOREGROUND);
- intent.putExtra(Intent.EXTRA_USER_HANDLE, profileUserId);
- broadcastIntentLocked(null, null, intent,
- null, null, 0, null, null, null, AppOpsManager.OP_NONE,
- null, false, false, MY_PID, Process.SYSTEM_UID, profileUserId);
- }
- }
- if (newUserId >= 0) {
- // Send USER_FOREGROUND broadcast to all profiles of the incoming user
- List<UserInfo> profiles = mUserManager.getProfiles(newUserId, false);
- int count = profiles.size();
- for (int i = 0; i < count; i++) {
- int profileUserId = profiles.get(i).id;
- intent = new Intent(Intent.ACTION_USER_FOREGROUND);
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
- | Intent.FLAG_RECEIVER_FOREGROUND);
- intent.putExtra(Intent.EXTRA_USER_HANDLE, profileUserId);
- broadcastIntentLocked(null, null, intent,
- null, null, 0, null, null, null, AppOpsManager.OP_NONE,
- null, false, false, MY_PID, Process.SYSTEM_UID, profileUserId);
- }
- intent = new Intent(Intent.ACTION_USER_SWITCHED);
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
- | Intent.FLAG_RECEIVER_FOREGROUND);
- intent.putExtra(Intent.EXTRA_USER_HANDLE, newUserId);
- broadcastIntentLocked(null, null, intent,
- null, null, 0, null, null,
- new String[] {android.Manifest.permission.MANAGE_USERS},
- AppOpsManager.OP_NONE, null, false, false, MY_PID, Process.SYSTEM_UID,
- UserHandle.USER_ALL);
- }
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
void scheduleStartProfilesLocked() {
if (!mHandler.hasMessages(START_PROFILES_MSG)) {
mHandler.sendMessageDelayed(mHandler.obtainMessage(START_PROFILES_MSG),
@@ -20148,20 +19997,8 @@ public final class ActivityManagerService extends ActivityManagerNative
throw new SecurityException(msg);
}
synchronized (this) {
- return isUserRunningLocked(userId, orStopped);
- }
- }
-
- boolean isUserRunningLocked(int userId, boolean orStopped) {
- UserState state = mUserController.getStartedUserState(userId);
- if (state == null) {
- return false;
- }
- if (orStopped) {
- return true;
+ return mUserController.isUserRunningLocked(userId, orStopped);
}
- return state.mState != UserState.STATE_STOPPING
- && state.mState != UserState.STATE_SHUTDOWN;
}
@Override
@@ -20190,19 +20027,6 @@ public final class ActivityManagerService extends ActivityManagerNative
mUserController.unregisterUserSwitchObserver(observer);
}
- int[] getUsersLocked() {
- UserManagerService ums = getUserManagerLocked();
- return ums != null ? ums.getUserIds() : new int[] { 0 };
- }
-
- UserManagerService getUserManagerLocked() {
- if (mUserManager == null) {
- IBinder b = ServiceManager.getService(Context.USER_SERVICE);
- mUserManager = (UserManagerService)IUserManager.Stub.asInterface(b);
- }
- return mUserManager;
- }
-
private int applyUserId(int uid, int userId) {
return UserHandle.getUid(userId, uid);
}
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index b5f424d34adf..eb79ae7fbfbf 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -16,14 +16,11 @@
package com.android.server.am;
-import static android.app.ActivityManager.DOCKED_STACK_ID;
-import static android.app.ActivityManager.FIRST_STATIC_STACK_ID;
-import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.HOME_STACK_ID;
-import static android.app.ActivityManager.INVALID_STACK_ID;
-import static android.app.ActivityManager.LAST_STATIC_STACK_ID;
-import static android.app.ActivityManager.PINNED_STACK_ID;
+import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
+import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.HOME_STACK_ID;
+import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
import static android.content.pm.ActivityInfo.FLAG_SHOW_FOR_ALL_USERS;
import static com.android.server.am.ActivityManagerDebugConfig.*;
@@ -49,6 +46,7 @@ import com.android.server.wm.WindowManagerService;
import android.app.Activity;
import android.app.ActivityManager;
+import android.app.ActivityManager.StackId;
import android.app.ActivityOptions;
import android.app.AppGlobals;
import android.app.IActivityController;
@@ -255,9 +253,6 @@ final class ActivityStack {
private final LaunchingTaskPositioner mTaskPositioner;
- // If the bounds of task contained in this stack should be persisted across power cycles.
- final boolean mPersistTaskBounds;
-
static final int PAUSE_TIMEOUT_MSG = ActivityManagerService.FIRST_ACTIVITY_STACK_MSG + 1;
static final int DESTROY_TIMEOUT_MSG = ActivityManagerService.FIRST_ACTIVITY_STACK_MSG + 2;
static final int LAUNCH_TICK_MSG = ActivityManagerService.FIRST_ACTIVITY_STACK_MSG + 3;
@@ -367,11 +362,10 @@ final class ActivityStack {
mHandler = new ActivityStackHandler(mService.mHandler.getLooper());
mWindowManager = mService.mWindowManager;
mStackId = activityContainer.mStackId;
- mCurrentUser = mService.mUserController.mCurrentUserId;
+ mCurrentUser = mService.mUserController.getCurrentUserIdLocked();
mRecentTasks = recentTasks;
mTaskPositioner = mStackId == FREEFORM_WORKSPACE_STACK_ID
? new LaunchingTaskPositioner() : null;
- mPersistTaskBounds = mStackId != DOCKED_STACK_ID && mStackId != PINNED_STACK_ID;
}
void attachDisplay(ActivityStackSupervisor.ActivityDisplay activityDisplay, boolean onTop) {
@@ -1365,7 +1359,7 @@ final class ActivityStack {
}
}
- if (mStackId >= FIRST_STATIC_STACK_ID && mStackId <= LAST_STATIC_STACK_ID) {
+ if (StackId.isStaticStack(mStackId)) {
// Visibility of any static stack should have been determined by the conditions above.
return false;
}
@@ -1377,9 +1371,7 @@ final class ActivityStack {
continue;
}
- if (stack.mStackId == FREEFORM_WORKSPACE_STACK_ID
- || stack.mStackId == HOME_STACK_ID
- || stack.mStackId == FULLSCREEN_WORKSPACE_STACK_ID) {
+ if (!StackId.isDynamicStacksVisibleBehindAllowed(stack.mStackId)) {
// These stacks can't have any dynamic stacks visible behind them.
return false;
}
@@ -2797,8 +2789,7 @@ final class ActivityStack {
ActivityRecord next = topRunningActivityLocked();
final String myReason = reason + " adjustFocus";
if (next != r) {
- if (next != null && (mStackId == FREEFORM_WORKSPACE_STACK_ID
- || mStackId == DOCKED_STACK_ID || mStackId == PINNED_STACK_ID)) {
+ if (next != null && StackId.keepFocusInStackIfPossible(mStackId)) {
// For freeform, docked, and pinned stacks we always keep the focus within the
// stack as long as there is a running activity in the stack that we can adjust
// focus to.
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 1cd71a1cb61a..0afc715d3500 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -18,6 +18,15 @@ package com.android.server.am;
import static android.Manifest.permission.START_ANY_ACTIVITY;
import static android.app.ActivityManager.*;
+import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
+import static android.app.ActivityManager.StackId.FIRST_DYNAMIC_STACK_ID;
+import static android.app.ActivityManager.StackId.FIRST_STATIC_STACK_ID;
+import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.HOME_STACK_ID;
+import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+import static android.app.ActivityManager.StackId.LAST_STATIC_STACK_ID;
+import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
@@ -40,6 +49,7 @@ import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_WHITELISTED;
import android.Manifest;
import android.app.Activity;
import android.app.ActivityManager;
+import android.app.ActivityManager.StackId;
import android.app.ActivityManager.StackInfo;
import android.app.ActivityOptions;
import android.app.AppGlobals;
@@ -554,8 +564,8 @@ public final class ActivityStackSupervisor implements DisplayListener {
* @param id Id of the task we would like returned.
* @param restoreFromRecents If the id was not in the active list, but was found in recents,
* restore the task from recents to the active list.
- * @param stackId The stack to restore the task to (default launch stack will be used
- * if stackId is {@link android.app.ActivityManager#INVALID_STACK_ID}).
+ * @param stackId The stack to restore the task to (default launch stack will be used if
+ * stackId is {@link android.app.ActivityManager.StackId#INVALID_STACK_ID}).
*/
TaskRecord anyTaskForIdLocked(int id, boolean restoreFromRecents, int stackId) {
int numDisplays = mActivityDisplays.size();
@@ -1820,8 +1830,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
final ArrayList<ActivityStack> homeDisplayStacks = mHomeStack.mStacks;
for (int stackNdx = homeDisplayStacks.size() - 1; stackNdx >= 0; --stackNdx) {
stack = homeDisplayStacks.get(stackNdx);
- final boolean isDynamicStack = stack.mStackId >= FIRST_DYNAMIC_STACK_ID;
- if (isDynamicStack) {
+ if (!StackId.isStaticStack(stack.mStackId)) {
if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS,
"computeStackFocus: Setting focused stack=" + stack);
return stack;
@@ -2899,15 +2908,14 @@ public final class ActivityStackSupervisor implements DisplayListener {
if (activityContainer != null) {
return activityContainer.mStack;
}
- if (!createStaticStackIfNeeded
- || (stackId < FIRST_STATIC_STACK_ID || stackId > LAST_STATIC_STACK_ID)) {
+ if (!createStaticStackIfNeeded || !StackId.isStaticStack(stackId)) {
return null;
}
return createStackOnDisplay(stackId, Display.DEFAULT_DISPLAY, createOnTop);
}
ArrayList<ActivityStack> getStacks() {
- ArrayList<ActivityStack> allStacks = new ArrayList<ActivityStack>();
+ ArrayList<ActivityStack> allStacks = new ArrayList<>();
for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
allStacks.addAll(mActivityDisplays.valueAt(displayNdx).mStacks);
}
@@ -3025,7 +3033,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
// In this case we make all other static stacks fullscreen and move all
// docked stack tasks to the fullscreen stack.
for (int i = FIRST_STATIC_STACK_ID; i <= LAST_STATIC_STACK_ID; i++) {
- if (i != DOCKED_STACK_ID && getStack(i) != null) {
+ if (StackId.isResizeableByDockedStack(i) && getStack(i) != null) {
resizeStackLocked(i, null, preserveWindows, true);
}
}
@@ -3046,7 +3054,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
mWindowManager.getStackDockedModeBounds(HOME_STACK_ID, tempRect);
for (int i = FIRST_STATIC_STACK_ID; i <= LAST_STATIC_STACK_ID; i++) {
- if (i != DOCKED_STACK_ID) {
+ if (StackId.isResizeableByDockedStack(i)) {
ActivityStack otherStack = getStack(i);
if (otherStack != null) {
resizeStackLocked(i, tempRect, PRESERVE_WINDOWS, true);
@@ -3190,7 +3198,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
* Restores a recent task to a stack
* @param task The recent task to be restored.
* @param stackId The stack to restore the task to (default launch stack will be used
- * if stackId is {@link android.app.ActivityManager#INVALID_STACK_ID}).
+ * if stackId is {@link android.app.ActivityManager.StackId#INVALID_STACK_ID}).
* @return true if the task has been restored successfully.
*/
private boolean restoreRecentTaskLocked(TaskRecord task, int stackId) {
@@ -3275,8 +3283,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
Slog.w(TAG, "moveTaskToStack: no task for id=" + taskId);
return;
}
- if (stackId == DOCKED_STACK_ID || stackId == PINNED_STACK_ID
- || stackId == FULLSCREEN_WORKSPACE_STACK_ID) {
+ if (StackId.preserveWindowOnTaskMove(stackId)) {
// We are about to relaunch the activity because its configuration changed due to
// being maximized, i.e. size change. The activity will first remove the old window
// and then add a new one. This call will tell window manager about this, so it can
@@ -4664,7 +4671,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
@Override
public final int startActivity(Intent intent) {
mService.enforceNotIsolatedCaller("ActivityContainer.startActivity");
- final int userId = mService.handleIncomingUser(Binder.getCallingPid(),
+ final int userId = mService.mUserController.handleIncomingUser(Binder.getCallingPid(),
Binder.getCallingUid(), mCurrentUser, false,
ActivityManagerService.ALLOW_FULL_ONLY, "ActivityContainer", null);
@@ -4690,7 +4697,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
throw new IllegalArgumentException("Bad PendingIntent object");
}
- final int userId = mService.handleIncomingUser(Binder.getCallingPid(),
+ final int userId = mService.mUserController.handleIncomingUser(Binder.getCallingPid(),
Binder.getCallingUid(), mCurrentUser, false,
ActivityManagerService.ALLOW_FULL_ONLY, "ActivityContainer", null);
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index 6ed880e0a2a5..8039072dccda 100644
--- a/services/core/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -248,7 +248,7 @@ final class PendingIntentRecord extends IIntentSender.Stub {
boolean sendFinish = finishedReceiver != null;
int userId = key.userId;
if (userId == UserHandle.USER_CURRENT) {
- userId = owner.mUserController.getCurrentUserIdLocked();
+ userId = owner.mUserController.getCurrentOrTargetUserIdLocked();
}
switch (key.type) {
case ActivityManager.INTENT_SENDER_ACTIVITY:
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 08203c55b5d9..b77eec8adf0e 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -114,6 +114,7 @@ final class ProcessRecord {
boolean killed; // True once we know the process has been killed
boolean procStateChanged; // Keep track of whether we changed 'setAdj'.
boolean reportedInteraction;// Whether we have told usage stats about it being an interaction
+ long interactionEventTime; // The time we sent the last interaction event
long fgInteractionTime; // When we became foreground for interaction purposes
String waitingToKill; // Process is waiting to be killed when in the bg, and reason
IBinder forcingToForeground;// Token that is forcing this process to be foreground
@@ -297,6 +298,10 @@ final class ProcessRecord {
if (reportedInteraction || fgInteractionTime != 0) {
pw.print(prefix); pw.print("reportedInteraction=");
pw.print(reportedInteraction);
+ if (interactionEventTime != 0) {
+ pw.print(" time=");
+ TimeUtils.formatDuration(interactionEventTime, SystemClock.elapsedRealtime(), pw);
+ }
if (fgInteractionTime != 0) {
pw.print(" fgInteractionTime=");
TimeUtils.formatDuration(fgInteractionTime, SystemClock.elapsedRealtime(), pw);
diff --git a/services/core/java/com/android/server/am/RecentTasks.java b/services/core/java/com/android/server/am/RecentTasks.java
index edd16ef25780..862a9739fe05 100644
--- a/services/core/java/com/android/server/am/RecentTasks.java
+++ b/services/core/java/com/android/server/am/RecentTasks.java
@@ -108,7 +108,7 @@ class RecentTasks extends ArrayList<TaskRecord> {
final IPackageManager pm = AppGlobals.getPackageManager();
final int[] users = (userId == UserHandle.USER_ALL)
- ? mService.getUsersLocked() : new int[] { userId };
+ ? mService.mUserController.getUsers() : new int[] { userId };
for (int userIdx = 0; userIdx < users.length; userIdx++) {
final int user = users[userIdx];
recentsCount = size() - 1;
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 120b40c73c13..5fd213f96c66 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -16,11 +16,9 @@
package com.android.server.am;
-import static android.app.ActivityManager.DOCKED_STACK_ID;
-import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.HOME_STACK_ID;
-import static android.app.ActivityManager.PINNED_STACK_ID;
+import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.HOME_STACK_ID;
import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
import static android.content.Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS;
import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_ALWAYS;
@@ -35,6 +33,7 @@ import static com.android.server.am.ActivityRecord.RECENTS_ACTIVITY_TYPE;
import android.app.Activity;
import android.app.ActivityManager;
+import android.app.ActivityManager.StackId;
import android.app.ActivityManager.TaskThumbnail;
import android.app.ActivityManager.TaskDescription;
import android.app.ActivityOptions;
@@ -1193,14 +1192,14 @@ final class TaskRecord {
mFullscreen = bounds == null;
if (mFullscreen) {
- if (mBounds != null && stack.mPersistTaskBounds) {
+ if (mBounds != null && StackId.persistTaskBounds(stack.mStackId)) {
mLastNonFullscreenBounds = mBounds;
}
mBounds = null;
mOverrideConfig = Configuration.EMPTY;
} else {
mBounds = new Rect(bounds);
- if (stack == null || stack.mPersistTaskBounds) {
+ if (stack == null || StackId.persistTaskBounds(stack.mStackId)) {
mLastNonFullscreenBounds = mBounds;
}
@@ -1244,7 +1243,7 @@ final class TaskRecord {
|| stackId == HOME_STACK_ID
|| stackId == FULLSCREEN_WORKSPACE_STACK_ID) {
return (mResizeable && stack != null) ? stack.mBounds : null;
- } else if (!stack.mPersistTaskBounds) {
+ } else if (!StackId.persistTaskBounds(stackId)) {
return stack.mBounds;
}
return mLastNonFullscreenBounds;
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index ff74d836e1ba..408548925175 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -18,11 +18,17 @@ package com.android.server.am;
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+
import static android.app.ActivityManager.USER_OP_IS_CURRENT;
import static android.app.ActivityManager.USER_OP_SUCCESS;
+import static android.os.Process.SYSTEM_UID;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.am.ActivityManagerService.ALLOW_FULL_ONLY;
+import static com.android.server.am.ActivityManagerService.ALLOW_NON_FULL;
+import static com.android.server.am.ActivityManagerService.ALLOW_NON_FULL_IN_PROFILE;
+import static com.android.server.am.ActivityManagerService.MY_PID;
import static com.android.server.am.ActivityManagerService.REPORT_USER_SWITCH_COMPLETE_MSG;
import static com.android.server.am.ActivityManagerService.REPORT_USER_SWITCH_MSG;
import static com.android.server.am.ActivityManagerService.SYSTEM_USER_CURRENT_MSG;
@@ -31,8 +37,10 @@ import static com.android.server.am.ActivityManagerService.USER_SWITCH_TIMEOUT_M
import android.app.ActivityManager;
import android.app.AppOpsManager;
+import android.app.Dialog;
import android.app.IStopUserCallback;
import android.app.IUserSwitchObserver;
+import android.content.Context;
import android.content.IIntentReceiver;
import android.content.Intent;
import android.content.pm.PackageManager;
@@ -40,11 +48,15 @@ import android.content.pm.UserInfo;
import android.os.BatteryStats;
import android.os.Binder;
import android.os.Bundle;
+import android.os.Debug;
import android.os.Handler;
+import android.os.IBinder;
import android.os.IRemoteCallback;
+import android.os.IUserManager;
import android.os.Process;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Slog;
@@ -58,7 +70,9 @@ import com.android.server.pm.UserManagerService;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
/**
* Helper class for {@link ActivityManagerService} responsible for multi-user functionality.
@@ -76,9 +90,9 @@ final class UserController {
private final Handler mHandler;
// Holds the current foreground user's id
- int mCurrentUserId = 0;
+ private int mCurrentUserId = UserHandle.USER_SYSTEM;
// Holds the target user's id during a user switch
- int mTargetUserId = UserHandle.USER_NULL;
+ private int mTargetUserId = UserHandle.USER_NULL;
/**
* Which users have been started, so are allowed to run code.
@@ -96,7 +110,7 @@ final class UserController {
// If there are multiple profiles for the current user, their ids are here
// Currently only the primary user can have managed profiles
- int[] mCurrentProfileIds = new int[] {}; // Accessed by ActivityStack
+ private int[] mCurrentProfileIds = new int[] {};
/**
* Mapping from each known user ID to the profile group ID it is associated with.
@@ -106,7 +120,7 @@ final class UserController {
/**
* Registered observers of the user switching mechanics.
*/
- final RemoteCallbackList<IUserSwitchObserver> mUserSwitchObservers
+ private final RemoteCallbackList<IUserSwitchObserver> mUserSwitchObservers
= new RemoteCallbackList<>();
/**
@@ -114,6 +128,8 @@ final class UserController {
*/
Object mCurUserSwitchCallback;
+ private volatile UserManagerService mUserManager;
+
UserController(ActivityManagerService service) {
mService = service;
mHandler = mService.mHandler;
@@ -176,8 +192,7 @@ final class UserController {
mService.broadcastIntentLocked(null, null, intent,
null, null, 0, null, null,
new String[]{android.Manifest.permission.RECEIVE_BOOT_COMPLETED},
- AppOpsManager.OP_NONE, null, true, false, ActivityManagerService.MY_PID,
- Process.SYSTEM_UID, userId);
+ AppOpsManager.OP_NONE, null, true, false, MY_PID, SYSTEM_UID, userId);
}
}
}
@@ -272,16 +287,14 @@ final class UserController {
mService.mSystemServiceManager.stopUser(userId);
mService.broadcastIntentLocked(null, null, shutdownIntent,
null, shutdownReceiver, 0, null, null, null, AppOpsManager.OP_NONE,
- null, true, false, ActivityManagerService.MY_PID,
- android.os.Process.SYSTEM_UID, userId);
+ null, true, false, MY_PID, SYSTEM_UID, userId);
}
};
// Kick things off.
mService.broadcastIntentLocked(null, null, stoppingIntent,
null, stoppingReceiver, 0, null, null,
new String[]{INTERACT_ACROSS_USERS}, AppOpsManager.OP_NONE,
- null, true, false, ActivityManagerService.MY_PID, Process.SYSTEM_UID,
- UserHandle.USER_ALL);
+ null, true, false, MY_PID, SYSTEM_UID, UserHandle.USER_ALL);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -338,8 +351,7 @@ final class UserController {
intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
mService.broadcastIntentLocked(null, null, intent,
null, null, 0, null, null, null, AppOpsManager.OP_NONE,
- null, false, false, ActivityManagerService.MY_PID, Process.SYSTEM_UID,
- UserHandle.USER_ALL);
+ null, false, false, MY_PID, SYSTEM_UID, UserHandle.USER_ALL);
}
@@ -357,7 +369,7 @@ final class UserController {
|| oldUss.mState == UserState.STATE_SHUTDOWN) {
continue;
}
- UserInfo userInfo = getUserManagerLocked().getUserInfo(oldUserId);
+ UserInfo userInfo = getUserInfo(oldUserId);
if (userInfo.isGuest()) {
// This is a user to be stopped.
stopUserLocked(oldUserId, null);
@@ -369,7 +381,7 @@ final class UserController {
void startProfilesLocked() {
if (DEBUG_MU) Slog.i(TAG, "startProfilesLocked");
- List<UserInfo> profiles = getUserManagerLocked().getProfiles(
+ List<UserInfo> profiles = getUserManager().getProfiles(
mCurrentUserId, false /* enabledOnly */);
List<UserInfo> profilesToStart = new ArrayList<>(profiles.size());
for (UserInfo user : profiles) {
@@ -388,8 +400,13 @@ final class UserController {
}
}
- private UserManagerService getUserManagerLocked() {
- return mService.getUserManagerLocked();
+ private UserManagerService getUserManager() {
+ UserManagerService userManager = mUserManager;
+ if (userManager == null) {
+ IBinder b = ServiceManager.getService(Context.USER_SERVICE);
+ userManager = mUserManager = (UserManagerService) IUserManager.Stub.asInterface(b);
+ }
+ return userManager;
}
boolean startUser(final int userId, final boolean foreground) {
@@ -416,7 +433,7 @@ final class UserController {
mService.mStackSupervisor.setLockTaskModeLocked(null,
ActivityManager.LOCK_TASK_MODE_NONE, "startUser", false);
- final UserInfo userInfo = getUserManagerLocked().getUserInfo(userId);
+ final UserInfo userInfo = getUserInfo(userId);
if (userInfo == null) {
Slog.w(TAG, "No user info for user #" + userId);
return false;
@@ -507,8 +524,7 @@ final class UserController {
intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
mService.broadcastIntentLocked(null, null, intent,
null, null, 0, null, null, null, AppOpsManager.OP_NONE,
- null, false, false, ActivityManagerService.MY_PID, Process.SYSTEM_UID,
- userId);
+ null, false, false, MY_PID, SYSTEM_UID, userId);
}
if ((userInfo.flags&UserInfo.FLAG_INITIALIZED) == 0) {
@@ -523,11 +539,10 @@ final class UserController {
onUserInitialized(uss, foreground, oldUserId, userId);
}
}, 0, null, null, null, AppOpsManager.OP_NONE,
- null, true, false, ActivityManagerService.MY_PID,
- Process.SYSTEM_UID, userId);
+ null, true, false, MY_PID, SYSTEM_UID, userId);
uss.initializing = true;
} else {
- getUserManagerLocked().makeInitialized(userInfo.id);
+ getUserManager().makeInitialized(userInfo.id);
}
}
@@ -551,9 +566,8 @@ final class UserController {
int sendingUser) throws RemoteException {
}
}, 0, null, null,
- new String[]{INTERACT_ACROSS_USERS}, AppOpsManager.OP_NONE,
- null, true, false, ActivityManagerService.MY_PID, Process.SYSTEM_UID,
- UserHandle.USER_ALL);
+ new String[] {INTERACT_ACROSS_USERS}, AppOpsManager.OP_NONE,
+ null, true, false, MY_PID, SYSTEM_UID, UserHandle.USER_ALL);
}
}
} finally {
@@ -563,6 +577,22 @@ final class UserController {
return true;
}
+ /**
+ * Start user, if its not already running, and bring it to foreground.
+ */
+ boolean startUserInForeground(final int userId, Dialog dlg) {
+ boolean result = startUser(userId, /* foreground */ true);
+ dlg.dismiss();
+ return result;
+ }
+
+ void showUserSwitchDialog(int userId, String userName) {
+ // The dialog will show and then initiate the user switch by calling startUserInForeground
+ Dialog d = new UserSwitchingDialog(mService, mService.mContext, userId, userName,
+ true /* above system */);
+ d.show();
+ }
+
void dispatchForegroundProfileChanged(int userId) {
final int observerCount = mUserSwitchObservers.beginBroadcast();
for (int i = 0; i < observerCount; i++) {
@@ -657,7 +687,7 @@ final class UserController {
synchronized (mService) {
if (clearInitializing) {
uss.initializing = false;
- getUserManagerLocked().makeInitialized(uss.mHandle.getIdentifier());
+ getUserManager().makeInitialized(uss.mHandle.getIdentifier());
}
if (clearSwitching) {
uss.switching = false;
@@ -683,8 +713,143 @@ final class UserController {
mService.mStackSupervisor.resumeTopActivitiesLocked();
}
EventLogTags.writeAmSwitchUser(newUserId);
- getUserManagerLocked().onUserForeground(newUserId);
- mService.sendUserSwitchBroadcastsLocked(oldUserId, newUserId);
+ getUserManager().onUserForeground(newUserId);
+ sendUserSwitchBroadcastsLocked(oldUserId, newUserId);
+ }
+
+ void sendUserSwitchBroadcastsLocked(int oldUserId, int newUserId) {
+ long ident = Binder.clearCallingIdentity();
+ try {
+ Intent intent;
+ if (oldUserId >= 0) {
+ // Send USER_BACKGROUND broadcast to all profiles of the outgoing user
+ List<UserInfo> profiles = getUserManager().getProfiles(oldUserId, false);
+ int count = profiles.size();
+ for (int i = 0; i < count; i++) {
+ int profileUserId = profiles.get(i).id;
+ intent = new Intent(Intent.ACTION_USER_BACKGROUND);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
+ | Intent.FLAG_RECEIVER_FOREGROUND);
+ intent.putExtra(Intent.EXTRA_USER_HANDLE, profileUserId);
+ mService.broadcastIntentLocked(null, null, intent,
+ null, null, 0, null, null, null, AppOpsManager.OP_NONE,
+ null, false, false, MY_PID, SYSTEM_UID, profileUserId);
+ }
+ }
+ if (newUserId >= 0) {
+ // Send USER_FOREGROUND broadcast to all profiles of the incoming user
+ List<UserInfo> profiles = getUserManager().getProfiles(newUserId, false);
+ int count = profiles.size();
+ for (int i = 0; i < count; i++) {
+ int profileUserId = profiles.get(i).id;
+ intent = new Intent(Intent.ACTION_USER_FOREGROUND);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
+ | Intent.FLAG_RECEIVER_FOREGROUND);
+ intent.putExtra(Intent.EXTRA_USER_HANDLE, profileUserId);
+ mService.broadcastIntentLocked(null, null, intent,
+ null, null, 0, null, null, null, AppOpsManager.OP_NONE,
+ null, false, false, MY_PID, SYSTEM_UID, profileUserId);
+ }
+ intent = new Intent(Intent.ACTION_USER_SWITCHED);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
+ | Intent.FLAG_RECEIVER_FOREGROUND);
+ intent.putExtra(Intent.EXTRA_USER_HANDLE, newUserId);
+ mService.broadcastIntentLocked(null, null, intent,
+ null, null, 0, null, null,
+ new String[] {android.Manifest.permission.MANAGE_USERS},
+ AppOpsManager.OP_NONE, null, false, false, MY_PID, SYSTEM_UID,
+ UserHandle.USER_ALL);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+
+ int handleIncomingUser(int callingPid, int callingUid, int userId, boolean allowAll,
+ int allowMode, String name, String callerPackage) {
+ final int callingUserId = UserHandle.getUserId(callingUid);
+ if (callingUserId == userId) {
+ return userId;
+ }
+
+ // Note that we may be accessing mCurrentUserId outside of a lock...
+ // shouldn't be a big deal, if this is being called outside
+ // of a locked context there is intrinsically a race with
+ // the value the caller will receive and someone else changing it.
+ // We assume that USER_CURRENT_OR_SELF will use the current user; later
+ // we will switch to the calling user if access to the current user fails.
+ int targetUserId = unsafeConvertIncomingUserLocked(userId);
+
+ if (callingUid != 0 && callingUid != SYSTEM_UID) {
+ final boolean allow;
+ if (mService.checkComponentPermission(INTERACT_ACROSS_USERS_FULL, callingPid,
+ callingUid, -1, true) == PackageManager.PERMISSION_GRANTED) {
+ // If the caller has this permission, they always pass go. And collect $200.
+ allow = true;
+ } else if (allowMode == ALLOW_FULL_ONLY) {
+ // We require full access, sucks to be you.
+ allow = false;
+ } else if (mService.checkComponentPermission(INTERACT_ACROSS_USERS, callingPid,
+ callingUid, -1, true) != PackageManager.PERMISSION_GRANTED) {
+ // If the caller does not have either permission, they are always doomed.
+ allow = false;
+ } else if (allowMode == ALLOW_NON_FULL) {
+ // We are blanket allowing non-full access, you lucky caller!
+ allow = true;
+ } else if (allowMode == ALLOW_NON_FULL_IN_PROFILE) {
+ // We may or may not allow this depending on whether the two users are
+ // in the same profile.
+ allow = isSameProfileGroup(callingUserId, targetUserId);
+ } else {
+ throw new IllegalArgumentException("Unknown mode: " + allowMode);
+ }
+ if (!allow) {
+ if (userId == UserHandle.USER_CURRENT_OR_SELF) {
+ // In this case, they would like to just execute as their
+ // owner user instead of failing.
+ targetUserId = callingUserId;
+ } else {
+ StringBuilder builder = new StringBuilder(128);
+ builder.append("Permission Denial: ");
+ builder.append(name);
+ if (callerPackage != null) {
+ builder.append(" from ");
+ builder.append(callerPackage);
+ }
+ builder.append(" asks to run as user ");
+ builder.append(userId);
+ builder.append(" but is calling from user ");
+ builder.append(UserHandle.getUserId(callingUid));
+ builder.append("; this requires ");
+ builder.append(INTERACT_ACROSS_USERS_FULL);
+ if (allowMode != ALLOW_FULL_ONLY) {
+ builder.append(" or ");
+ builder.append(INTERACT_ACROSS_USERS);
+ }
+ String msg = builder.toString();
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+ }
+ }
+ }
+ if (!allowAll && targetUserId < 0) {
+ throw new IllegalArgumentException(
+ "Call does not support special user #" + targetUserId);
+ }
+ // Check shell permission
+ if (callingUid == Process.SHELL_UID && targetUserId >= UserHandle.USER_SYSTEM) {
+ if (hasUserRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, targetUserId)) {
+ throw new SecurityException("Shell does not have permission to access user "
+ + targetUserId + "\n " + Debug.getCallers(3));
+ }
+ }
+ return targetUserId;
+ }
+
+ int unsafeConvertIncomingUserLocked(int userId) {
+ return (userId == UserHandle.USER_CURRENT || userId == UserHandle.USER_CURRENT_OR_SELF)
+ ? getCurrentUserIdLocked(): userId;
}
void registerUserSwitchObserver(IUserSwitchObserver observer) {
@@ -705,7 +870,7 @@ final class UserController {
mUserSwitchObservers.unregister(observer);
}
- UserState getStartedUserState(int userId) {
+ UserState getStartedUserStateLocked(int userId) {
return mStartedUsers.get(userId);
}
@@ -746,9 +911,8 @@ final class UserController {
intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT);
mService.broadcastIntentLocked(null, null, intent, null,
resultTo, 0, null, null,
- new String[]{android.Manifest.permission.RECEIVE_BOOT_COMPLETED},
- AppOpsManager.OP_NONE, null, true, false,
- ActivityManagerService.MY_PID, Process.SYSTEM_UID, userId);
+ new String[] {android.Manifest.permission.RECEIVE_BOOT_COMPLETED},
+ AppOpsManager.OP_NONE, null, true, false, MY_PID, SYSTEM_UID, userId);
}
}
}
@@ -759,7 +923,7 @@ final class UserController {
* background.
*/
void updateCurrentProfileIdsLocked() {
- final List<UserInfo> profiles = getUserManagerLocked().getProfiles(mCurrentUserId,
+ final List<UserInfo> profiles = getUserManager().getProfiles(mCurrentUserId,
false /* enabledOnly */);
int[] currentProfileIds = new int[profiles.size()]; // profiles will not be null
for (int i = 0; i < currentProfileIds.length; i++) {
@@ -769,7 +933,7 @@ final class UserController {
synchronized (mUserProfileGroupIdsSelfLocked) {
mUserProfileGroupIdsSelfLocked.clear();
- final List<UserInfo> users = getUserManagerLocked().getUsers(false);
+ final List<UserInfo> users = getUserManager().getUsers(false);
for (int i = 0; i < users.size(); i++) {
UserInfo user = users.get(i);
if (user.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID) {
@@ -783,6 +947,18 @@ final class UserController {
return mStartedUserArray;
}
+ boolean isUserRunningLocked(int userId, boolean orStopped) {
+ UserState state = getStartedUserStateLocked(userId);
+ if (state == null) {
+ return false;
+ }
+ if (orStopped) {
+ return true;
+ }
+ return state.mState != UserState.STATE_STOPPING
+ && state.mState != UserState.STATE_SHUTDOWN;
+ }
+
UserInfo getCurrentUser() {
if ((mService.checkCallingPermission(INTERACT_ACROSS_USERS)
!= PackageManager.PERMISSION_GRANTED) && (
@@ -802,13 +978,52 @@ final class UserController {
UserInfo getCurrentUserLocked() {
int userId = mTargetUserId != UserHandle.USER_NULL ? mTargetUserId : mCurrentUserId;
- return getUserManagerLocked().getUserInfo(userId);
+ return getUserInfo(userId);
}
- int getCurrentUserIdLocked() {
+ int getCurrentOrTargetUserIdLocked() {
return mTargetUserId != UserHandle.USER_NULL ? mTargetUserId : mCurrentUserId;
}
+ int getCurrentUserIdLocked() {
+ return mCurrentUserId;
+ }
+
+ int setTargetUserIdLocked(int targetUserId) {
+ return mTargetUserId = targetUserId;
+ }
+
+ int[] getUsers() {
+ UserManagerService ums = getUserManager();
+ return ums != null ? ums.getUserIds() : new int[] { 0 };
+ }
+
+ UserInfo getUserInfo(int userId) {
+ return getUserManager().getUserInfo(userId);
+ }
+
+ int[] getUserIds() {
+ return getUserManager().getUserIds();
+ }
+
+ boolean exists(int userId) {
+ return getUserManager().exists(userId);
+ }
+
+ boolean hasUserRestriction(String restriction, int userId) {
+ return getUserManager().hasUserRestriction(restriction, userId);
+ }
+
+ Set<Integer> getProfileIds(int userId) {
+ Set<Integer> userIds = new HashSet<>();
+ final List<UserInfo> profiles = getUserManager().getProfiles(userId,
+ false /* enabledOnly */);
+ for (UserInfo user : profiles) {
+ userIds.add(user.id);
+ }
+ return userIds;
+ }
+
boolean isSameProfileGroup(int callingUserId, int targetUserId) {
synchronized (mUserProfileGroupIdsSelfLocked) {
int callingProfile = mUserProfileGroupIdsSelfLocked.get(callingUserId,
@@ -824,6 +1039,10 @@ final class UserController {
return ArrayUtils.contains(mCurrentProfileIds, userId);
}
+ int[] getCurrentProfileIdsLocked() {
+ return mCurrentProfileIds;
+ }
+
void dump(PrintWriter pw, boolean dumpAll) {
pw.println(" mStartedUsers:");
for (int i = 0; i < mStartedUsers.size(); i++) {
@@ -858,5 +1077,4 @@ final class UserController {
}
}
}
-
}
diff --git a/services/core/java/com/android/server/am/UserSwitchingDialog.java b/services/core/java/com/android/server/am/UserSwitchingDialog.java
index 5a66f4a74d99..28b4096c8e16 100644
--- a/services/core/java/com/android/server/am/UserSwitchingDialog.java
+++ b/services/core/java/com/android/server/am/UserSwitchingDialog.java
@@ -97,7 +97,7 @@ final class UserSwitchingDialog extends AlertDialog
void startUser() {
synchronized (this) {
if (!mStartedUser) {
- mService.startUserInForeground(mUserId, this);
+ mService.mUserController.startUserInForeground(mUserId, this);
mStartedUser = true;
final View decorView = getWindow().getDecorView();
if (decorView != null) {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 66e731a29dde..d89f47cc622d 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -1756,7 +1756,8 @@ public class AudioService extends IAudioService.Stub {
if (uid == android.os.Process.SYSTEM_UID) {
uid = UserHandle.getUid(userId, UserHandle.getAppId(uid));
}
- if (mAppOps.noteOp(AppOpsManager.OP_AUDIO_MASTER_VOLUME, uid, callingPackage)
+ // If OP_AUDIO_MASTER_VOLUME is set, disallow unmuting.
+ if (!mute && mAppOps.noteOp(AppOpsManager.OP_AUDIO_MASTER_VOLUME, uid, callingPackage)
!= AppOpsManager.MODE_ALLOWED) {
return;
}
@@ -1848,7 +1849,8 @@ public class AudioService extends IAudioService.Stub {
if (uid == android.os.Process.SYSTEM_UID) {
uid = UserHandle.getUid(userId, UserHandle.getAppId(uid));
}
- if (mAppOps.noteOp(AppOpsManager.OP_MUTE_MICROPHONE, uid, callingPackage)
+ // If OP_MUTE_MICROPHONE is set, disallow unmuting.
+ if (!on && mAppOps.noteOp(AppOpsManager.OP_MUTE_MICROPHONE, uid, callingPackage)
!= AppOpsManager.MODE_ALLOWED) {
return;
}
diff --git a/services/core/java/com/android/server/content/AppIdleMonitor.java b/services/core/java/com/android/server/content/AppIdleMonitor.java
index fe5c2da4c6d1..2d768d8b2aae 100644
--- a/services/core/java/com/android/server/content/AppIdleMonitor.java
+++ b/services/core/java/com/android/server/content/AppIdleMonitor.java
@@ -50,8 +50,8 @@ class AppIdleMonitor extends AppIdleStateChangeListener {
}
}
- boolean isAppIdle(String packageName, int userId) {
- return !mAppIdleParoleOn && mUsageStats.isAppIdle(packageName, userId);
+ boolean isAppIdle(String packageName, int uidForAppId, int userId) {
+ return !mAppIdleParoleOn && mUsageStats.isAppIdle(packageName, uidForAppId, userId);
}
@Override
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index 15e539314a6e..82e0eafe6bea 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -2625,9 +2625,18 @@ public class SyncManager {
continue;
}
String packageName = getPackageName(op.target);
+ ApplicationInfo ai = null;
+ if (packageName != null) {
+ try {
+ ai = mContext.getPackageManager().getApplicationInfo(packageName,
+ PackageManager.GET_UNINSTALLED_PACKAGES
+ | PackageManager.GET_DISABLED_COMPONENTS);
+ } catch (NameNotFoundException e) {
+ }
+ }
// If app is considered idle, then skip for now and backoff
- if (packageName != null
- && mAppIdleMonitor.isAppIdle(packageName, op.target.userId)) {
+ if (ai != null
+ && mAppIdleMonitor.isAppIdle(packageName, ai.uid, op.target.userId)) {
increaseBackoffSetting(op);
op.appIdle = true;
if (isLoggable) {
diff --git a/services/core/java/com/android/server/job/controllers/AppIdleController.java b/services/core/java/com/android/server/job/controllers/AppIdleController.java
index 02d4f4034244..6fc02f6eba16 100644
--- a/services/core/java/com/android/server/job/controllers/AppIdleController.java
+++ b/services/core/java/com/android/server/job/controllers/AppIdleController.java
@@ -67,7 +67,7 @@ public class AppIdleController extends StateController {
mTrackedTasks.add(jobStatus);
String packageName = jobStatus.job.getService().getPackageName();
final boolean appIdle = !mAppIdleParoleOn && mUsageStatsInternal.isAppIdle(packageName,
- jobStatus.getUserId());
+ jobStatus.uId, jobStatus.getUserId());
if (DEBUG) {
Slog.d(LOG_TAG, "Start tracking, setting idle state of "
+ packageName + " to " + appIdle);
@@ -108,7 +108,7 @@ public class AppIdleController extends StateController {
for (JobStatus task : mTrackedTasks) {
String packageName = task.job.getService().getPackageName();
final boolean appIdle = !mAppIdleParoleOn && mUsageStatsInternal.isAppIdle(packageName,
- task.getUserId());
+ task.uId, task.getUserId());
if (DEBUG) {
Slog.d(LOG_TAG, "Setting idle state of " + packageName + " to " + appIdle);
}
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 17ae6dce50d9..41aea040cdc5 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -2227,7 +2227,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
final int userId = UserHandle.getUserId(uid);
for (String packageName : packages) {
- if (!mUsageStats.isAppIdle(packageName, userId)) {
+ if (!mUsageStats.isAppIdle(packageName, uid, userId)) {
return false;
}
}
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index d8676167497f..150c84932aca 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -53,6 +53,14 @@ public final class Installer extends SystemService {
mInstaller = new InstallerConnection();
}
+ /**
+ * Yell loudly if someone tries making future calls while holding a lock on
+ * the given object.
+ */
+ public void setWarnIfHeld(Object warnIfHeld) {
+ mInstaller.setWarnIfHeld(warnIfHeld);
+ }
+
@Override
public void onStart() {
Slog.i(TAG, "Waiting for installd to be ready.");
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 22bedc3c2850..08f9bd5be167 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -162,6 +162,7 @@ import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
+import android.os.ResultReceiver;
import android.os.SELinux;
import android.os.ServiceManager;
import android.os.SystemClock;
@@ -2390,6 +2391,11 @@ public class PackageManagerService extends IPackageManager.Stub {
// tidy.
Runtime.getRuntime().gc();
+ // The initial scanning above does many calls into installd while
+ // holding the mPackages lock, but we're mostly interested in yelling
+ // once we have a booted system.
+ mInstaller.setWarnIfHeld(mPackages);
+
// Expose private service for system components to use.
LocalServices.addService(PackageManagerInternal.class, new PackageManagerInternalImpl());
}
@@ -15086,6 +15092,13 @@ public class PackageManagerService extends IPackageManager.Stub {
}
@Override
+ public void onShellCommand(FileDescriptor in, FileDescriptor out,
+ FileDescriptor err, String[] args, ResultReceiver resultReceiver) {
+ (new PackageManagerShellCommand(this)).exec(
+ this, in, out, err, args, resultReceiver);
+ }
+
+ @Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
!= PackageManager.PERMISSION_GRANTED) {
@@ -16545,7 +16558,7 @@ public class PackageManagerService extends IPackageManager.Stub {
*/
private void removeUnusedPackagesLILPw(UserManagerService userManager, final int userHandle) {
final boolean DEBUG_CLEAN_APKS = false;
- int [] users = userManager.getUserIdsLPr();
+ int [] users = userManager.getUserIds();
Iterator<PackageSetting> psit = mSettings.mPackages.values().iterator();
while (psit.hasNext()) {
PackageSetting ps = psit.next();
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
new file mode 100644
index 000000000000..c259ac2b2fec
--- /dev/null
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -0,0 +1,529 @@
+package com.android.server.pm;
+
+import android.content.ComponentName;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.FeatureInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.InstrumentationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageItemInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
+import android.content.pm.PermissionGroupInfo;
+import android.content.pm.PermissionInfo;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.os.RemoteException;
+import android.os.ShellCommand;
+import android.os.UserHandle;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.WeakHashMap;
+
+class PackageManagerShellCommand extends ShellCommand {
+ final IPackageManager mInterface;
+ final private WeakHashMap<String, Resources> mResourceCache =
+ new WeakHashMap<String, Resources>();
+
+ PackageManagerShellCommand(PackageManagerService service) {
+ mInterface = service;
+ }
+
+ @Override
+ public int onCommand(String cmd) {
+ if (cmd == null) {
+ return handleDefaultCommands(cmd);
+ }
+
+ final PrintWriter pw = getOutPrintWriter();
+ try {
+ switch(cmd) {
+ case "list":
+ return runList();
+ default:
+ return handleDefaultCommands(cmd);
+ }
+ } catch (RemoteException e) {
+ pw.println("Remote exception: " + e);
+ }
+ return -1;
+ }
+
+ private int runList() throws RemoteException {
+ final PrintWriter pw = getOutPrintWriter();
+ final String type = getNextArg();
+ if (type == null) {
+ pw.println("Error: didn't specify type of data to list");
+ return -1;
+ }
+ switch(type) {
+ case "features":
+ return runListFeatures();
+ case "instrumentation":
+ return runListInstrumentation();
+ case "libraries":
+ return runListLibraries();
+ case "package":
+ case "packages":
+ return runListPackages(false /*showSourceDir*/);
+ case "permission-groups":
+ return runListPermissionGroups();
+ case "permissions":
+ return runListPermissions();
+ }
+ pw.println("Error: unknown list type '" + type + "'");
+ return -1;
+ }
+
+ private int runListFeatures() throws RemoteException {
+ final PrintWriter pw = getOutPrintWriter();
+ final List<FeatureInfo> list = new ArrayList<FeatureInfo>();
+ final FeatureInfo[] rawList = mInterface.getSystemAvailableFeatures();
+ for (int i=0; i<rawList.length; i++) {
+ list.add(rawList[i]);
+ }
+
+ // sort by name
+ Collections.sort(list, new Comparator<FeatureInfo>() {
+ public int compare(FeatureInfo o1, FeatureInfo o2) {
+ if (o1.name == o2.name) return 0;
+ if (o1.name == null) return -1;
+ if (o2.name == null) return 1;
+ return o1.name.compareTo(o2.name);
+ }
+ });
+
+ final int count = (list != null) ? list.size() : 0;
+ for (int p = 0; p < count; p++) {
+ FeatureInfo fi = list.get(p);
+ pw.print("feature:");
+ if (fi.name != null) pw.println(fi.name);
+ else pw.println("reqGlEsVersion=0x"
+ + Integer.toHexString(fi.reqGlEsVersion));
+ }
+ return 0;
+ }
+
+ private int runListInstrumentation() throws RemoteException {
+ final PrintWriter pw = getOutPrintWriter();
+ boolean showSourceDir = false;
+ String targetPackage = null;
+
+ try {
+ String opt;
+ while ((opt = getNextArg()) != null) {
+ switch (opt) {
+ case "-f":
+ showSourceDir = true;
+ break;
+ default:
+ if (opt.charAt(0) != '-') {
+ targetPackage = opt;
+ } else {
+ pw.println("Error: Unknown option: " + opt);
+ return -1;
+ }
+ break;
+ }
+ }
+ } catch (RuntimeException ex) {
+ pw.println("Error: " + ex.toString());
+ return -1;
+ }
+
+ final List<InstrumentationInfo> list =
+ mInterface.queryInstrumentation(targetPackage, 0 /*flags*/);
+
+ // sort by target package
+ Collections.sort(list, new Comparator<InstrumentationInfo>() {
+ public int compare(InstrumentationInfo o1, InstrumentationInfo o2) {
+ return o1.targetPackage.compareTo(o2.targetPackage);
+ }
+ });
+
+ final int count = (list != null) ? list.size() : 0;
+ for (int p = 0; p < count; p++) {
+ final InstrumentationInfo ii = list.get(p);
+ pw.print("instrumentation:");
+ if (showSourceDir) {
+ pw.print(ii.sourceDir);
+ pw.print("=");
+ }
+ final ComponentName cn = new ComponentName(ii.packageName, ii.name);
+ pw.print(cn.flattenToShortString());
+ pw.print(" (target=");
+ pw.print(ii.targetPackage);
+ pw.println(")");
+ }
+ return 0;
+ }
+
+ private int runListLibraries() throws RemoteException {
+ final PrintWriter pw = getOutPrintWriter();
+ final List<String> list = new ArrayList<String>();
+ final String[] rawList = mInterface.getSystemSharedLibraryNames();
+ for (int i = 0; i < rawList.length; i++) {
+ list.add(rawList[i]);
+ }
+
+ // sort by name
+ Collections.sort(list, new Comparator<String>() {
+ public int compare(String o1, String o2) {
+ if (o1 == o2) return 0;
+ if (o1 == null) return -1;
+ if (o2 == null) return 1;
+ return o1.compareTo(o2);
+ }
+ });
+
+ final int count = (list != null) ? list.size() : 0;
+ for (int p = 0; p < count; p++) {
+ String lib = list.get(p);
+ pw.print("library:");
+ pw.println(lib);
+ }
+ return 0;
+ }
+
+ private int runListPackages(boolean showSourceDir) throws RemoteException {
+ final PrintWriter pw = getOutPrintWriter();
+ int getFlags = 0;
+ boolean listDisabled = false, listEnabled = false;
+ boolean listSystem = false, listThirdParty = false;
+ boolean listInstaller = false;
+ int userId = UserHandle.USER_SYSTEM;
+ try {
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ switch (opt) {
+ case "-d":
+ listDisabled = true;
+ break;
+ case "-e":
+ listEnabled = true;
+ break;
+ case "-f":
+ showSourceDir = true;
+ break;
+ case "-i":
+ listInstaller = true;
+ break;
+ case "-l":
+ // old compat
+ break;
+ case "-lf":
+ showSourceDir = true;
+ break;
+ case "-s":
+ listSystem = true;
+ break;
+ case "-u":
+ getFlags |= PackageManager.GET_UNINSTALLED_PACKAGES;
+ break;
+ case "-3":
+ listThirdParty = true;
+ break;
+ case "--user":
+ userId = Integer.parseInt(getNextArg());
+ break;
+ default:
+ pw.println("Error: Unknown option: " + opt);
+ return -1;
+ }
+ }
+ } catch (RuntimeException ex) {
+ pw.println("Error: " + ex.toString());
+ return -1;
+ }
+
+ final String filter = getNextArg();
+
+ @SuppressWarnings("unchecked")
+ final ParceledListSlice<PackageInfo> slice =
+ mInterface.getInstalledPackages(getFlags, userId);
+ final List<PackageInfo> packages = slice.getList();
+
+ final int count = packages.size();
+ for (int p = 0; p < count; p++) {
+ final PackageInfo info = packages.get(p);
+ if (filter != null && !info.packageName.contains(filter)) {
+ continue;
+ }
+ final boolean isSystem =
+ (info.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) != 0;
+ if ((!listDisabled || !info.applicationInfo.enabled) &&
+ (!listEnabled || info.applicationInfo.enabled) &&
+ (!listSystem || isSystem) &&
+ (!listThirdParty || !isSystem)) {
+ pw.print("package:");
+ if (showSourceDir) {
+ pw.print(info.applicationInfo.sourceDir);
+ pw.print("=");
+ }
+ pw.print(info.packageName);
+ if (listInstaller) {
+ pw.print(" installer=");
+ pw.print(mInterface.getInstallerPackageName(info.packageName));
+ }
+ pw.println();
+ }
+ }
+ return 0;
+ }
+
+ private int runListPermissionGroups() throws RemoteException {
+ final PrintWriter pw = getOutPrintWriter();
+ final List<PermissionGroupInfo> pgs = mInterface.getAllPermissionGroups(0);
+
+ final int count = pgs.size();
+ for (int p = 0; p < count ; p++) {
+ final PermissionGroupInfo pgi = pgs.get(p);
+ pw.print("permission group:");
+ pw.println(pgi.name);
+ }
+ return 0;
+ }
+
+ private int runListPermissions() throws RemoteException {
+ final PrintWriter pw = getOutPrintWriter();
+ boolean labels = false;
+ boolean groups = false;
+ boolean userOnly = false;
+ boolean summary = false;
+ boolean dangerousOnly = false;
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ switch (opt) {
+ case "-d":
+ dangerousOnly = true;
+ break;
+ case "-f":
+ labels = true;
+ break;
+ case "-g":
+ groups = true;
+ break;
+ case "-s":
+ groups = true;
+ labels = true;
+ summary = true;
+ break;
+ case "-u":
+ userOnly = true;
+ break;
+ default:
+ pw.println("Error: Unknown option: " + opt);
+ return 1;
+ }
+ }
+
+ final ArrayList<String> groupList = new ArrayList<String>();
+ if (groups) {
+ final List<PermissionGroupInfo> infos =
+ mInterface.getAllPermissionGroups(0 /*flags*/);
+ final int count = infos.size();
+ for (int i = 0; i < count; i++) {
+ groupList.add(infos.get(i).name);
+ }
+ groupList.add(null);
+ } else {
+ final String grp = getNextArg();
+ groupList.add(grp);
+ }
+
+ if (dangerousOnly) {
+ pw.println("Dangerous Permissions:");
+ pw.println("");
+ doListPermissions(groupList, groups, labels, summary,
+ PermissionInfo.PROTECTION_DANGEROUS,
+ PermissionInfo.PROTECTION_DANGEROUS);
+ if (userOnly) {
+ pw.println("Normal Permissions:");
+ pw.println("");
+ doListPermissions(groupList, groups, labels, summary,
+ PermissionInfo.PROTECTION_NORMAL,
+ PermissionInfo.PROTECTION_NORMAL);
+ }
+ } else if (userOnly) {
+ pw.println("Dangerous and Normal Permissions:");
+ pw.println("");
+ doListPermissions(groupList, groups, labels, summary,
+ PermissionInfo.PROTECTION_NORMAL,
+ PermissionInfo.PROTECTION_DANGEROUS);
+ } else {
+ pw.println("All Permissions:");
+ pw.println("");
+ doListPermissions(groupList, groups, labels, summary,
+ -10000, 10000);
+ }
+ return 0;
+ }
+
+ private void doListPermissions(ArrayList<String> groupList, boolean groups, boolean labels,
+ boolean summary, int startProtectionLevel, int endProtectionLevel)
+ throws RemoteException {
+ final PrintWriter pw = getOutPrintWriter();
+ final int groupCount = groupList.size();
+ for (int i = 0; i < groupCount; i++) {
+ String groupName = groupList.get(i);
+ String prefix = "";
+ if (groups) {
+ if (i > 0) {
+ pw.println("");
+ }
+ if (groupName != null) {
+ PermissionGroupInfo pgi =
+ mInterface.getPermissionGroupInfo(groupName, 0 /*flags*/);
+ if (summary) {
+ Resources res = getResources(pgi);
+ if (res != null) {
+ pw.print(loadText(pgi, pgi.labelRes, pgi.nonLocalizedLabel) + ": ");
+ } else {
+ pw.print(pgi.name + ": ");
+
+ }
+ } else {
+ pw.println((labels ? "+ " : "") + "group:" + pgi.name);
+ if (labels) {
+ pw.println(" package:" + pgi.packageName);
+ Resources res = getResources(pgi);
+ if (res != null) {
+ pw.println(" label:"
+ + loadText(pgi, pgi.labelRes, pgi.nonLocalizedLabel));
+ pw.println(" description:"
+ + loadText(pgi, pgi.descriptionRes,
+ pgi.nonLocalizedDescription));
+ }
+ }
+ }
+ } else {
+ pw.println(((labels && !summary) ? "+ " : "") + "ungrouped:");
+ }
+ prefix = " ";
+ }
+ List<PermissionInfo> ps =
+ mInterface.queryPermissionsByGroup(groupList.get(i), 0 /*flags*/);
+ final int count = ps.size();
+ boolean first = true;
+ for (int p = 0 ; p < count ; p++) {
+ PermissionInfo pi = ps.get(p);
+ if (groups && groupName == null && pi.group != null) {
+ continue;
+ }
+ final int base = pi.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE;
+ if (base < startProtectionLevel
+ || base > endProtectionLevel) {
+ continue;
+ }
+ if (summary) {
+ if (first) {
+ first = false;
+ } else {
+ pw.print(", ");
+ }
+ Resources res = getResources(pi);
+ if (res != null) {
+ pw.print(loadText(pi, pi.labelRes,
+ pi.nonLocalizedLabel));
+ } else {
+ pw.print(pi.name);
+ }
+ } else {
+ pw.println(prefix + (labels ? "+ " : "")
+ + "permission:" + pi.name);
+ if (labels) {
+ pw.println(prefix + " package:" + pi.packageName);
+ Resources res = getResources(pi);
+ if (res != null) {
+ pw.println(prefix + " label:"
+ + loadText(pi, pi.labelRes,
+ pi.nonLocalizedLabel));
+ pw.println(prefix + " description:"
+ + loadText(pi, pi.descriptionRes,
+ pi.nonLocalizedDescription));
+ }
+ pw.println(prefix + " protectionLevel:"
+ + PermissionInfo.protectionToString(pi.protectionLevel));
+ }
+ }
+ }
+
+ if (summary) {
+ pw.println("");
+ }
+ }
+ }
+
+ private String loadText(PackageItemInfo pii, int res, CharSequence nonLocalized)
+ throws RemoteException {
+ if (nonLocalized != null) {
+ return nonLocalized.toString();
+ }
+ if (res != 0) {
+ Resources r = getResources(pii);
+ if (r != null) {
+ try {
+ return r.getString(res);
+ } catch (Resources.NotFoundException e) {
+ }
+ }
+ }
+ return null;
+ }
+
+ private Resources getResources(PackageItemInfo pii) throws RemoteException {
+ Resources res = mResourceCache.get(pii.packageName);
+ if (res != null) return res;
+
+ ApplicationInfo ai = mInterface.getApplicationInfo(pii.packageName, 0, 0);
+ AssetManager am = new AssetManager();
+ am.addAssetPath(ai.publicSourceDir);
+ res = new Resources(am, null, null);
+ mResourceCache.put(pii.packageName, res);
+ return res;
+ }
+
+ @Override
+ public void onHelp() {
+ final PrintWriter pw = getOutPrintWriter();
+ pw.println("Package manager (package) commands:");
+ pw.println(" help");
+ pw.println(" Print this help text.");
+ pw.println("");
+ pw.println(" list features");
+ pw.println(" Prints all features of the system.");
+ pw.println(" list instrumentation [-f] [TARGET-PACKAGE]");
+ pw.println(" Prints all test packages; optionally only those targetting TARGET-PACKAGE");
+ pw.println(" Options:");
+ pw.println(" -f: dump the name of the .apk file containing the test package");
+ pw.println(" list libraries");
+ pw.println(" Prints all system libraries.");
+ pw.println(" list packages [-f] [-d] [-e] [-s] [-3] [-i] [-u] [--user USER_ID] [FILTER]");
+ pw.println(" Prints all packages; optionally only those whose name contains");
+ pw.println(" the text in FILTER.");
+ pw.println(" Options:");
+ pw.println(" -f: see their associated file");
+ pw.println(" -d: filter to only show disbled packages");
+ pw.println(" -e: filter to only show enabled packages");
+ pw.println(" -s: filter to only show system packages");
+ pw.println(" -3: filter to only show third party packages");
+ pw.println(" -i: see the installer for the packages");
+ pw.println(" -u: also include uninstalled packages");
+ pw.println(" list permission-groups");
+ pw.println(" Prints all known permission groups.");
+ pw.println(" list permissions [-g] [-f] [-d] [-u] [GROUP]");
+ pw.println(" Prints all known permissions; optionally only those in GROUP.");
+ pw.println(" Options:");
+ pw.println(" -g: organize by group");
+ pw.println(" -f: print all information");
+ pw.println(" -s: short summary");
+ pw.println(" -d: only list dangerous permissions");
+ pw.println(" -u: list only the permissions users will see");
+ pw.println("");
+ }
+}
+
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 5d0e83d0fa15..c41d49359a66 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -22,6 +22,7 @@ import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.ActivityManagerNative;
+import android.app.IActivityManager;
import android.app.IStopUserCallback;
import android.app.admin.DevicePolicyManager;
import android.app.admin.DevicePolicyManagerInternal;
@@ -44,7 +45,9 @@ import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
import android.os.Process;
import android.os.RemoteException;
+import android.os.ResultReceiver;
import android.os.ServiceManager;
+import android.os.ShellCommand;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
@@ -92,13 +95,15 @@ import libcore.io.IoUtils;
* Service for {@link UserManager}.
*
* Method naming convention:
- * - Methods suffixed with "Locked" should be called within the {@code this} lock.
- * - Methods suffixed with "RL" should be called within the {@link #mRestrictionsLock} lock.
+ * <ul>
+ * <li> Methods suffixed with "LILP" should be called within {@link #mInstallLock} and
+ * {@link #mPackagesLock} locks obtained in the respective order.
+ * <li> Methods suffixed with "LR" should be called within the {@link #mRestrictionsLock} lock.
+ * <li> Methods suffixed with "LU" should be called within the {@link #mUsersLock} lock.
+ * </ul>
*/
public class UserManagerService extends IUserManager.Stub {
-
private static final String LOG_TAG = "UserManagerService";
-
private static final boolean DBG = false; // DO NOT SUBMIT WITH TRUE
private static final String TAG_NAME = "name";
@@ -160,15 +165,17 @@ public class UserManagerService extends IUserManager.Stub {
private final PackageManagerService mPm;
private final Object mInstallLock;
private final Object mPackagesLock;
+ // Short-term lock for internal state, when interaction/sync with PM is not required
+ private final Object mUsersLock = new Object();
+ private final Object mRestrictionsLock = new Object();
private final Handler mHandler;
private final File mUsersDir;
private final File mUserListFile;
- private final SparseArray<UserInfo> mUsers = new SparseArray<UserInfo>();
-
- private final Object mRestrictionsLock = new Object();
+ @GuardedBy("mUsersLock")
+ private final SparseArray<UserInfo> mUsers = new SparseArray<>();
/**
* User restrictions set via UserManager. This doesn't include restrictions set by
@@ -179,7 +186,7 @@ public class UserManagerService extends IUserManager.Stub {
* maybe shared between {@link #mBaseUserRestrictions} and
* {@link #mCachedEffectiveUserRestrictions}, but they should always updated separately.
* (Otherwise we won't be able to detect what restrictions have changed in
- * {@link #updateUserRestrictionsInternalRL).
+ * {@link #updateUserRestrictionsInternalLR}.
*/
@GuardedBy("mRestrictionsLock")
private final SparseArray<Bundle> mBaseUserRestrictions = new SparseArray<>();
@@ -194,20 +201,29 @@ public class UserManagerService extends IUserManager.Stub {
* maybe shared between {@link #mBaseUserRestrictions} and
* {@link #mCachedEffectiveUserRestrictions}, but they should always updated separately.
* (Otherwise we won't be able to detect what restrictions have changed in
- * {@link #updateUserRestrictionsInternalRL).
+ * {@link #updateUserRestrictionsInternalLR}.
*/
@GuardedBy("mRestrictionsLock")
private final SparseArray<Bundle> mCachedEffectiveUserRestrictions = new SparseArray<>();
+ /**
+ * User restrictions that have already been applied in {@link #applyUserRestrictionsLR}. We
+ * use it to detect restrictions that have changed since the last
+ * {@link #applyUserRestrictionsLR} call.
+ */
+ @GuardedBy("mRestrictionsLock")
+ private final SparseArray<Bundle> mAppliedUserRestrictions = new SparseArray<>();
+
private final Bundle mGuestRestrictions = new Bundle();
/**
* Set of user IDs being actively removed. Removed IDs linger in this set
* for several seconds to work around a VFS caching issue.
*/
- // @GuardedBy("mPackagesLock")
+ @GuardedBy("mUsersLock")
private final SparseBooleanArray mRemovingUserIds = new SparseBooleanArray();
+ @GuardedBy("mUsersLock")
private int[] mUserIds;
private int mNextSerialNumber;
private int mUserVersion = 0;
@@ -267,7 +283,7 @@ public class UserManagerService extends IUserManager.Stub {
-1, -1);
mUserListFile = new File(mUsersDir, USER_LIST_FILENAME);
initDefaultGuestRestrictions();
- readUserListLocked();
+ readUserListLILP();
sInstance = this;
}
}
@@ -278,21 +294,23 @@ public class UserManagerService extends IUserManager.Stub {
void systemReady() {
synchronized (mInstallLock) {
synchronized (mPackagesLock) {
- // Prune out any partially created/partially removed users.
- ArrayList<UserInfo> partials = new ArrayList<UserInfo>();
- final int userSize = mUsers.size();
- for (int i = 0; i < userSize; i++) {
- UserInfo ui = mUsers.valueAt(i);
- if ((ui.partial || ui.guestToRemove) && i != 0) {
- partials.add(ui);
+ synchronized (mUsersLock) {
+ // Prune out any partially created/partially removed users.
+ ArrayList<UserInfo> partials = new ArrayList<UserInfo>();
+ final int userSize = mUsers.size();
+ for (int i = 0; i < userSize; i++) {
+ UserInfo ui = mUsers.valueAt(i);
+ if ((ui.partial || ui.guestToRemove) && i != 0) {
+ partials.add(ui);
+ }
+ }
+ final int partialsSize = partials.size();
+ for (int i = 0; i < partialsSize; i++) {
+ UserInfo ui = partials.get(i);
+ Slog.w(LOG_TAG, "Removing partially created user " + ui.id
+ + " (name=" + ui.name + ")");
+ removeUserStateLILP(ui.id);
}
- }
- final int partialsSize = partials.size();
- for (int i = 0; i < partialsSize; i++) {
- UserInfo ui = partials.get(i);
- Slog.w(LOG_TAG, "Removing partially created user " + ui.id
- + " (name=" + ui.name + ")");
- removeUserStateLocked(ui.id);
}
}
}
@@ -312,7 +330,7 @@ public class UserManagerService extends IUserManager.Stub {
@Override
public UserInfo getPrimaryUser() {
checkManageUsersPermission("query users");
- synchronized (mPackagesLock) {
+ synchronized (mUsersLock) {
final int userSize = mUsers.size();
for (int i = 0; i < userSize; i++) {
UserInfo ui = mUsers.valueAt(i);
@@ -327,7 +345,7 @@ public class UserManagerService extends IUserManager.Stub {
@Override
public @NonNull List<UserInfo> getUsers(boolean excludeDying) {
checkManageUsersPermission("query users");
- synchronized (mPackagesLock) {
+ synchronized (mUsersLock) {
ArrayList<UserInfo> users = new ArrayList<UserInfo>(mUsers.size());
final int userSize = mUsers.size();
for (int i = 0; i < userSize; i++) {
@@ -350,8 +368,8 @@ public class UserManagerService extends IUserManager.Stub {
}
final long ident = Binder.clearCallingIdentity();
try {
- synchronized (mPackagesLock) {
- return getProfilesLocked(userId, enabledOnly);
+ synchronized (mUsersLock) {
+ return getProfilesLU(userId, enabledOnly);
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -359,8 +377,8 @@ public class UserManagerService extends IUserManager.Stub {
}
/** Assume permissions already checked and caller's identity cleared */
- private List<UserInfo> getProfilesLocked(int userId, boolean enabledOnly) {
- UserInfo user = getUserInfoLocked(userId);
+ private List<UserInfo> getProfilesLU(int userId, boolean enabledOnly) {
+ UserInfo user = getUserInfoLU(userId);
ArrayList<UserInfo> users = new ArrayList<UserInfo>(mUsers.size());
if (user == null) {
// Probably a dying user
@@ -387,8 +405,8 @@ public class UserManagerService extends IUserManager.Stub {
public int getCredentialOwnerProfile(int userHandle) {
checkManageUsersPermission("get the credential owner");
if (!"file".equals(SystemProperties.get("ro.crypto.type", "none"))) {
- synchronized (mPackagesLock) {
- UserInfo profileParent = getProfileParentLocked(userHandle);
+ synchronized (mUsersLock) {
+ UserInfo profileParent = getProfileParentLU(userHandle);
if (profileParent != null) {
return profileParent.id;
}
@@ -403,32 +421,35 @@ public class UserManagerService extends IUserManager.Stub {
if (userId == otherUserId) return true;
checkManageUsersPermission("check if in the same profile group");
synchronized (mPackagesLock) {
- return isSameProfileGroupLocked(userId, otherUserId);
+ return isSameProfileGroupLP(userId, otherUserId);
}
}
- private boolean isSameProfileGroupLocked(int userId, int otherUserId) {
- UserInfo userInfo = getUserInfoLocked(userId);
- if (userInfo == null || userInfo.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID) {
- return false;
- }
- UserInfo otherUserInfo = getUserInfoLocked(otherUserId);
- if (otherUserInfo == null || otherUserInfo.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID) {
- return false;
+ private boolean isSameProfileGroupLP(int userId, int otherUserId) {
+ synchronized (mUsersLock) {
+ UserInfo userInfo = getUserInfoLU(userId);
+ if (userInfo == null || userInfo.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID) {
+ return false;
+ }
+ UserInfo otherUserInfo = getUserInfoLU(otherUserId);
+ if (otherUserInfo == null
+ || otherUserInfo.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID) {
+ return false;
+ }
+ return userInfo.profileGroupId == otherUserInfo.profileGroupId;
}
- return userInfo.profileGroupId == otherUserInfo.profileGroupId;
}
@Override
public UserInfo getProfileParent(int userHandle) {
checkManageUsersPermission("get the profile parent");
- synchronized (mPackagesLock) {
- return getProfileParentLocked(userHandle);
+ synchronized (mUsersLock) {
+ return getProfileParentLU(userHandle);
}
}
- private UserInfo getProfileParentLocked(int userHandle) {
- UserInfo profile = getUserInfoLocked(userHandle);
+ private UserInfo getProfileParentLU(int userHandle) {
+ UserInfo profile = getUserInfoLU(userHandle);
if (profile == null) {
return null;
}
@@ -436,11 +457,11 @@ public class UserManagerService extends IUserManager.Stub {
if (parentUserId == UserInfo.NO_PROFILE_GROUP_ID) {
return null;
} else {
- return getUserInfoLocked(parentUserId);
+ return getUserInfoLU(parentUserId);
}
}
- private boolean isProfileOf(UserInfo user, UserInfo profile) {
+ private static boolean isProfileOf(UserInfo user, UserInfo profile) {
return user.id == profile.id ||
(user.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID
&& user.profileGroupId == profile.profileGroupId);
@@ -450,10 +471,13 @@ public class UserManagerService extends IUserManager.Stub {
public void setUserEnabled(int userId) {
checkManageUsersPermission("enable user");
synchronized (mPackagesLock) {
- UserInfo info = getUserInfoLocked(userId);
+ UserInfo info;
+ synchronized (mUsersLock) {
+ info = getUserInfoLU(userId);
+ }
if (info != null && !info.isEnabled()) {
info.flags ^= UserInfo.FLAG_DISABLED;
- writeUserLocked(info);
+ writeUserLP(info);
}
}
}
@@ -461,23 +485,23 @@ public class UserManagerService extends IUserManager.Stub {
@Override
public UserInfo getUserInfo(int userId) {
checkManageUsersPermission("query user");
- synchronized (mPackagesLock) {
- return getUserInfoLocked(userId);
+ synchronized (mUsersLock) {
+ return getUserInfoLU(userId);
}
}
@Override
public boolean isRestricted() {
- synchronized (mPackagesLock) {
- return getUserInfoLocked(UserHandle.getCallingUserId()).isRestricted();
+ synchronized (mUsersLock) {
+ return getUserInfoLU(UserHandle.getCallingUserId()).isRestricted();
}
}
@Override
public boolean canHaveRestrictedProfile(int userId) {
checkManageUsersPermission("canHaveRestrictedProfile");
- synchronized (mPackagesLock) {
- final UserInfo userInfo = getUserInfoLocked(userId);
+ synchronized (mUsersLock) {
+ final UserInfo userInfo = getUserInfoLU(userId);
if (userInfo == null || !userInfo.canHaveProfile()) {
return false;
}
@@ -494,7 +518,7 @@ public class UserManagerService extends IUserManager.Stub {
/*
* Should be locked on mUsers before calling this.
*/
- private UserInfo getUserInfoLocked(int userId) {
+ private UserInfo getUserInfoLU(int userId) {
UserInfo ui = mUsers.get(userId);
// If it is partial and not in the process of being removed, return as unknown user.
if (ui != null && ui.partial && !mRemovingUserIds.get(userId)) {
@@ -504,11 +528,19 @@ public class UserManagerService extends IUserManager.Stub {
return ui;
}
+ /**
+ * Obtains {@link #mUsersLock} and return UserInfo from mUsers.
+ * <p>No permissions checking or any addition checks are made</p>
+ */
+ private UserInfo getUserInfoNoChecks(int userId) {
+ synchronized (mUsersLock) {
+ return mUsers.get(userId);
+ }
+ }
+
/** Called by PackageManagerService */
public boolean exists(int userId) {
- synchronized (mPackagesLock) {
- return mUsers.get(userId) != null;
- }
+ return getUserInfoNoChecks(userId) != null;
}
@Override
@@ -516,14 +548,14 @@ public class UserManagerService extends IUserManager.Stub {
checkManageUsersPermission("rename users");
boolean changed = false;
synchronized (mPackagesLock) {
- UserInfo info = mUsers.get(userId);
+ UserInfo info = getUserInfoNoChecks(userId);
if (info == null || info.partial) {
Slog.w(LOG_TAG, "setUserName: unknown user #" + userId);
return;
}
if (name != null && !name.equals(info.name)) {
info.name = name;
- writeUserLocked(info);
+ writeUserLP(info);
changed = true;
}
}
@@ -538,13 +570,13 @@ public class UserManagerService extends IUserManager.Stub {
long ident = Binder.clearCallingIdentity();
try {
synchronized (mPackagesLock) {
- UserInfo info = mUsers.get(userId);
+ UserInfo info = getUserInfoNoChecks(userId);
if (info == null || info.partial) {
Slog.w(LOG_TAG, "setUserIcon: unknown user #" + userId);
return;
}
- writeBitmapLocked(info, bitmap);
- writeUserLocked(info);
+ writeBitmapLP(info, bitmap);
+ writeUserLP(info);
}
sendUserInfoChangedBroadcast(userId);
} finally {
@@ -563,12 +595,12 @@ public class UserManagerService extends IUserManager.Stub {
public ParcelFileDescriptor getUserIcon(int userId) {
String iconPath;
synchronized (mPackagesLock) {
- UserInfo info = mUsers.get(userId);
+ UserInfo info = getUserInfoNoChecks(userId);
if (info == null || info.partial) {
Slog.w(LOG_TAG, "getUserIcon: unknown user #" + userId);
return null;
}
- int callingGroupId = mUsers.get(UserHandle.getCallingUserId()).profileGroupId;
+ int callingGroupId = getUserInfoNoChecks(UserHandle.getCallingUserId()).profileGroupId;
if (callingGroupId == UserInfo.NO_PROFILE_GROUP_ID
|| callingGroupId != info.profileGroupId) {
checkManageUsersPermission("get the icon of a user who is not related");
@@ -591,13 +623,14 @@ public class UserManagerService extends IUserManager.Stub {
public void makeInitialized(int userId) {
checkManageUsersPermission("makeInitialized");
synchronized (mPackagesLock) {
- UserInfo info = mUsers.get(userId);
+ UserInfo info = getUserInfoNoChecks(userId);
if (info == null || info.partial) {
Slog.w(LOG_TAG, "makeInitialized: unknown user #" + userId);
+ // TODO Check if we should return here instead of a null check below
}
- if ((info.flags&UserInfo.FLAG_INITIALIZED) == 0) {
+ if (info != null && (info.flags&UserInfo.FLAG_INITIALIZED) == 0) {
info.flags |= UserInfo.FLAG_INITIALIZED;
- scheduleWriteUserLocked(info);
+ scheduleWriteUserLP(info);
}
}
}
@@ -616,6 +649,7 @@ public class UserManagerService extends IUserManager.Stub {
@Override
public Bundle getDefaultGuestRestrictions() {
checkManageUsersPermission("getDefaultGuestRestrictions");
+ // TODO Switch to mGuestRestrictions for locking
synchronized (mPackagesLock) {
return new Bundle(mGuestRestrictions);
}
@@ -624,15 +658,17 @@ public class UserManagerService extends IUserManager.Stub {
@Override
public void setDefaultGuestRestrictions(Bundle restrictions) {
checkManageUsersPermission("setDefaultGuestRestrictions");
- synchronized (mPackagesLock) {
- mGuestRestrictions.clear();
- mGuestRestrictions.putAll(restrictions);
- writeUserListLocked();
+ synchronized (mInstallLock) {
+ synchronized (mPackagesLock) {
+ mGuestRestrictions.clear();
+ mGuestRestrictions.putAll(restrictions);
+ writeUserListLILP();
+ }
}
}
@GuardedBy("mRestrictionsLock")
- private Bundle computeEffectiveUserRestrictionsRL(int userId) {
+ private Bundle computeEffectiveUserRestrictionsLR(int userId) {
final DevicePolicyManagerInternal dpmi =
LocalServices.getService(DevicePolicyManagerInternal.class);
final Bundle systemRestrictions = mBaseUserRestrictions.get(userId);
@@ -648,7 +684,7 @@ public class UserManagerService extends IUserManager.Stub {
}
@GuardedBy("mRestrictionsLock")
- private void invalidateEffectiveUserRestrictionsRL(int userId) {
+ private void invalidateEffectiveUserRestrictionsLR(int userId) {
if (DBG) {
Log.d(LOG_TAG, "invalidateEffectiveUserRestrictions userId=" + userId);
}
@@ -659,7 +695,7 @@ public class UserManagerService extends IUserManager.Stub {
synchronized (mRestrictionsLock) {
Bundle restrictions = mCachedEffectiveUserRestrictions.get(userId);
if (restrictions == null) {
- restrictions = computeEffectiveUserRestrictionsRL(userId);
+ restrictions = computeEffectiveUserRestrictionsLR(userId);
mCachedEffectiveUserRestrictions.put(userId, restrictions);
}
return restrictions;
@@ -705,7 +741,7 @@ public class UserManagerService extends IUserManager.Stub {
UserRestrictionsUtils.merge(newRestrictions, mBaseUserRestrictions.get(userId));
newRestrictions.putBoolean(key, value);
- updateUserRestrictionsInternalRL(newRestrictions, userId);
+ updateUserRestrictionsInternalLR(newRestrictions, userId);
}
}
@@ -721,14 +757,12 @@ public class UserManagerService extends IUserManager.Stub {
* @param userId target user ID.
*/
@GuardedBy("mRestrictionsLock")
- private void updateUserRestrictionsInternalRL(
+ private void updateUserRestrictionsInternalLR(
@Nullable Bundle newRestrictions, int userId) {
if (DBG) {
Log.d(LOG_TAG, "updateUserRestrictionsInternalLocked userId=" + userId
+ " bundle=" + newRestrictions);
}
- final Bundle prevRestrictions = getEffectiveUserRestrictions(userId);
-
// Update system restrictions.
if (newRestrictions != null) {
// If newRestrictions == the current one, it's probably a bug.
@@ -738,15 +772,22 @@ public class UserManagerService extends IUserManager.Stub {
mBaseUserRestrictions.put(userId, newRestrictions);
}
- mCachedEffectiveUserRestrictions.put(
- userId, computeEffectiveUserRestrictionsRL(userId));
+ final Bundle effective = computeEffectiveUserRestrictionsLR(userId);
- applyUserRestrictionsRL(userId, mBaseUserRestrictions.get(userId), prevRestrictions);
+ mCachedEffectiveUserRestrictions.put(userId, effective);
+
+ applyUserRestrictionsLR(userId, effective);
}
@GuardedBy("mRestrictionsLock")
- private void applyUserRestrictionsRL(int userId,
- Bundle newRestrictions, Bundle prevRestrictions) {
+ private void applyUserRestrictionsLR(int userId, Bundle newRestrictions) {
+ final Bundle prevRestrictions = mAppliedUserRestrictions.get(userId);
+
+ if (DBG) {
+ Log.d(LOG_TAG, "applyUserRestrictionsRL userId=" + userId
+ + " new=" + newRestrictions + " prev=" + prevRestrictions);
+ }
+
final long token = Binder.clearCallingIdentity();
try {
mAppOpsService.setUserRestrictions(newRestrictions, userId);
@@ -756,20 +797,22 @@ public class UserManagerService extends IUserManager.Stub {
Binder.restoreCallingIdentity(token);
}
- // TODO Move the code from DPMS.setUserRestriction().
+ UserRestrictionsUtils.applyUserRestrictions(
+ mContext, userId, newRestrictions, prevRestrictions);
+
+ mAppliedUserRestrictions.put(userId, new Bundle(newRestrictions));
}
@GuardedBy("mRestrictionsLock")
- private void updateEffectiveUserRestrictionsRL(int userId) {
- updateUserRestrictionsInternalRL(null, userId);
+ private void updateEffectiveUserRestrictionsLR(int userId) {
+ updateUserRestrictionsInternalLR(null, userId);
}
@GuardedBy("mRestrictionsLock")
- private void updateEffectiveUserRestrictionsForAllUsersRL() {
+ private void updateEffectiveUserRestrictionsForAllUsersLR() {
// First, invalidate all cached values.
- synchronized (mRestrictionsLock) {
- mCachedEffectiveUserRestrictions.clear();
- }
+ mCachedEffectiveUserRestrictions.clear();
+
// We don't want to call into ActivityManagerNative while taking a lock, so we'll call
// it on a handler.
final Runnable r = new Runnable() {
@@ -790,7 +833,7 @@ public class UserManagerService extends IUserManager.Stub {
// TODO: "Apply restrictions upon user start hasn't been implemented. Implement it.
synchronized (mRestrictionsLock) {
for (int i = 0; i < runningUsers.length; i++) {
- updateUserRestrictionsInternalRL(null, runningUsers[i]);
+ updateUserRestrictionsInternalLR(null, runningUsers[i]);
}
}
}
@@ -801,8 +844,12 @@ public class UserManagerService extends IUserManager.Stub {
/**
* Check if we've hit the limit of how many users can be created.
*/
- private boolean isUserLimitReachedLocked() {
- return getAliveUsersExcludingGuestsCountLocked() >= UserManager.getMaxSupportedUsers();
+ private boolean isUserLimitReached() {
+ int count;
+ synchronized (mUsersLock) {
+ count = getAliveUsersExcludingGuestsCountLU();
+ }
+ return count >= UserManager.getMaxSupportedUsers();
}
@Override
@@ -820,18 +867,18 @@ public class UserManagerService extends IUserManager.Stub {
if (managedProfilesCount >= MAX_MANAGED_PROFILES) {
return false;
}
- synchronized(mPackagesLock) {
- UserInfo userInfo = getUserInfoLocked(userId);
+ synchronized(mUsersLock) {
+ UserInfo userInfo = getUserInfoLU(userId);
if (!userInfo.canHaveProfile()) {
return false;
}
- int usersCount = getAliveUsersExcludingGuestsCountLocked();
+ int usersCount = getAliveUsersExcludingGuestsCountLU();
// We allow creating a managed profile in the special case where there is only one user.
return usersCount == 1 || usersCount < UserManager.getMaxSupportedUsers();
}
}
- private int getAliveUsersExcludingGuestsCountLocked() {
+ private int getAliveUsersExcludingGuestsCountLU() {
int aliveUserCount = 0;
final int totalUserCount = mUsers.size();
// Skip over users being removed
@@ -870,7 +917,7 @@ public class UserManagerService extends IUserManager.Stub {
}
}
- private void writeBitmapLocked(UserInfo info, Bitmap bitmap) {
+ private void writeBitmapLP(UserInfo info, Bitmap bitmap) {
try {
File dir = new File(mUsersDir, Integer.toString(info.id));
File file = new File(dir, USER_PHOTO_FILENAME);
@@ -904,18 +951,14 @@ public class UserManagerService extends IUserManager.Stub {
* @return the array of user ids.
*/
public int[] getUserIds() {
- synchronized (mPackagesLock) {
+ synchronized (mUsersLock) {
return mUserIds;
}
}
- int[] getUserIdsLPr() {
- return mUserIds;
- }
-
- private void readUserListLocked() {
+ private void readUserListLILP() {
if (!mUserListFile.exists()) {
- fallbackToSingleUserLocked();
+ fallbackToSingleUserLILP();
return;
}
FileInputStream fis = null;
@@ -932,7 +975,7 @@ public class UserManagerService extends IUserManager.Stub {
if (type != XmlPullParser.START_TAG) {
Slog.e(LOG_TAG, "Unable to read user list");
- fallbackToSingleUserLocked();
+ fallbackToSingleUserLILP();
return;
}
@@ -953,12 +996,14 @@ public class UserManagerService extends IUserManager.Stub {
final String name = parser.getName();
if (name.equals(TAG_USER)) {
String id = parser.getAttributeValue(null, ATTR_ID);
- UserInfo user = readUserLocked(Integer.parseInt(id));
+ UserInfo user = readUserLILP(Integer.parseInt(id));
if (user != null) {
- mUsers.put(user.id, user);
- if (mNextSerialNumber < 0 || mNextSerialNumber <= user.id) {
- mNextSerialNumber = user.id + 1;
+ synchronized (mUsersLock) {
+ mUsers.put(user.id, user);
+ if (mNextSerialNumber < 0 || mNextSerialNumber <= user.id) {
+ mNextSerialNumber = user.id + 1;
+ }
}
}
} else if (name.equals(TAG_GUEST_RESTRICTIONS)) {
@@ -975,12 +1020,12 @@ public class UserManagerService extends IUserManager.Stub {
}
}
}
- updateUserIdsLocked();
- upgradeIfNecessaryLocked();
+ updateUserIds();
+ upgradeIfNecessaryLILP();
} catch (IOException ioe) {
- fallbackToSingleUserLocked();
+ fallbackToSingleUserLILP();
} catch (XmlPullParserException pe) {
- fallbackToSingleUserLocked();
+ fallbackToSingleUserLILP();
} finally {
if (fis != null) {
try {
@@ -994,24 +1039,24 @@ public class UserManagerService extends IUserManager.Stub {
/**
* Upgrade steps between versions, either for fixing bugs or changing the data format.
*/
- private void upgradeIfNecessaryLocked() {
+ private void upgradeIfNecessaryLILP() {
int userVersion = mUserVersion;
if (userVersion < 1) {
// Assign a proper name for the owner, if not initialized correctly before
- UserInfo user = mUsers.get(UserHandle.USER_SYSTEM);
+ UserInfo user = getUserInfoNoChecks(UserHandle.USER_SYSTEM);
if ("Primary".equals(user.name)) {
user.name = mContext.getResources().getString(com.android.internal.R.string.owner_name);
- scheduleWriteUserLocked(user);
+ scheduleWriteUserLP(user);
}
userVersion = 1;
}
if (userVersion < 2) {
// Owner should be marked as initialized
- UserInfo user = mUsers.get(UserHandle.USER_SYSTEM);
+ UserInfo user = getUserInfoNoChecks(UserHandle.USER_SYSTEM);
if ((user.flags & UserInfo.FLAG_INITIALIZED) == 0) {
user.flags |= UserInfo.FLAG_INITIALIZED;
- scheduleWriteUserLocked(user);
+ scheduleWriteUserLP(user);
}
userVersion = 2;
}
@@ -1028,13 +1073,15 @@ public class UserManagerService extends IUserManager.Stub {
if (userVersion < 6) {
final boolean splitSystemUser = UserManager.isSplitSystemUser();
- for (int i = 0; i < mUsers.size(); i++) {
- UserInfo user = mUsers.valueAt(i);
- // In non-split mode, only user 0 can have restricted profiles
- if (!splitSystemUser && user.isRestricted()
- && (user.restrictedProfileParentId == UserInfo.NO_PROFILE_GROUP_ID)) {
- user.restrictedProfileParentId = UserHandle.USER_SYSTEM;
- scheduleWriteUserLocked(user);
+ synchronized (mUsersLock) {
+ for (int i = 0; i < mUsers.size(); i++) {
+ UserInfo user = mUsers.valueAt(i);
+ // In non-split mode, only user 0 can have restricted profiles
+ if (!splitSystemUser && user.isRestricted()
+ && (user.restrictedProfileParentId == UserInfo.NO_PROFILE_GROUP_ID)) {
+ user.restrictedProfileParentId = UserHandle.USER_SYSTEM;
+ scheduleWriteUserLP(user);
+ }
}
}
userVersion = 6;
@@ -1045,11 +1092,11 @@ public class UserManagerService extends IUserManager.Stub {
+ USER_VERSION);
} else {
mUserVersion = userVersion;
- writeUserListLocked();
+ writeUserListLILP();
}
}
- private void fallbackToSingleUserLocked() {
+ private void fallbackToSingleUserLILP() {
int flags = UserInfo.FLAG_INITIALIZED;
// In split system user mode, the admin and primary flags are assigned to the first human
// user.
@@ -1060,7 +1107,9 @@ public class UserManagerService extends IUserManager.Stub {
UserInfo system = new UserInfo(UserHandle.USER_SYSTEM,
mContext.getResources().getString(com.android.internal.R.string.owner_name), null,
flags);
- mUsers.put(system.id, system);
+ synchronized (mUsersLock) {
+ mUsers.put(system.id, system);
+ }
mNextSerialNumber = MIN_USER_ID;
mUserVersion = USER_VERSION;
@@ -1069,14 +1118,14 @@ public class UserManagerService extends IUserManager.Stub {
mBaseUserRestrictions.append(UserHandle.USER_SYSTEM, restrictions);
}
- updateUserIdsLocked();
+ updateUserIds();
initDefaultGuestRestrictions();
- writeUserListLocked();
- writeUserLocked(system);
+ writeUserListLILP();
+ writeUserLP(system);
}
- private void scheduleWriteUserLocked(UserInfo userInfo) {
+ private void scheduleWriteUserLP(UserInfo userInfo) {
if (!mHandler.hasMessages(WRITE_USER_MSG, userInfo)) {
Message msg = mHandler.obtainMessage(WRITE_USER_MSG, userInfo);
mHandler.sendMessageDelayed(msg, WRITE_USER_DELAY);
@@ -1090,7 +1139,7 @@ public class UserManagerService extends IUserManager.Stub {
* <name>Primary</name>
* </user>
*/
- private void writeUserLocked(UserInfo userInfo) {
+ private void writeUserLP(UserInfo userInfo) {
FileOutputStream fos = null;
AtomicFile userFile = new AtomicFile(new File(mUsersDir, userInfo.id + XML_SUFFIX));
try {
@@ -1156,7 +1205,8 @@ public class UserManagerService extends IUserManager.Stub {
* <user id="2"></user>
* </users>
*/
- private void writeUserListLocked() {
+ private void writeUserListLILP() {
+ // TODO Investigate removing a dependency on mInstallLock
FileOutputStream fos = null;
AtomicFile userListFile = new AtomicFile(mUserListFile);
try {
@@ -1177,11 +1227,17 @@ public class UserManagerService extends IUserManager.Stub {
UserRestrictionsUtils
.writeRestrictions(serializer, mGuestRestrictions, TAG_RESTRICTIONS);
serializer.endTag(null, TAG_GUEST_RESTRICTIONS);
- final int userSize = mUsers.size();
- for (int i = 0; i < userSize; i++) {
- UserInfo user = mUsers.valueAt(i);
+ int[] userIdsToWrite;
+ synchronized (mUsersLock) {
+ userIdsToWrite = new int[mUsers.size()];
+ for (int i = 0; i < userIdsToWrite.length; i++) {
+ UserInfo user = mUsers.valueAt(i);
+ userIdsToWrite[i] = user.id;
+ }
+ }
+ for (int id : userIdsToWrite) {
serializer.startTag(null, TAG_USER);
- serializer.attribute(null, ATTR_ID, Integer.toString(user.id));
+ serializer.attribute(null, ATTR_ID, Integer.toString(id));
serializer.endTag(null, TAG_USER);
}
@@ -1195,7 +1251,7 @@ public class UserManagerService extends IUserManager.Stub {
}
}
- private UserInfo readUserLocked(int id) {
+ private UserInfo readUserLILP(int id) {
int flags = 0;
int serialNumber = id;
String name = null;
@@ -1389,20 +1445,22 @@ public class UserManagerService extends IUserManager.Stub {
synchronized (mPackagesLock) {
UserInfo parent = null;
if (parentId != UserHandle.USER_NULL) {
- parent = getUserInfoLocked(parentId);
+ synchronized (mUsersLock) {
+ parent = getUserInfoLU(parentId);
+ }
if (parent == null) return null;
}
if (isManagedProfile && !canAddMoreManagedProfiles(parentId)) {
Log.e(LOG_TAG, "Cannot add more managed profiles for user " + parentId);
return null;
}
- if (!isGuest && !isManagedProfile && isUserLimitReachedLocked()) {
+ if (!isGuest && !isManagedProfile && isUserLimitReached()) {
// If we're not adding a guest user or a managed profile and the limit has
// been reached, cannot add a user.
return null;
}
// If we're adding a guest and there already exists one, bail.
- if (isGuest && findCurrentGuestUserLocked() != null) {
+ if (isGuest && findCurrentGuestUser() != null) {
return null;
}
// In legacy mode, restricted profile's parent can only be the owner user
@@ -1436,7 +1494,7 @@ public class UserManagerService extends IUserManager.Stub {
flags |= UserInfo.FLAG_ADMIN;
}
}
- userId = getNextAvailableIdLocked();
+ userId = getNextAvailableId();
userInfo = new UserInfo(userId, name, null, flags);
userInfo.serialNumber = mNextSerialNumber++;
long now = System.currentTimeMillis();
@@ -1444,12 +1502,12 @@ public class UserManagerService extends IUserManager.Stub {
userInfo.partial = true;
Environment.getUserSystemDirectory(userInfo.id).mkdirs();
mUsers.put(userId, userInfo);
- writeUserListLocked();
+ writeUserListLILP();
if (parent != null) {
if (isManagedProfile) {
if (parent.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID) {
parent.profileGroupId = parent.id;
- scheduleWriteUserLocked(parent);
+ scheduleWriteUserLP(parent);
}
userInfo.profileGroupId = parent.profileGroupId;
} else if (isRestricted) {
@@ -1458,7 +1516,7 @@ public class UserManagerService extends IUserManager.Stub {
}
if (parent.restrictedProfileParentId == UserInfo.NO_PROFILE_GROUP_ID) {
parent.restrictedProfileParentId = parent.id;
- scheduleWriteUserLocked(parent);
+ scheduleWriteUserLP(parent);
}
userInfo.restrictedProfileParentId = parent.restrictedProfileParentId;
}
@@ -1477,8 +1535,8 @@ public class UserManagerService extends IUserManager.Stub {
}
mPm.createNewUserLILPw(userId);
userInfo.partial = false;
- scheduleWriteUserLocked(userInfo);
- updateUserIdsLocked();
+ scheduleWriteUserLP(userInfo);
+ updateUserIds();
Bundle restrictions = new Bundle();
synchronized (mRestrictionsLock) {
mBaseUserRestrictions.append(userId, restrictions);
@@ -1521,12 +1579,14 @@ public class UserManagerService extends IUserManager.Stub {
* Find the current guest user. If the Guest user is partial,
* then do not include it in the results as it is about to die.
*/
- private UserInfo findCurrentGuestUserLocked() {
- final int size = mUsers.size();
- for (int i = 0; i < size; i++) {
- final UserInfo user = mUsers.valueAt(i);
- if (user.isGuest() && !user.guestToRemove && !mRemovingUserIds.get(user.id)) {
- return user;
+ private UserInfo findCurrentGuestUser() {
+ synchronized (mUsersLock) {
+ final int size = mUsers.size();
+ for (int i = 0; i < size; i++) {
+ final UserInfo user = mUsers.valueAt(i);
+ if (user.isGuest() && !user.guestToRemove && !mRemovingUserIds.get(user.id)) {
+ return user;
+ }
}
}
return null;
@@ -1550,9 +1610,11 @@ public class UserManagerService extends IUserManager.Stub {
try {
final UserInfo user;
synchronized (mPackagesLock) {
- user = mUsers.get(userHandle);
- if (userHandle == 0 || user == null || mRemovingUserIds.get(userHandle)) {
- return false;
+ synchronized (mUsersLock) {
+ user = mUsers.get(userHandle);
+ if (userHandle == 0 || user == null || mRemovingUserIds.get(userHandle)) {
+ return false;
+ }
}
if (!user.isGuest()) {
return false;
@@ -1566,7 +1628,7 @@ public class UserManagerService extends IUserManager.Stub {
// Mark it as disabled, so that it isn't returned any more when
// profiles are queried.
user.flags |= UserInfo.FLAG_DISABLED;
- writeUserLocked(user);
+ writeUserLP(user);
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -1596,15 +1658,17 @@ public class UserManagerService extends IUserManager.Stub {
return false;
}
synchronized (mPackagesLock) {
- user = mUsers.get(userHandle);
- if (userHandle == 0 || user == null || mRemovingUserIds.get(userHandle)) {
- return false;
- }
+ synchronized (mUsersLock) {
+ user = mUsers.get(userHandle);
+ if (userHandle == 0 || user == null || mRemovingUserIds.get(userHandle)) {
+ return false;
+ }
- // We remember deleted user IDs to prevent them from being
- // reused during the current boot; they can still be reused
- // after a reboot.
- mRemovingUserIds.put(userHandle, true);
+ // We remember deleted user IDs to prevent them from being
+ // reused during the current boot; they can still be reused
+ // after a reboot.
+ mRemovingUserIds.put(userHandle, true);
+ }
try {
mAppOpsService.removeUser(userHandle);
@@ -1618,7 +1682,7 @@ public class UserManagerService extends IUserManager.Stub {
// Mark it as disabled, so that it isn't returned any more when
// profiles are queried.
user.flags |= UserInfo.FLAG_DISABLED;
- writeUserLocked(user);
+ writeUserLP(user);
}
if (user.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID
@@ -1676,7 +1740,7 @@ public class UserManagerService extends IUserManager.Stub {
.onUserRemoved(userHandle);
synchronized (mInstallLock) {
synchronized (mPackagesLock) {
- removeUserStateLocked(userHandle);
+ removeUserStateLILP(userHandle);
}
}
}
@@ -1690,20 +1754,22 @@ public class UserManagerService extends IUserManager.Stub {
}
}
- private void removeUserStateLocked(final int userHandle) {
+ private void removeUserStateLILP(final int userHandle) {
mContext.getSystemService(StorageManager.class)
.deleteUserKey(userHandle);
// Cleanup package manager settings
mPm.cleanUpUserLILPw(this, userHandle);
// Remove this user from the list
- mUsers.remove(userHandle);
+ synchronized (mUsersLock) {
+ mUsers.remove(userHandle);
+ }
// Remove user file
AtomicFile userFile = new AtomicFile(new File(mUsersDir, userHandle + XML_SUFFIX));
userFile.delete();
// Update the user list
- writeUserListLocked();
- updateUserIdsLocked();
+ writeUserListLILP();
+ updateUserIds();
removeDirectoryRecursive(Environment.getUserSystemDirectory(userHandle));
}
@@ -1739,7 +1805,7 @@ public class UserManagerService extends IUserManager.Stub {
}
synchronized (mPackagesLock) {
// Read the restrictions from XML
- return readApplicationRestrictionsLocked(packageName, userId);
+ return readApplicationRestrictionsLP(packageName, userId);
}
}
@@ -1752,7 +1818,7 @@ public class UserManagerService extends IUserManager.Stub {
cleanAppRestrictionsForPackage(packageName, userId);
} else {
// Write the restrictions to XML
- writeApplicationRestrictionsLocked(packageName, restrictions, userId);
+ writeApplicationRestrictionsLP(packageName, restrictions, userId);
}
}
@@ -1800,16 +1866,15 @@ public class UserManagerService extends IUserManager.Stub {
}
}
- private Bundle readApplicationRestrictionsLocked(String packageName,
- int userId) {
+ private Bundle readApplicationRestrictionsLP(String packageName, int userId) {
AtomicFile restrictionsFile =
new AtomicFile(new File(Environment.getUserSystemDirectory(userId),
packageToRestrictionsFileName(packageName)));
- return readApplicationRestrictionsLocked(restrictionsFile);
+ return readApplicationRestrictionsLP(restrictionsFile);
}
@VisibleForTesting
- static Bundle readApplicationRestrictionsLocked(AtomicFile restrictionsFile) {
+ static Bundle readApplicationRestrictionsLP(AtomicFile restrictionsFile) {
final Bundle restrictions = new Bundle();
final ArrayList<String> values = new ArrayList<>();
if (!restrictionsFile.getBaseFile().exists()) {
@@ -1892,17 +1957,16 @@ public class UserManagerService extends IUserManager.Stub {
return childBundle;
}
- private void writeApplicationRestrictionsLocked(String packageName,
+ private void writeApplicationRestrictionsLP(String packageName,
Bundle restrictions, int userId) {
AtomicFile restrictionsFile = new AtomicFile(
new File(Environment.getUserSystemDirectory(userId),
packageToRestrictionsFileName(packageName)));
- writeApplicationRestrictionsLocked(restrictions, restrictionsFile);
+ writeApplicationRestrictionsLP(restrictions, restrictionsFile);
}
@VisibleForTesting
- static void writeApplicationRestrictionsLocked(Bundle restrictions,
- AtomicFile restrictionsFile) {
+ static void writeApplicationRestrictionsLP(Bundle restrictions, AtomicFile restrictionsFile) {
FileOutputStream fos = null;
try {
fos = restrictionsFile.startWrite();
@@ -1972,17 +2036,17 @@ public class UserManagerService extends IUserManager.Stub {
@Override
public int getUserSerialNumber(int userHandle) {
- synchronized (mPackagesLock) {
+ synchronized (mUsersLock) {
if (!exists(userHandle)) return -1;
- return getUserInfoLocked(userHandle).serialNumber;
+ return getUserInfoLU(userHandle).serialNumber;
}
}
@Override
public int getUserHandle(int userSerialNumber) {
- synchronized (mPackagesLock) {
+ synchronized (mUsersLock) {
for (int userId : mUserIds) {
- UserInfo info = getUserInfoLocked(userId);
+ UserInfo info = getUserInfoLU(userId);
if (info != null && info.serialNumber == userSerialNumber) return userId;
}
// Not found
@@ -1994,13 +2058,13 @@ public class UserManagerService extends IUserManager.Stub {
public long getUserCreationTime(int userHandle) {
int callingUserId = UserHandle.getCallingUserId();
UserInfo userInfo = null;
- synchronized (mPackagesLock) {
+ synchronized (mUsersLock) {
if (callingUserId == userHandle) {
- userInfo = getUserInfoLocked(userHandle);
+ userInfo = getUserInfoLU(userHandle);
} else {
- UserInfo parent = getProfileParentLocked(userHandle);
+ UserInfo parent = getProfileParentLU(userHandle);
if (parent != null && parent.id == callingUserId) {
- userInfo = getUserInfoLocked(userHandle);
+ userInfo = getUserInfoLU(userHandle);
}
}
}
@@ -2014,22 +2078,24 @@ public class UserManagerService extends IUserManager.Stub {
/**
* Caches the list of user ids in an array, adjusting the array size when necessary.
*/
- private void updateUserIdsLocked() {
+ private void updateUserIds() {
int num = 0;
- final int userSize = mUsers.size();
- for (int i = 0; i < userSize; i++) {
- if (!mUsers.valueAt(i).partial) {
- num++;
+ synchronized (mUsersLock) {
+ final int userSize = mUsers.size();
+ for (int i = 0; i < userSize; i++) {
+ if (!mUsers.valueAt(i).partial) {
+ num++;
+ }
}
- }
- final int[] newUsers = new int[num];
- int n = 0;
- for (int i = 0; i < userSize; i++) {
- if (!mUsers.valueAt(i).partial) {
- newUsers[n++] = mUsers.keyAt(i);
+ final int[] newUsers = new int[num];
+ int n = 0;
+ for (int i = 0; i < userSize; i++) {
+ if (!mUsers.valueAt(i).partial) {
+ newUsers[n++] = mUsers.keyAt(i);
+ }
}
+ mUserIds = newUsers;
}
- mUserIds = newUsers;
}
/**
@@ -2038,7 +2104,7 @@ public class UserManagerService extends IUserManager.Stub {
*/
public void onUserForeground(int userId) {
synchronized (mPackagesLock) {
- UserInfo user = mUsers.get(userId);
+ UserInfo user = getUserInfoNoChecks(userId);
long now = System.currentTimeMillis();
if (user == null || user.partial) {
Slog.w(LOG_TAG, "userForeground: unknown user #" + userId);
@@ -2046,7 +2112,7 @@ public class UserManagerService extends IUserManager.Stub {
}
if (now > EPOCH_PLUS_30_YEARS) {
user.lastLoggedInTime = now;
- scheduleWriteUserLocked(user);
+ scheduleWriteUserLP(user);
}
}
}
@@ -2057,8 +2123,8 @@ public class UserManagerService extends IUserManager.Stub {
* for data and battery stats collection, or unexpected cross-talk.
* @return
*/
- private int getNextAvailableIdLocked() {
- synchronized (mPackagesLock) {
+ private int getNextAvailableId() {
+ synchronized (mUsersLock) {
int i = MIN_USER_ID;
while (i < MAX_USER_ID) {
if (mUsers.indexOfKey(i) < 0 && !mRemovingUserIds.get(i)) {
@@ -2150,6 +2216,45 @@ public class UserManagerService extends IUserManager.Stub {
}
@Override
+ public void onShellCommand(FileDescriptor in, FileDescriptor out,
+ FileDescriptor err, String[] args, ResultReceiver resultReceiver) {
+ (new Shell()).exec(this, in, out, err, args, resultReceiver);
+ }
+
+ int onShellCommand(Shell shell, String cmd) {
+ if (cmd == null) {
+ return shell.handleDefaultCommands(cmd);
+ }
+
+ final PrintWriter pw = shell.getOutPrintWriter();
+ try {
+ switch(cmd) {
+ case "list":
+ return runList(pw);
+ }
+ } catch (RemoteException e) {
+ pw.println("Remote exception: " + e);
+ }
+ return -1;
+ }
+
+ private int runList(PrintWriter pw) throws RemoteException {
+ final IActivityManager am = ActivityManagerNative.getDefault();
+ final List<UserInfo> users = getUsers(false);
+ if (users == null) {
+ pw.println("Error: couldn't get users");
+ return 1;
+ } else {
+ pw.println("Users:");
+ for (int i = 0; i < users.size(); i++) {
+ String running = am.isUserRunning(users.get(i).id, false) ? " running" : "";
+ pw.println("\t" + users.get(i).toString() + running);
+ }
+ return 0;
+ }
+ }
+
+ @Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
!= PackageManager.PERMISSION_GRANTED) {
@@ -2164,38 +2269,49 @@ public class UserManagerService extends IUserManager.Stub {
long now = System.currentTimeMillis();
StringBuilder sb = new StringBuilder();
synchronized (mPackagesLock) {
- pw.println("Users:");
- for (int i = 0; i < mUsers.size(); i++) {
- UserInfo user = mUsers.valueAt(i);
- if (user == null) continue;
- pw.print(" "); pw.print(user); pw.print(" serialNo="); pw.print(user.serialNumber);
- if (mRemovingUserIds.get(mUsers.keyAt(i))) pw.print(" <removing> ");
- if (user.partial) pw.print(" <partial>");
- pw.println();
- pw.print(" Created: ");
- if (user.creationTime == 0) {
- pw.println("<unknown>");
- } else {
- sb.setLength(0);
- TimeUtils.formatDuration(now - user.creationTime, sb);
- sb.append(" ago");
- pw.println(sb);
- }
- pw.print(" Last logged in: ");
- if (user.lastLoggedInTime == 0) {
- pw.println("<unknown>");
- } else {
- sb.setLength(0);
- TimeUtils.formatDuration(now - user.lastLoggedInTime, sb);
- sb.append(" ago");
- pw.println(sb);
+ synchronized (mUsersLock) {
+ pw.println("Users:");
+ for (int i = 0; i < mUsers.size(); i++) {
+ UserInfo user = mUsers.valueAt(i);
+ if (user == null) {
+ continue;
+ }
+ pw.print(" "); pw.print(user);
+ pw.print(" serialNo="); pw.print(user.serialNumber);
+ if (mRemovingUserIds.get(mUsers.keyAt(i))) {
+ pw.print(" <removing> ");
+ }
+ if (user.partial) {
+ pw.print(" <partial>");
+ }
+ pw.println();
+ pw.print(" Created: ");
+ if (user.creationTime == 0) {
+ pw.println("<unknown>");
+ } else {
+ sb.setLength(0);
+ TimeUtils.formatDuration(now - user.creationTime, sb);
+ sb.append(" ago");
+ pw.println(sb);
+ }
+ pw.print(" Last logged in: ");
+ if (user.lastLoggedInTime == 0) {
+ pw.println("<unknown>");
+ } else {
+ sb.setLength(0);
+ TimeUtils.formatDuration(now - user.lastLoggedInTime, sb);
+ sb.append(" ago");
+ pw.println(sb);
+ }
+ pw.println(" Restrictions:");
+ synchronized (mRestrictionsLock) {
+ UserRestrictionsUtils.dumpRestrictions(
+ pw, " ", mBaseUserRestrictions.get(user.id));
+ pw.println(" Effective restrictions:");
+ UserRestrictionsUtils.dumpRestrictions(
+ pw, " ", mCachedEffectiveUserRestrictions.get(user.id));
+ }
}
- pw.println(" Restrictions:");
- UserRestrictionsUtils.dumpRestrictions(
- pw, " ", mBaseUserRestrictions.get(user.id));
- pw.println(" Effective restrictions:");
- UserRestrictionsUtils.dumpRestrictions(
- pw, " ", mCachedEffectiveUserRestrictions.get(user.id));
}
pw.println();
pw.println("Guest restrictions:");
@@ -2212,9 +2328,9 @@ public class UserManagerService extends IUserManager.Stub {
removeMessages(WRITE_USER_MSG, msg.obj);
synchronized (mPackagesLock) {
int userId = ((UserInfo) msg.obj).id;
- UserInfo userInfo = mUsers.get(userId);
+ UserInfo userInfo = getUserInfoNoChecks(userId);
if (userInfo != null) {
- writeUserLocked(userInfo);
+ writeUserLP(userInfo);
}
}
}
@@ -2238,14 +2354,14 @@ public class UserManagerService extends IUserManager.Stub {
@Override
@GuardedBy("mRestrictionsLock")
- public void updateEffectiveUserRestrictionsRL(int userId) {
- UserManagerService.this.updateEffectiveUserRestrictionsRL(userId);
+ public void updateEffectiveUserRestrictionsLR(int userId) {
+ UserManagerService.this.updateEffectiveUserRestrictionsLR(userId);
}
@Override
@GuardedBy("mRestrictionsLock")
- public void updateEffectiveUserRestrictionsForAllUsersRL() {
- UserManagerService.this.updateEffectiveUserRestrictionsForAllUsersRL();
+ public void updateEffectiveUserRestrictionsForAllUsersLR() {
+ UserManagerService.this.updateEffectiveUserRestrictionsForAllUsersLR();
}
@Override
@@ -2260,17 +2376,35 @@ public class UserManagerService extends IUserManager.Stub {
int userId, Bundle baseRestrictions) {
synchronized (mRestrictionsLock) {
mBaseUserRestrictions.put(userId, new Bundle(baseRestrictions));
- invalidateEffectiveUserRestrictionsRL(userId);
+ invalidateEffectiveUserRestrictionsLR(userId);
}
+ final UserInfo userInfo = getUserInfoNoChecks(userId);
synchronized (mPackagesLock) {
- final UserInfo userInfo = mUsers.get(userId);
if (userInfo != null) {
- writeUserLocked(userInfo);
+ writeUserLP(userInfo);
} else {
Slog.w(LOG_TAG, "UserInfo not found for " + userId);
}
}
}
}
+
+ private class Shell extends ShellCommand {
+ @Override
+ public int onCommand(String cmd) {
+ return onShellCommand(this, cmd);
+ }
+
+ @Override
+ public void onHelp() {
+ final PrintWriter pw = getOutPrintWriter();
+ pw.println("User manager (user) commands:");
+ pw.println(" help");
+ pw.println(" Print this help text.");
+ pw.println("");
+ pw.println(" list");
+ pw.println(" Prints all users on the system.");
+ }
+ }
}
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 23e3b35ae3da..28df9f673ed0 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -18,10 +18,19 @@ package com.android.server.pm;
import com.google.android.collect.Sets;
-import com.android.internal.util.Preconditions;
-
+import android.annotation.Nullable;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.media.IAudioService;
+import android.net.Uri;
+import android.os.Binder;
import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemProperties;
+import android.os.UserHandle;
import android.os.UserManager;
+import android.util.Slog;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlSerializer;
@@ -31,6 +40,8 @@ import java.io.PrintWriter;
import java.util.Set;
public class UserRestrictionsUtils {
+ private static final String TAG = "UserRestrictionsUtils";
+
private UserRestrictionsUtils() {
}
@@ -115,6 +126,118 @@ public class UserRestrictionsUtils {
}
}
+ /**
+ * Takes a new use restriction set and the previous set, and apply the restrictions that have
+ * changed.
+ */
+ public static void applyUserRestrictions(Context context, int userId,
+ @Nullable Bundle newRestrictions, @Nullable Bundle prevRestrictions) {
+ if (newRestrictions == null) {
+ newRestrictions = Bundle.EMPTY;
+ }
+ if (prevRestrictions == null) {
+ prevRestrictions = Bundle.EMPTY;
+ }
+ for (String key : USER_RESTRICTIONS) {
+ final boolean newValue = newRestrictions.getBoolean(key);
+ final boolean prevValue = prevRestrictions.getBoolean(key);
+
+ if (newValue != prevValue) {
+ applyUserRestriction(context, userId, key, newValue);
+ }
+ }
+ }
+
+ private static void applyUserRestriction(Context context, int userId, String key,
+ boolean newValue) {
+ // When certain restrictions are cleared, we don't update the system settings,
+ // because these settings are changeable on the Settings UI and we don't know the original
+ // value -- for example LOCATION_MODE might have been off already when the restriction was
+ // set, and in that case even if the restriction is lifted, changing it to ON would be
+ // wrong. So just don't do anything in such a case. If the user hopes to enable location
+ // later, they can do it on the Settings UI.
+
+ final ContentResolver cr = context.getContentResolver();
+ final long id = Binder.clearCallingIdentity();
+ try {
+ switch (key) {
+ case UserManager.DISALLOW_UNMUTE_MICROPHONE:
+ IAudioService.Stub.asInterface(ServiceManager.getService(Context.AUDIO_SERVICE))
+ .setMicrophoneMute(newValue, context.getPackageName(), userId);
+ break;
+ case UserManager.DISALLOW_ADJUST_VOLUME:
+ IAudioService.Stub.asInterface(ServiceManager.getService(Context.AUDIO_SERVICE))
+ .setMasterMute(newValue, 0, context.getPackageName(), userId);
+ break;
+ case UserManager.DISALLOW_CONFIG_WIFI:
+ if (newValue) {
+ android.provider.Settings.Secure.putIntForUser(cr,
+ android.provider.Settings.Secure
+ .WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 0, userId);
+ }
+ break;
+ case UserManager.DISALLOW_SHARE_LOCATION:
+ if (newValue) {
+ android.provider.Settings.Secure.putIntForUser(cr,
+ android.provider.Settings.Secure.LOCATION_MODE,
+ android.provider.Settings.Secure.LOCATION_MODE_OFF,
+ userId);
+ android.provider.Settings.Secure.putStringForUser(cr,
+ android.provider.Settings.Secure.LOCATION_PROVIDERS_ALLOWED, "",
+ userId);
+ }
+ // Send out notifications as some clients may want to reread the
+ // value which actually changed due to a restriction having been
+ // applied.
+ final String property =
+ android.provider.Settings.Secure.SYS_PROP_SETTING_VERSION;
+ long version = SystemProperties.getLong(property, 0) + 1;
+ SystemProperties.set(property, Long.toString(version));
+
+ final String name = android.provider.Settings.Secure.LOCATION_PROVIDERS_ALLOWED;
+ final Uri url = Uri.withAppendedPath(
+ android.provider.Settings.Secure.CONTENT_URI, name);
+ context.getContentResolver().notifyChange(url, null, true, userId);
+
+ break;
+ case UserManager.DISALLOW_DEBUGGING_FEATURES:
+ if (newValue) {
+ // Only disable adb if changing for system user, since it is global
+ // TODO: should this be admin user?
+ if (userId == UserHandle.USER_SYSTEM) {
+ android.provider.Settings.Global.putStringForUser(cr,
+ android.provider.Settings.Global.ADB_ENABLED, "0",
+ userId);
+ }
+ }
+ break;
+ case UserManager.ENSURE_VERIFY_APPS:
+ if (newValue) {
+ android.provider.Settings.Global.putStringForUser(
+ context.getContentResolver(),
+ android.provider.Settings.Global.PACKAGE_VERIFIER_ENABLE, "1",
+ userId);
+ android.provider.Settings.Global.putStringForUser(
+ context.getContentResolver(),
+ android.provider.Settings.Global.PACKAGE_VERIFIER_INCLUDE_ADB, "1",
+ userId);
+ }
+ break;
+ case UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES:
+ if (newValue) {
+ android.provider.Settings.Secure.putIntForUser(cr,
+ android.provider.Settings.Secure.INSTALL_NON_MARKET_APPS, 0,
+ userId);
+ }
+ break;
+ }
+ } catch (RemoteException re) {
+ Slog.e(TAG, "Failed to talk to AudioService.", re);
+ } finally {
+ Binder.restoreCallingIdentity(id);
+ }
+ }
+
public static void dumpRestrictions(PrintWriter pw, String prefix, Bundle restrictions) {
boolean noneSet = true;
if (restrictions != null) {
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index b38b9ceb0712..209751ee4bcd 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -16,8 +16,8 @@
package com.android.server.policy;
-import static android.app.ActivityManager.DOCKED_STACK_ID;
-import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
+import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
import static android.view.WindowManager.LayoutParams.*;
import static android.view.WindowManagerPolicy.WindowManagerFuncs.LID_ABSENT;
import static android.view.WindowManagerPolicy.WindowManagerFuncs.LID_OPEN;
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateService.java b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
index d4c5f8716c0e..ac79b36f7005 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateService.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
@@ -40,6 +40,8 @@ public class WebViewUpdateService extends SystemService {
private boolean mRelroReady32Bit = false;
private boolean mRelroReady64Bit = false;
+ private String oldWebViewPackageName = null;
+
private BroadcastReceiver mWebViewUpdatedReceiver;
public WebViewUpdateService(Context context) {
@@ -51,9 +53,22 @@ public class WebViewUpdateService extends SystemService {
mWebViewUpdatedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- String webviewPackage = "package:" + WebViewFactory.getWebViewPackageName();
- if (webviewPackage.equals(intent.getDataString())) {
- onWebViewUpdateInstalled();
+
+ for (String packageName : WebViewFactory.getWebViewPackageNames()) {
+ String webviewPackage = "package:" + packageName;
+
+ if (webviewPackage.equals(intent.getDataString())) {
+ String usedPackageName =
+ WebViewFactory.findPreferredWebViewPackage().packageName;
+ // Only trigger update actions if the updated package is the one that
+ // will be used, or the one that was in use before the update.
+ if (packageName.equals(usedPackageName) ||
+ packageName.equals(oldWebViewPackageName)) {
+ onWebViewUpdateInstalled();
+ oldWebViewPackageName = usedPackageName;
+ }
+ return;
+ }
}
}
};
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index fab8ee55b22c..d6f807eb0ab8 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -16,14 +16,15 @@
package com.android.server.wm;
-import static android.app.ActivityManager.DOCKED_STACK_ID;
-import static android.app.ActivityManager.HOME_STACK_ID;
-import static android.app.ActivityManager.PINNED_STACK_ID;
+import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
+import static android.app.ActivityManager.StackId.HOME_STACK_ID;
+import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
import static com.android.server.wm.WindowManagerService.DEBUG_VISIBILITY;
import static com.android.server.wm.WindowManagerService.TAG;
import static com.android.server.wm.WindowState.RESIZE_HANDLE_WIDTH_IN_DP;
import static com.android.server.wm.WindowState.BOUNDS_FOR_TOUCH;
+import android.app.ActivityManager.StackId;
import android.graphics.Rect;
import android.graphics.Region;
import android.util.DisplayMetrics;
@@ -311,7 +312,7 @@ class DisplayContent {
final int delta = mService.dipToPixel(RESIZE_HANDLE_WIDTH_IN_DP, mDisplayMetrics);
for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
TaskStack stack = mStacks.get(stackNdx);
- if (!stack.allowTaskResize()) {
+ if (!StackId.isTaskResizeAllowed(stack.mStackId)) {
break;
}
final ArrayList<Task> tasks = stack.getTasks();
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index 04cba81726ec..eafc3c6beccf 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -16,7 +16,7 @@
package com.android.server.wm;
-import static android.app.ActivityManager.DOCKED_STACK_ID;
+import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.view.PointerIcon.STYLE_HORIZONTAL_DOUBLE_ARROW;
import static android.view.PointerIcon.STYLE_VERTICAL_DOUBLE_ARROW;
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 1f62bc15024e..c47c377f4cb8 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -187,6 +187,12 @@ final class Session extends IWindowSession.Stub
mService.removeWindow(this, window);
}
+ @Override
+ public void repositionChild(IWindow window, int x, int y, long deferTransactionUntilFrame,
+ Rect outFrame) {
+ mService.repositionChild(this, window, x, y, deferTransactionUntilFrame, outFrame);
+ }
+
public int relayout(IWindow window, int seq, WindowManager.LayoutParams attrs,
int requestedWidth, int requestedHeight, int viewFlags,
int flags, Rect outFrame, Rect outOverscanInsets, Rect outContentInsets,
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index d4d99af9675d..dbf13fefef9b 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -16,22 +16,21 @@
package com.android.server.wm;
-import static android.app.ActivityManager.DOCKED_STACK_ID;
-import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.HOME_STACK_ID;
-import static android.app.ActivityManager.PINNED_STACK_ID;
+import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
+import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.HOME_STACK_ID;
+import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
import static android.app.ActivityManager.RESIZE_MODE_SYSTEM_SCREEN_ROTATION;
import static com.android.server.wm.WindowManagerService.TAG;
import static com.android.server.wm.WindowManagerService.DEBUG_RESIZE;
import static com.android.server.wm.WindowManagerService.DEBUG_STACK;
import static com.android.server.wm.WindowManagerService.H.RESIZE_TASK;
-
+import android.app.ActivityManager.StackId;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.util.EventLog;
import android.util.Slog;
-import android.util.SparseArray;
import android.view.DisplayInfo;
import android.view.Surface;
@@ -245,7 +244,7 @@ class Task implements DimLayer.DimLayerUser {
private boolean useCurrentBounds() {
final DisplayContent displayContent = mStack.getDisplayContent();
if (mFullscreen
- || mStack.allowTaskResize()
+ || !StackId.isTaskResizeableByDockedStack(mStack.mStackId)
|| displayContent == null
|| displayContent.getDockedStackLocked() != null) {
return true;
diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java
index 227b3f01fac2..aae3bd24439f 100644
--- a/services/core/java/com/android/server/wm/TaskPositioner.java
+++ b/services/core/java/com/android/server/wm/TaskPositioner.java
@@ -18,7 +18,7 @@ package com.android.server.wm;
import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
-import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
import static android.app.ActivityManager.RESIZE_MODE_USER;
import static android.app.ActivityManager.RESIZE_MODE_USER_FORCED;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index df664bd271bd..95e23917b7e3 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -17,17 +17,16 @@
package com.android.server.wm;
import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
-import static android.app.ActivityManager.DOCKED_STACK_ID;
-import static android.app.ActivityManager.FIRST_STATIC_STACK_ID;
-import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.LAST_STATIC_STACK_ID;
-import static android.app.ActivityManager.PINNED_STACK_ID;
+import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
+import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
import static com.android.server.wm.WindowManagerService.DEBUG_TASK_MOVEMENT;
import static com.android.server.wm.WindowManagerService.H.RESIZE_STACK;
import static com.android.server.wm.WindowManagerService.TAG;
import android.annotation.IntDef;
+import android.app.ActivityManager.StackId;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Debug;
@@ -122,12 +121,6 @@ public class TaskStack implements DimLayer.DimLayerUser {
}
}
- boolean allowTaskResize() {
- return mStackId == FREEFORM_WORKSPACE_STACK_ID
- || mStackId == DOCKED_STACK_ID
- || mStackId == PINNED_STACK_ID;
- }
-
/**
* Set the bounds of the stack and its containing tasks.
* @param stackBounds New stack bounds. Passing in null sets the bounds to fullscreen.
@@ -203,8 +196,7 @@ public class TaskStack implements DimLayer.DimLayerUser {
/** Return true if the current bound can get outputted to the rest of the system as-is. */
private boolean useCurrentBounds() {
if (mFullscreen
- || mStackId == DOCKED_STACK_ID
- || mStackId == PINNED_STACK_ID
+ || !StackId.isResizeableByDockedStack(mStackId)
|| mDisplayContent == null
|| mDisplayContent.getDockedStackLocked() != null) {
return true;
@@ -396,9 +388,9 @@ public class TaskStack implements DimLayer.DimLayerUser {
Rect bounds = null;
final TaskStack dockedStack = mService.mStackIdToStack.get(DOCKED_STACK_ID);
- if (mStackId == DOCKED_STACK_ID || (dockedStack != null && mStackId != PINNED_STACK_ID
- && mStackId >= FIRST_STATIC_STACK_ID && mStackId <= LAST_STATIC_STACK_ID)) {
- // The existence of a docked stack affects the size of any static stack created since
+ if (mStackId == DOCKED_STACK_ID
+ || (dockedStack != null && StackId.isResizeableByDockedStack(mStackId))) {
+ // The existence of a docked stack affects the size of other static stack created since
// the docked stack occupies a dedicated region on screen.
bounds = new Rect();
displayContent.getLogicalDisplayRect(mTmpRect);
@@ -424,10 +416,7 @@ public class TaskStack implements DimLayer.DimLayerUser {
}
void getStackDockedModeBoundsLocked(Rect outBounds) {
- if (mStackId == DOCKED_STACK_ID
- || mStackId == PINNED_STACK_ID
- || mStackId > LAST_STATIC_STACK_ID
- || mDisplayContent == null) {
+ if (!StackId.isResizeableByDockedStack(mStackId) || mDisplayContent == null) {
outBounds.set(mBounds);
return;
}
@@ -537,9 +526,7 @@ public class TaskStack implements DimLayer.DimLayerUser {
for (int i = 0; i < count; i++) {
final TaskStack otherStack = mService.mStackIdToStack.valueAt(i);
final int otherStackId = otherStack.mStackId;
- if (otherStackId != DOCKED_STACK_ID && mStackId != PINNED_STACK_ID
- && otherStackId >= FIRST_STATIC_STACK_ID
- && otherStackId <= LAST_STATIC_STACK_ID) {
+ if (StackId.isResizeableByDockedStack(otherStackId)) {
mService.mH.sendMessage(
mService.mH.obtainMessage(RESIZE_STACK, otherStackId,
1 /*allowResizeInDockedMode*/, bounds));
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 7f2f2cda8128..4a9d8cbdf006 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -16,7 +16,7 @@
package com.android.server.wm;
-import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index bcfc8f22bf15..230e81b52e78 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -17,8 +17,8 @@
package com.android.server.wm;
import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
-import static android.app.ActivityManager.DOCKED_STACK_ID;
-import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
+import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
@@ -2468,6 +2468,54 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
+ void repositionChild(Session session, IWindow client,
+ int x, int y, long deferTransactionUntilFrame, Rect outFrame) {
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "repositionChild");
+ long origId = Binder.clearCallingIdentity();
+
+ try {
+ synchronized(mWindowMap) {
+ WindowState win = windowForClientLocked(session, client, false);
+ if (win == null) {
+ return;
+ }
+ if (win.mAttachedWindow == null) {
+ throw new IllegalArgumentException(
+ "repositionChild called but window is not"
+ + "attached to a parent win=" + win);
+ }
+
+ win.mFrame.left = x;
+ win.mFrame.top = y;
+
+ win.mWinAnimator.computeShownFrameLocked();
+
+ if (SHOW_TRANSACTIONS) {
+ Slog.i(TAG, ">>> OPEN TRANSACTION repositionChild");
+ }
+
+ SurfaceControl.openTransaction();
+
+ if (deferTransactionUntilFrame > 0) {
+ win.mWinAnimator.mSurfaceControl.deferTransactionUntil(
+ win.mAttachedWindow.mWinAnimator.mSurfaceControl.getHandle(),
+ deferTransactionUntilFrame);
+ }
+ win.mWinAnimator.setSurfaceBoundariesLocked(false);
+
+ SurfaceControl.closeTransaction();
+ if (SHOW_TRANSACTIONS) {
+ Slog.i(TAG, "<<< CLOSE TRANSACTION repositionChild");
+ }
+
+ outFrame = win.mCompatFrame;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+ }
+
public int relayoutWindow(Session session, IWindow client, int seq,
WindowManager.LayoutParams attrs, int requestedWidth,
int requestedHeight, int viewVisibility, int flags,
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 5bc329e16e65..c1fa78ac8ce0 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1999,4 +1999,13 @@ final class WindowState implements WindowManagerPolicy.WindowState {
}
return mStringNameCache;
}
+
+ void transformFromScreenToSurfaceSpace(Rect rect) {
+ if (mHScale >= 0) {
+ rect.right = rect.left + (int)((rect.right - rect.left) / mHScale);
+ }
+ if (mVScale >= 0) {
+ rect.bottom = rect.top + (int)((rect.bottom - rect.top) / mVScale);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index aa242f1131d3..8dddbd1c93c3 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -1223,14 +1223,6 @@ class WindowStateAnimator {
mShownAlpha *= appTransformation.getAlpha();
if (appTransformation.hasClipRect()) {
mClipRect.set(appTransformation.getClipRect());
- if (mWin.mHScale > 0) {
- mClipRect.left /= mWin.mHScale;
- mClipRect.right /= mWin.mHScale;
- }
- if (mWin.mVScale > 0) {
- mClipRect.top /= mWin.mVScale;
- mClipRect.bottom /= mWin.mVScale;
- }
mHasClipRect = true;
}
}
@@ -1350,11 +1342,7 @@ class WindowStateAnimator {
final DisplayInfo displayInfo = displayContent.getDisplayInfo();
// Need to recompute a new system decor rect each time.
- if ((w.mAttrs.flags & LayoutParams.FLAG_SCALED) != 0) {
- // Currently can't do this cropping for scaled windows. We'll
- // just keep the crop rect the same as the source surface.
- w.mSystemDecorRect.set(0, 0, w.mRequestedWidth, w.mRequestedHeight);
- } else if (!w.isDefaultDisplay()) {
+ if (!w.isDefaultDisplay()) {
// On a different display there is no system decor. Crop the window
// by the screen boundaries.
w.mSystemDecorRect.set(0, 0, w.mCompatFrame.width(), w.mCompatFrame.height());
@@ -1407,9 +1395,13 @@ class WindowStateAnimator {
clipRect.offset(attrs.surfaceInsets.left, attrs.surfaceInsets.top);
// We don't want to clip to stack bounds windows that are currently doing entrance
// animation for docked window, otherwise the animating window will be suddenly cut off.
+
if (!(mAnimator.mAnimating && w.inDockedWorkspace())) {
adjustCropToStackBounds(w, clipRect);
}
+
+ w.transformFromScreenToSurfaceSpace(clipRect);
+
if (!clipRect.equals(mLastClipRect)) {
mLastClipRect.set(clipRect);
try {
diff --git a/services/core/jni/com_android_server_UsbMidiDevice.cpp b/services/core/jni/com_android_server_UsbMidiDevice.cpp
index 06b9bc3c01ec..e12a01661a44 100644
--- a/services/core/jni/com_android_server_UsbMidiDevice.cpp
+++ b/services/core/jni/com_android_server_UsbMidiDevice.cpp
@@ -43,12 +43,26 @@ android_server_UsbMidiDevice_get_subdevice_count(JNIEnv *env, jobject /* thiz */
jint card, jint device)
{
char path[100];
+ int fd;
+ const int kMaxRetries = 10;
+ const int kSleepMicroseconds = 2000;
snprintf(path, sizeof(path), "/dev/snd/controlC%d", card);
- int fd = open(path, O_RDWR);
- if (fd < 0) {
- ALOGE("could not open %s", path);
- return 0;
+ // This control device may not have been created yet. So we should
+ // try to open it several times to prevent intermittent failure
+ // from a race condition.
+ int retryCounter = 0;
+ while ((fd = open(path, O_RDWR)) < 0) {
+ if (++retryCounter > kMaxRetries) {
+ ALOGE("timed out after %d tries, could not open %s", retryCounter, path);
+ return 0;
+ } else {
+ ALOGW("attempt #%d, could not open %s", retryCounter, path);
+ // Increase the sleep interval each time.
+ // 10 retries will total 2 * sum(1..10) = 110 milliseconds.
+ // Typically the device should be ready in 5-10 milliseconds.
+ usleep(kSleepMicroseconds * retryCounter);
+ }
}
struct snd_rawmidi_info info;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index b4c8f966ba71..aea2ecf68858 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -1016,6 +1016,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
+ // DO NOT call it while taking the "this" lock, which could cause a dead lock.
private void handlePackagesChanged(String packageName, int userHandle) {
boolean removed = false;
if (VERBOSE_LOG) Slog.d(LOG_TAG, "Handling package changes for user " + userHandle);
@@ -1042,7 +1043,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
if (removed) {
validatePasswordOwnerLocked(policy);
- syncDeviceCapabilitiesLocked(policy);
saveSettingsLocked(policy.mUserHandle);
}
@@ -1061,6 +1061,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
}
+ if (removed) {
+ synchronized (mUserManagerInternal.getUserRestrictionsLock()) {
+ synchronized (DevicePolicyManagerService.this) {
+ mUserManagerInternal.updateEffectiveUserRestrictionsLR(userHandle);
+ }
+ }
+ }
}
/**
@@ -1682,7 +1689,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
- void removeActiveAdminLocked(final ComponentName adminReceiver, int userHandle) {
+ void removeActiveAdminLocked(final ComponentName adminReceiver, final int userHandle) {
final ActiveAdmin admin = getActiveAdminUncheckedLocked(adminReceiver, userHandle);
if (admin != null) {
synchronized (this) {
@@ -1701,7 +1708,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
policy.mAdminList.remove(admin);
policy.mAdminMap.remove(adminReceiver);
validatePasswordOwnerLocked(policy);
- syncDeviceCapabilitiesLocked(policy);
if (doProxyCleanup) {
resetGlobalProxyLocked(getUserData(userHandle));
}
@@ -1709,6 +1715,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
updateMaximumTimeToLockLocked(policy);
policy.mRemovingAdmins.remove(adminReceiver);
}
+ synchronized (mUserManagerInternal.getUserRestrictionsLock()) {
+ synchronized (DevicePolicyManagerService.this) {
+ mUserManagerInternal.updateEffectiveUserRestrictionsLR(
+ userHandle);
+ }
+ }
}
});
}
@@ -2022,7 +2034,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
validatePasswordOwnerLocked(policy);
- syncDeviceCapabilitiesLocked(policy);
updateMaximumTimeToLockLocked(policy);
updateLockTaskPackagesLocked(policy.mLockTaskPackages, userHandle);
if (policy.mStatusBarDisabled) {
@@ -2089,31 +2100,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
- /**
- * Pushes down policy information to the system for any policies related to general device
- * capabilities that need to be enforced by lower level services (e.g. Camera services).
- */
- void syncDeviceCapabilitiesLocked(DevicePolicyData policy) {
- // Ensure the status of the camera is synced down to the system. Interested native services
- // should monitor this value and act accordingly.
- String cameraPropertyForUser = SYSTEM_PROP_DISABLE_CAMERA_PREFIX + policy.mUserHandle;
- boolean systemState = mInjector.systemPropertiesGetBoolean(cameraPropertyForUser, false);
- boolean cameraDisabled = getCameraDisabled(null, policy.mUserHandle);
- if (cameraDisabled != systemState) {
- long token = mInjector.binderClearCallingIdentity();
- try {
- String value = cameraDisabled ? "1" : "0";
- if (VERBOSE_LOG) {
- Slog.v(LOG_TAG, "Change in camera state ["
- + cameraPropertyForUser + "] = " + value);
- }
- mInjector.systemPropertiesSet(cameraPropertyForUser, value);
- } finally {
- mInjector.binderRestoreCallingIdentity(token);
- }
- }
- }
-
@VisibleForTesting
void systemReady(int phase) {
if (!mHasFeature) {
@@ -4329,13 +4315,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
/**
- * The system property used to share the state of the camera. The native camera service
- * is expected to read this property and act accordingly. The userId should be appended
- * to this key.
- */
- public static final String SYSTEM_PROP_DISABLE_CAMERA_PREFIX = "sys.secpolicy.camera.off_";
-
- /**
* Disables all device cameras according to the specified admin.
*/
@Override
@@ -4352,7 +4331,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
ap.disableCamera = disabled;
saveSettingsLocked(userHandle);
}
- syncDeviceCapabilitiesLocked(getUserData(userHandle));
+ }
+ // Tell the user manager that the restrictions have changed.
+ synchronized (mUserManagerInternal.getUserRestrictionsLock()) {
+ synchronized (this) {
+ if (isDeviceOwner(who)) {
+ mUserManagerInternal.updateEffectiveUserRestrictionsForAllUsersLR();
+ } else {
+ mUserManagerInternal.updateEffectiveUserRestrictionsLR(userHandle);
+ }
+ }
}
}
@@ -4370,7 +4358,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
ActiveAdmin admin = getActiveAdminUncheckedLocked(who, userHandle);
return (admin != null) ? admin.disableCamera : false;
}
+ // First, see if DO has set it. If so, it's device-wide.
+ final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
+ if (deviceOwner != null && deviceOwner.disableCamera) {
+ return true;
+ }
+ // Then check each device admin on the user.
DevicePolicyData policy = getUserData(userHandle);
// Determine whether or not the device camera is disabled for any active admins.
final int N = policy.mAdminList.size();
@@ -4404,7 +4398,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
ap.disabledKeyguardFeatures = which;
saveSettingsLocked(userHandle);
}
- syncDeviceCapabilitiesLocked(getUserData(userHandle));
}
}
@@ -4657,7 +4650,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
0 /* flagValues */, userHandle.getIdentifier());
// TODO This will not revert audio mute restrictions if they were set. b/24981972
synchronized (mUserManagerInternal.getUserRestrictionsLock()) {
- mUserManagerInternal.updateEffectiveUserRestrictionsRL(userHandle.getIdentifier());
+ mUserManagerInternal.updateEffectiveUserRestrictionsLR(userHandle.getIdentifier());
}
} catch (RemoteException re) {
} finally {
@@ -5036,7 +5029,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
DeviceAdminInfo.USES_POLICY_DISABLE_KEYGUARD_FEATURES);
ap.trustAgentInfos.put(agent.flattenToString(), new TrustAgentInfo(args));
saveSettingsLocked(userHandle);
- syncDeviceCapabilitiesLocked(getUserData(userHandle));
}
}
@@ -5602,6 +5594,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
+ // DO NOT call it while taking the "this" lock, which could cause a dead lock.
@Override
public void setUserRestriction(ComponentName who, String key, boolean enabledFromThisOwner) {
Preconditions.checkNotNull(who, "ComponentName is null");
@@ -5612,7 +5605,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
ActiveAdmin activeAdmin =
getActiveAdminForCallerLocked(who,
DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
- boolean isDeviceOwner = isDeviceOwner(who);
+ final boolean isDeviceOwner = isDeviceOwner(who);
if (!isDeviceOwner && userHandle != UserHandle.USER_SYSTEM
&& DEVICE_OWNER_USER_RESTRICTIONS.contains(key)) {
throw new SecurityException(
@@ -5624,9 +5617,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
final long id = mInjector.binderClearCallingIdentity();
try {
- // Original value.
- final boolean alreadyRestricted = mUserManager.hasUserRestriction(key, user);
-
// Save the restriction to ActiveAdmin.
// TODO When DO sets a restriction, it'll always be treated as device-wide.
// If there'll be a policy that can be set by both, we'll need scoping support,
@@ -5635,85 +5625,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
activeAdmin.ensureUserRestrictions().putBoolean(key, enabledFromThisOwner);
saveSettingsLocked(userHandle);
- // Tell UserManager the new value. Note this needs to be done before calling
- // into AudioService, because AS will check AppOps that'll be updated by UM.
+ // Tell UserManager the new value.
if (isDeviceOwner) {
- mUserManagerInternal.updateEffectiveUserRestrictionsForAllUsersRL();
+ mUserManagerInternal.updateEffectiveUserRestrictionsForAllUsersLR();
} else {
- mUserManagerInternal.updateEffectiveUserRestrictionsRL(userHandle);
+ mUserManagerInternal.updateEffectiveUserRestrictionsLR(userHandle);
}
-
- // New value.
- final boolean enabled = mUserManager.hasUserRestriction(key, user);
-
- // TODO The rest of the code should move to UserManagerService.
-
- if (enabled && !alreadyRestricted) {
- if (UserManager.DISALLOW_UNMUTE_MICROPHONE.equals(key)) {
- mInjector.getIAudioService()
- .setMicrophoneMute(true, mContext.getPackageName(), userHandle);
- } else if (UserManager.DISALLOW_ADJUST_VOLUME.equals(key)) {
- mInjector.getIAudioService()
- .setMasterMute(true, 0, mContext.getPackageName(), userHandle);
- } else if (UserManager.DISALLOW_CONFIG_WIFI.equals(key)) {
- mInjector.settingsSecurePutIntForUser(
- Settings.Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 0,
- userHandle);
- } else if (UserManager.DISALLOW_SHARE_LOCATION.equals(key)) {
- mInjector.settingsSecurePutIntForUser(
- Settings.Secure.LOCATION_MODE,
- Settings.Secure.LOCATION_MODE_OFF,
- userHandle);
- mInjector.settingsSecurePutStringForUser(
- Settings.Secure.LOCATION_PROVIDERS_ALLOWED, "",
- userHandle);
- } else if (UserManager.DISALLOW_DEBUGGING_FEATURES.equals(key)) {
- // Only disable adb if changing for system user, since it is global
- // TODO: should this be admin user?
- if (userHandle == UserHandle.USER_SYSTEM) {
- mInjector.settingsGlobalPutStringForUser(
- Settings.Global.ADB_ENABLED, "0", userHandle);
- }
- } else if (UserManager.ENSURE_VERIFY_APPS.equals(key)) {
- mInjector.settingsGlobalPutStringForUser(
- Settings.Global.PACKAGE_VERIFIER_ENABLE, "1",
- userHandle);
- mInjector.settingsGlobalPutStringForUser(
- Settings.Global.PACKAGE_VERIFIER_INCLUDE_ADB, "1",
- userHandle);
- } else if (UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES.equals(key)) {
- mInjector.settingsSecurePutIntForUser(
- Settings.Secure.INSTALL_NON_MARKET_APPS, 0,
- userHandle);
- }
- }
-
- if (enabled != alreadyRestricted) {
- if (UserManager.DISALLOW_SHARE_LOCATION.equals(key)) {
- // Send out notifications however as some clients may want to reread the
- // value which actually changed due to a restriction having been
- // applied.
- final String property = Settings.Secure.SYS_PROP_SETTING_VERSION;
- long version = mInjector.systemPropertiesGetLong(property, 0) + 1;
- mInjector.systemPropertiesSet(property, Long.toString(version));
-
- final String name = Settings.Secure.LOCATION_PROVIDERS_ALLOWED;
- Uri url = Uri.withAppendedPath(Settings.Secure.CONTENT_URI, name);
- mContext.getContentResolver().notifyChange(url, null, true, userHandle);
- }
- }
- if (!enabled && alreadyRestricted) {
- if (UserManager.DISALLOW_UNMUTE_MICROPHONE.equals(key)) {
- mInjector.getIAudioService()
- .setMicrophoneMute(false, mContext.getPackageName(),
- userHandle);
- } else if (UserManager.DISALLOW_ADJUST_VOLUME.equals(key)) {
- mInjector.getIAudioService()
- .setMasterMute(false, 0, mContext.getPackageName(), userHandle);
- }
- }
- } catch (RemoteException re) {
- Slog.e(LOG_TAG, "Failed to talk to AudioService.", re);
} finally {
mInjector.binderRestoreCallingIdentity(id);
}
@@ -6463,8 +6380,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
deviceOwner == null ? null : deviceOwner.userRestrictions;
final Bundle profileOwnerRestrictions =
profileOwner == null ? null : profileOwner.userRestrictions;
+ final boolean cameraDisabled = getCameraDisabled(null, userId);
- if (deviceOwnerRestrictions == null && profileOwnerRestrictions == null) {
+ if (deviceOwnerRestrictions == null && profileOwnerRestrictions == null
+ && !cameraDisabled) {
// No restrictions to merge.
return inBundle;
}
@@ -6473,6 +6392,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
UserRestrictionsUtils.merge(composed, deviceOwnerRestrictions);
UserRestrictionsUtils.merge(composed, profileOwnerRestrictions);
+ // Also merge in the camera restriction.
+ if (cameraDisabled) {
+ composed.putBoolean(UserManager.DISALLOW_CAMERA, true);
+ }
+
return composed;
}
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
index eb7eb15a491a..9e701385d59d 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -46,11 +46,11 @@ public class UserManagerServiceTest extends AndroidTestCase {
public void testWriteReadApplicationRestrictions() throws IOException {
AtomicFile atomicFile = new AtomicFile(restrictionsFile);
Bundle bundle = createBundle();
- UserManagerService.writeApplicationRestrictionsLocked(bundle, atomicFile);
+ UserManagerService.writeApplicationRestrictionsLP(bundle, atomicFile);
assertTrue(atomicFile.getBaseFile().exists());
String s = FileUtils.readTextFile(restrictionsFile, 10000, "");
System.out.println("restrictionsFile: " + s);
- bundle = UserManagerService.readApplicationRestrictionsLocked(atomicFile);
+ bundle = UserManagerService.readApplicationRestrictionsLP(atomicFile);
System.out.println("readApplicationRestrictionsLocked bundle: " + bundle);
assertBundle(bundle);
}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 13e600ef30e7..013154b39242 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -385,9 +385,11 @@ public class UsageStatsService extends SystemService implements
timeNow);
final int packageCount = packages.size();
for (int p = 0; p < packageCount; p++) {
- final String packageName = packages.get(p).packageName;
- final boolean isIdle = isAppIdleFiltered(packageName, userId, service, timeNow,
- screenOnTime);
+ final PackageInfo pi = packages.get(p);
+ final String packageName = pi.packageName;
+ final boolean isIdle = isAppIdleFiltered(packageName,
+ UserHandle.getAppId(pi.applicationInfo.uid),
+ userId, service, timeNow, screenOnTime);
mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS,
userId, isIdle ? 1 : 0, packageName));
mAppIdleHistory.addEntry(packageName, userId, isIdle, timeNow);
@@ -769,10 +771,17 @@ public class UsageStatsService extends SystemService implements
if (mAppIdleParoled) {
return false;
}
- return isAppIdleFiltered(packageName, userId, timeNow);
+ try {
+ ApplicationInfo ai = getContext().getPackageManager().getApplicationInfo(packageName,
+ PackageManager.GET_UNINSTALLED_PACKAGES
+ | PackageManager.GET_DISABLED_COMPONENTS);
+ return isAppIdleFiltered(packageName, ai.uid, userId, timeNow);
+ } catch (PackageManager.NameNotFoundException e) {
+ }
+ return false;
}
- boolean isAppIdleFiltered(String packageName, int userId, long timeNow) {
+ boolean isAppIdleFiltered(String packageName, int uidForAppId, int userId, long timeNow) {
final UserUsageStatsService userService;
final long screenOnTime;
synchronized (mLock) {
@@ -782,7 +791,8 @@ public class UsageStatsService extends SystemService implements
userService = getUserDataAndInitializeIfNeededLocked(userId, timeNow);
screenOnTime = getScreenOnTimeLocked(timeNow);
}
- return isAppIdleFiltered(packageName, userId, userService, timeNow, screenOnTime);
+ return isAppIdleFiltered(packageName, UserHandle.getAppId(uidForAppId), userId,
+ userService, timeNow, screenOnTime);
}
/**
@@ -791,14 +801,22 @@ public class UsageStatsService extends SystemService implements
* This happens if the device is plugged in or temporarily allowed to make exceptions.
* Called by interface impls.
*/
- private boolean isAppIdleFiltered(String packageName, int userId,
+ private boolean isAppIdleFiltered(String packageName, int appId, int userId,
UserUsageStatsService userService, long timeNow, long screenOnTime) {
if (packageName == null) return false;
// If not enabled at all, of course nobody is ever idle.
if (!mAppIdleEnabled) {
return false;
}
- if (packageName.equals("android")) return false;
+ if (appId < Process.FIRST_APPLICATION_UID) {
+ // System uids never go idle.
+ return false;
+ }
+ if (packageName.equals("android")) {
+ // Nor does the framework (which should be redundant with the above, but for MR1 we will
+ // retain this for safety).
+ return false;
+ }
try {
// We allow all whitelisted apps, including those that don't want to be whitelisted
// for idle mode, because app idle (aka app standby) is really not as big an issue
@@ -865,8 +883,8 @@ public class UsageStatsService extends SystemService implements
ApplicationInfo ai = apps.get(i);
// Check whether this app is idle.
- boolean idle = isAppIdleFiltered(ai.packageName, userId, userService, timeNow,
- screenOnTime);
+ boolean idle = isAppIdleFiltered(ai.packageName, UserHandle.getAppId(ai.uid),
+ userId, userService, timeNow, screenOnTime);
int index = uidStates.indexOfKey(ai.uid);
if (index < 0) {
@@ -1352,8 +1370,8 @@ public class UsageStatsService extends SystemService implements
}
@Override
- public boolean isAppIdle(String packageName, int userId) {
- return UsageStatsService.this.isAppIdleFiltered(packageName, userId, -1);
+ public boolean isAppIdle(String packageName, int uidForAppId, int userId) {
+ return UsageStatsService.this.isAppIdleFiltered(packageName, uidForAppId, userId, -1);
}
@Override
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index e11c8d3acc43..d1d6e0dcdece 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -332,9 +332,24 @@ public class SubscriptionInfo implements Parcelable {
return 0;
}
+ /**
+ * @hide
+ */
+ public static String givePrintableIccid(String iccId) {
+ String iccIdToPrint = null;
+ if (iccId != null) {
+ if (iccId.length() > 9) {
+ iccIdToPrint = iccId.substring(0, 9) + "XXXXXXXXXXX";
+ } else {
+ iccIdToPrint = iccId;
+ }
+ }
+ return iccIdToPrint;
+ }
+
@Override
public String toString() {
- String iccIdToPrint = mIccId != null ? mIccId.substring(0, 9) + "XXXXXXXXXXX" : null;
+ String iccIdToPrint = givePrintableIccid(mIccId);
return "{id=" + mId + ", iccId=" + iccIdToPrint + " simSlotIndex=" + mSimSlotIndex
+ " displayName=" + mDisplayName + " carrierName=" + mCarrierName
+ " nameSource=" + mNameSource + " iconTint=" + mIconTint
diff --git a/telephony/java/com/android/ims/ImsCallProfile.java b/telephony/java/com/android/ims/ImsCallProfile.java
index 861a37990629..5f84e0ce353a 100644
--- a/telephony/java/com/android/ims/ImsCallProfile.java
+++ b/telephony/java/com/android/ims/ImsCallProfile.java
@@ -188,6 +188,20 @@ public class ImsCallProfile implements Parcelable {
public static final String EXTRA_CODEC = "Codec";
public static final String EXTRA_DISPLAY_TEXT = "DisplayText";
public static final String EXTRA_ADDITIONAL_CALL_INFO = "AdditionalCallInfo";
+
+ /**
+ * Extra key which the RIL can use to indicate the radio technology used for a call.
+ * Valid values are:
+ * {@link android.telephony.ServiceState#RIL_RADIO_TECHNOLOGY_LTE},
+ * {@link android.telephony.ServiceState#RIL_RADIO_TECHNOLOGY_IWLAN}, and the other defined
+ * {@code RIL_RADIO_TECHNOLOGY_*} constants.
+ * Note: Despite the fact the {@link android.telephony.ServiceState} values are integer
+ * constants, the values passed for the {@link #EXTRA_CALL_RAT_TYPE} should be strings (e.g.
+ * "14" vs (int) 14).
+ * Note: This is used by {@link com.android.internal.telephony.imsphone.ImsPhoneConnection#
+ * updateWifiStateFromExtras(Bundle)} to determine whether to set the
+ * {@link android.telecom.Connection#CAPABILITY_WIFI} capability on a connection.
+ */
public static final String EXTRA_CALL_RAT_TYPE = "CallRadioTech";
public int mServiceType;
diff --git a/tests/SurfaceComposition/src/android/surfacecomposition/SurfaceCompositionMeasuringActivity.java b/tests/SurfaceComposition/src/android/surfacecomposition/SurfaceCompositionMeasuringActivity.java
index b4e0c707d3f3..4771b6cfc750 100644
--- a/tests/SurfaceComposition/src/android/surfacecomposition/SurfaceCompositionMeasuringActivity.java
+++ b/tests/SurfaceComposition/src/android/surfacecomposition/SurfaceCompositionMeasuringActivity.java
@@ -24,6 +24,7 @@ import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityManager.MemoryInfo;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.graphics.Color;
import android.graphics.PixelFormat;
import android.graphics.Rect;
@@ -95,11 +96,9 @@ public class SurfaceCompositionMeasuringActivity extends Activity implements OnC
private boolean mResumed;
// Drop one frame per half second.
- // TODO(khmel)
- // Add a feature flag and set the target FPS dependent on the target system as e.g.:
- // 59FPS for MULTI_WINDOW and 54 otherwise (to satisfy the default lax Android requirements).
private double mRefreshRate;
private double mTargetFPS;
+ private boolean mAndromeda;
private int mWidth;
private int mHeight;
@@ -182,6 +181,10 @@ public class SurfaceCompositionMeasuringActivity extends Activity implements OnC
return score;
}
+ public boolean isAndromeda() {
+ return mAndromeda;
+ }
+
@Override
public void onClick(View view) {
if (view == mMeasureCompositionButton) {
@@ -247,6 +250,9 @@ public class SurfaceCompositionMeasuringActivity extends Activity implements OnC
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ // Detect Andromeda devices by having free-form window management feature.
+ mAndromeda = getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT);
detectRefreshRate();
// To layouts in parent. First contains list of Surfaces and second
@@ -513,7 +519,8 @@ public class SurfaceCompositionMeasuringActivity extends Activity implements OnC
}
MemoryInfo memInfo = getMemoryInfo();
- String info = "Available " +
+ String platformName = mAndromeda ? "Andromeda" : "Android";
+ String info = platformName + ": available " +
getReadableMemory(memInfo.availMem) + " from " +
getReadableMemory(memInfo.totalMem) + ".\nVisible " +
visibleCnt + " from " + mViews.size() + " " +
diff --git a/tests/SurfaceComposition/src/android/surfacecomposition/SurfaceCompositionTest.java b/tests/SurfaceComposition/src/android/surfacecomposition/SurfaceCompositionTest.java
index 3f04888da637..388f91a2c3fb 100644
--- a/tests/SurfaceComposition/src/android/surfacecomposition/SurfaceCompositionTest.java
+++ b/tests/SurfaceComposition/src/android/surfacecomposition/SurfaceCompositionTest.java
@@ -17,6 +17,7 @@ package android.surfacecomposition;
import android.app.Activity;
import android.graphics.PixelFormat;
+import android.os.Build;
import android.os.Bundle;
import android.surfacecomposition.SurfaceCompositionMeasuringActivity.AllocationScore;
import android.surfacecomposition.SurfaceCompositionMeasuringActivity.CompositorScore;
@@ -44,11 +45,16 @@ public class SurfaceCompositionTest extends
PixelFormat.OPAQUE,
};
- // Based on Nexus 9 performance which is usually < 9.0.
- private final static double[] MIN_ACCEPTED_COMPOSITION_SCORE = new double[] {
+ // Nexus 9 performance is around 8.8. We distinguish results for Andromeda and
+ // Android devices. Andromeda devices require higher performance score.
+ private final static double[] MIN_ACCEPTED_COMPOSITION_SCORE_ANDROMDEDA = new double[] {
8.0,
8.0,
};
+ private final static double[] MIN_ACCEPTED_COMPOSITION_SCORE_ANDROID = new double[] {
+ 4.0,
+ 4.0,
+ };
// Based on Nexus 6 performance which is usually < 28.0.
private final static double[] MIN_ACCEPTED_ALLOCATION_SCORE = new double[] {
@@ -66,6 +72,8 @@ public class SurfaceCompositionTest extends
@SmallTest
public void testSurfaceCompositionPerformance() {
Bundle status = new Bundle();
+ double[] minScores = getActivity().isAndromeda() ?
+ MIN_ACCEPTED_COMPOSITION_SCORE_ANDROMDEDA : MIN_ACCEPTED_COMPOSITION_SCORE_ANDROID;
for (int i = 0; i < TEST_PIXEL_FORMATS.length; ++i) {
int pixelFormat = TEST_PIXEL_FORMATS[i];
String formatName = SurfaceCompositionMeasuringActivity.getPixelFormatInfo(pixelFormat);
@@ -73,8 +81,8 @@ public class SurfaceCompositionTest extends
Log.i(TAG, "testSurfaceCompositionPerformance(" + formatName + ") = " + score);
assertTrue("Device does not support surface(" + formatName + ") composition " +
"performance score. " + score.mSurfaces + " < " +
- MIN_ACCEPTED_COMPOSITION_SCORE[i] + ".",
- score.mSurfaces >= MIN_ACCEPTED_COMPOSITION_SCORE[i]);
+ minScores[i] + ". Build: " + Build.FINGERPRINT + ".",
+ score.mSurfaces >= minScores[i]);
// Send status only for TRANSLUCENT format.
if (pixelFormat == PixelFormat.TRANSLUCENT) {
status.putDouble(KEY_SURFACE_COMPOSITION_PERFORMANCE, score.mSurfaces);
@@ -96,7 +104,8 @@ public class SurfaceCompositionTest extends
Log.i(TAG, "testSurfaceAllocationPerformance(" + formatName + ") = " + score);
assertTrue("Device does not support surface(" + formatName + ") allocation " +
"performance score. " + score.mMedian + " < " +
- MIN_ACCEPTED_ALLOCATION_SCORE[i] + ".",
+ MIN_ACCEPTED_ALLOCATION_SCORE[i] + ". Build: " +
+ Build.FINGERPRINT + ".",
score.mMedian >= MIN_ACCEPTED_ALLOCATION_SCORE[i]);
// Send status only for TRANSLUCENT format.
if (pixelFormat == PixelFormat.TRANSLUCENT) {
diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
index 007d07553e80..498be5afe852 100644
--- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
+++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
@@ -513,4 +513,8 @@ public class IWindowManagerImpl implements IWindowManager {
// TODO Auto-generated method stub
return null;
}
+
+ @Override
+ public void cancelTaskWindowTransition(int taskId) {
+ }
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
index 299790765e39..11bd15d780fc 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
@@ -95,6 +95,13 @@ public final class BridgeWindowSession implements IWindowSession {
}
@Override
+ public void repositionChild(IWindow childWindow, int x, int y, long deferTransactionUntilFrame,
+ Rect outFrame) {
+ // pass for now.
+ return;
+ }
+
+ @Override
public void performDeferredDestroy(IWindow window) {
// pass for now.
}