diff options
278 files changed, 13473 insertions, 6470 deletions
diff --git a/Android.bp b/Android.bp index 2dc1cc3c19fe..c89cc400171d 100644 --- a/Android.bp +++ b/Android.bp @@ -72,9 +72,10 @@ cc_library { subdirs = [ "cmds/*", - "core/jni", + "core/*", "libs/*", "media/*", + "proto", "tools/*", "native/android", "native/graphics/jni", diff --git a/Android.mk b/Android.mk index c87aec40a0ca..9890bb401404 100644 --- a/Android.mk +++ b/Android.mk @@ -128,7 +128,7 @@ LOCAL_SRC_FILES += \ ../../system/bt/binder/android/bluetooth/IBluetoothHeadsetPhone.aidl \ ../../system/bt/binder/android/bluetooth/IBluetoothHealth.aidl \ ../../system/bt/binder/android/bluetooth/IBluetoothHealthCallback.aidl \ - ../../system/bt/binder/android/bluetooth/IBluetoothInputDevice.aidl \ + ../../system/bt/binder/android/bluetooth/IBluetoothHidHost.aidl \ ../../system/bt/binder/android/bluetooth/IBluetoothPan.aidl \ ../../system/bt/binder/android/bluetooth/IBluetoothManager.aidl \ ../../system/bt/binder/android/bluetooth/IBluetoothManagerCallback.aidl \ @@ -139,7 +139,7 @@ LOCAL_SRC_FILES += \ ../../system/bt/binder/android/bluetooth/IBluetoothSap.aidl \ ../../system/bt/binder/android/bluetooth/IBluetoothStateChangeCallback.aidl \ ../../system/bt/binder/android/bluetooth/IBluetoothHeadsetClient.aidl \ - ../../system/bt/binder/android/bluetooth/IBluetoothInputHost.aidl \ + ../../system/bt/binder/android/bluetooth/IBluetoothHidDevice.aidl \ ../../system/bt/binder/android/bluetooth/IBluetoothHidDeviceCallback.aidl \ ../../system/bt/binder/android/bluetooth/IBluetoothGatt.aidl \ ../../system/bt/binder/android/bluetooth/IBluetoothGattCallback.aidl \ @@ -270,6 +270,7 @@ LOCAL_SRC_FILES += \ core/java/android/os/IRecoverySystemProgressListener.aidl \ core/java/android/os/IRemoteCallback.aidl \ core/java/android/os/ISchedulingPolicyService.aidl \ + core/java/android/os/IStatsCallbacks.aidl \ core/java/android/os/IStatsCompanionService.aidl \ core/java/android/os/IStatsManager.aidl \ core/java/android/os/IThermalEventListener.aidl \ diff --git a/api/current.txt b/api/current.txt index df8b7ef63bae..96fbd31af65b 100644 --- a/api/current.txt +++ b/api/current.txt @@ -6451,6 +6451,8 @@ package android.app.admin { method public boolean setStatusBarDisabled(android.content.ComponentName, boolean); method public int setStorageEncryption(android.content.ComponentName, boolean); method public void setSystemUpdatePolicy(android.content.ComponentName, android.app.admin.SystemUpdatePolicy); + method public boolean setTime(android.content.ComponentName, long); + method public boolean setTimeZone(android.content.ComponentName, java.lang.String); method public void setTrustAgentConfiguration(android.content.ComponentName, android.content.ComponentName, android.os.PersistableBundle); method public void setUninstallBlocked(android.content.ComponentName, java.lang.String, boolean); method public void setUserIcon(android.content.ComponentName, android.graphics.Bitmap); @@ -6897,6 +6899,7 @@ package android.app.job { method public int getClipGrantFlags(); method public android.os.PersistableBundle getExtras(); method public int getJobId(); + method public android.net.Network getNetwork(); method public android.os.Bundle getTransientExtras(); method public java.lang.String[] getTriggeredContentAuthorities(); method public android.net.Uri[] getTriggeredContentUris(); @@ -40443,6 +40446,8 @@ package android.telephony { field public static final int ENCODING_7BIT = 1; // 0x1 field public static final int ENCODING_8BIT = 2; // 0x2 field public static final int ENCODING_UNKNOWN = 0; // 0x0 + field public static final java.lang.String FORMAT_3GPP = "3gpp"; + field public static final java.lang.String FORMAT_3GPP2 = "3gpp2"; field public static final int MAX_USER_DATA_BYTES = 140; // 0x8c field public static final int MAX_USER_DATA_BYTES_WITH_HEADER = 134; // 0x86 field public static final int MAX_USER_DATA_SEPTETS = 160; // 0xa0 @@ -52070,7 +52075,6 @@ package android.widget { method public android.graphics.Typeface getTypeface(); method public android.text.style.URLSpan[] getUrls(); method public boolean hasSelection(); - method public void invalidate(int, int, int, int); method public boolean isAllCaps(); method public boolean isCursorVisible(); method public boolean isElegantTextHeight(); diff --git a/api/system-current.txt b/api/system-current.txt index 007316fcf524..4f2c2ba7c2b6 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -6686,6 +6686,8 @@ package android.app.admin { method public boolean setStatusBarDisabled(android.content.ComponentName, boolean); method public int setStorageEncryption(android.content.ComponentName, boolean); method public void setSystemUpdatePolicy(android.content.ComponentName, android.app.admin.SystemUpdatePolicy); + method public boolean setTime(android.content.ComponentName, long); + method public boolean setTimeZone(android.content.ComponentName, java.lang.String); method public void setTrustAgentConfiguration(android.content.ComponentName, android.content.ComponentName, android.os.PersistableBundle); method public void setUninstallBlocked(android.content.ComponentName, java.lang.String, boolean); method public void setUserIcon(android.content.ComponentName, android.graphics.Bitmap); @@ -7339,6 +7341,7 @@ package android.app.job { method public int getClipGrantFlags(); method public android.os.PersistableBundle getExtras(); method public int getJobId(); + method public android.net.Network getNetwork(); method public android.os.Bundle getTransientExtras(); method public java.lang.String[] getTriggeredContentAuthorities(); method public android.net.Uri[] getTriggeredContentUris(); @@ -43963,6 +43966,8 @@ package android.telephony { field public static final int ENCODING_7BIT = 1; // 0x1 field public static final int ENCODING_8BIT = 2; // 0x2 field public static final int ENCODING_UNKNOWN = 0; // 0x0 + field public static final java.lang.String FORMAT_3GPP = "3gpp"; + field public static final java.lang.String FORMAT_3GPP2 = "3gpp2"; field public static final int MAX_USER_DATA_BYTES = 140; // 0x8c field public static final int MAX_USER_DATA_BYTES_WITH_HEADER = 134; // 0x86 field public static final int MAX_USER_DATA_SEPTETS = 160; // 0xa0 @@ -56174,7 +56179,6 @@ package android.widget { method public android.graphics.Typeface getTypeface(); method public android.text.style.URLSpan[] getUrls(); method public boolean hasSelection(); - method public void invalidate(int, int, int, int); method public boolean isAllCaps(); method public boolean isCursorVisible(); method public boolean isElegantTextHeight(); diff --git a/api/system-removed.txt b/api/system-removed.txt index 639877fae6e2..e9afa701c755 100644 --- a/api/system-removed.txt +++ b/api/system-removed.txt @@ -330,7 +330,6 @@ package android.net.wifi { method public deprecated java.util.List<android.net.wifi.BatchedScanResult> getBatchedScanResults(); method public android.net.wifi.WifiConnectionStatistics getConnectionStatistics(); method public deprecated boolean isBatchedScanSupported(); - method public deprecated boolean setWifiApEnabled(android.net.wifi.WifiConfiguration, boolean); method public deprecated boolean startLocationRestrictedScan(android.os.WorkSource); } diff --git a/api/test-current.txt b/api/test-current.txt index faccb6b58a3c..7930c47529b9 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -6519,6 +6519,8 @@ package android.app.admin { method public boolean setStatusBarDisabled(android.content.ComponentName, boolean); method public int setStorageEncryption(android.content.ComponentName, boolean); method public void setSystemUpdatePolicy(android.content.ComponentName, android.app.admin.SystemUpdatePolicy); + method public boolean setTime(android.content.ComponentName, long); + method public boolean setTimeZone(android.content.ComponentName, java.lang.String); method public void setTrustAgentConfiguration(android.content.ComponentName, android.content.ComponentName, android.os.PersistableBundle); method public void setUninstallBlocked(android.content.ComponentName, java.lang.String, boolean); method public void setUserIcon(android.content.ComponentName, android.graphics.Bitmap); @@ -6968,6 +6970,7 @@ package android.app.job { method public int getClipGrantFlags(); method public android.os.PersistableBundle getExtras(); method public int getJobId(); + method public android.net.Network getNetwork(); method public android.os.Bundle getTransientExtras(); method public java.lang.String[] getTriggeredContentAuthorities(); method public android.net.Uri[] getTriggeredContentUris(); @@ -32044,17 +32047,16 @@ package android.os { public static final class StrictMode.ViolationInfo implements android.os.Parcelable { ctor public StrictMode.ViolationInfo(); ctor public StrictMode.ViolationInfo(java.lang.Throwable, int); - ctor public StrictMode.ViolationInfo(java.lang.String, java.lang.Throwable, int); ctor public StrictMode.ViolationInfo(android.os.Parcel); ctor public StrictMode.ViolationInfo(android.os.Parcel, boolean); method public int describeContents(); method public void dump(android.util.Printer, java.lang.String); + method public java.lang.String getStackTrace(); + method public java.lang.String getViolationDetails(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.os.StrictMode.ViolationInfo> CREATOR; field public java.lang.String broadcastIntentAction; - field public final android.app.ApplicationErrorReport.CrashInfo crashInfo; field public int durationMillis; - field public final java.lang.String message; field public int numAnimationsRunning; field public long numInstances; field public final int policy; @@ -40838,6 +40840,8 @@ package android.telephony { field public static final int ENCODING_7BIT = 1; // 0x1 field public static final int ENCODING_8BIT = 2; // 0x2 field public static final int ENCODING_UNKNOWN = 0; // 0x0 + field public static final java.lang.String FORMAT_3GPP = "3gpp"; + field public static final java.lang.String FORMAT_3GPP2 = "3gpp2"; field public static final int MAX_USER_DATA_BYTES = 140; // 0x8c field public static final int MAX_USER_DATA_BYTES_WITH_HEADER = 134; // 0x86 field public static final int MAX_USER_DATA_SEPTETS = 160; // 0xa0 @@ -52678,7 +52682,6 @@ package android.widget { method public android.graphics.Typeface getTypeface(); method public android.text.style.URLSpan[] getUrls(); method public boolean hasSelection(); - method public void invalidate(int, int, int, int); method public boolean isAllCaps(); method public boolean isCursorVisible(); method public boolean isElegantTextHeight(); diff --git a/cmds/incidentd/tests/Reporter_test.cpp b/cmds/incidentd/tests/Reporter_test.cpp index 5d074bcb0e4c..8d99fc7dbbbc 100644 --- a/cmds/incidentd/tests/Reporter_test.cpp +++ b/cmds/incidentd/tests/Reporter_test.cpp @@ -32,8 +32,6 @@ using namespace android::binder; using namespace std; using ::testing::StrEq; using ::testing::Test; -using ::testing::internal::CaptureStdout; -using ::testing::internal::GetCapturedStdout; class TestListener : public IIncidentReportStatusListener { @@ -139,20 +137,24 @@ TEST_F(ReporterTest, RunReportEmpty) { } TEST_F(ReporterTest, RunReportWithHeaders) { + TemporaryFile tf; IncidentReportArgs args1, args2; args1.addSection(1); args2.addSection(2); std::vector<int8_t> header {'a', 'b', 'c', 'd', 'e'}; args2.addHeader(header); - sp<ReportRequest> r1 = new ReportRequest(args1, l, STDOUT_FILENO); - sp<ReportRequest> r2 = new ReportRequest(args2, l, STDOUT_FILENO); + sp<ReportRequest> r1 = new ReportRequest(args1, l, tf.fd); + sp<ReportRequest> r2 = new ReportRequest(args2, l, tf.fd); reporter->batch.add(r1); reporter->batch.add(r2); - CaptureStdout(); ASSERT_EQ(Reporter::REPORT_FINISHED, reporter->runReport()); - EXPECT_THAT(GetCapturedStdout(), StrEq("\n\x5" "abcde")); + + string result; + ReadFileToString(tf.path, &result); + EXPECT_THAT(result, StrEq("\n\x5" "abcde")); + EXPECT_EQ(l->startInvoked, 2); EXPECT_EQ(l->finishInvoked, 2); EXPECT_TRUE(l->startSections.empty()); diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java index 60ec8a96f325..29433f3fc14f 100644 --- a/cmds/pm/src/com/android/commands/pm/Pm.java +++ b/cmds/pm/src/com/android/commands/pm/Pm.java @@ -116,9 +116,8 @@ public final class Pm { } public int run(String[] args) throws RemoteException { - boolean validCommand = false; if (args.length < 1) { - return showUsage(); + return runShellCommand("package", mArgs); } mAm = IAccountManager.Stub.asInterface(ServiceManager.getService(Context.ACCOUNT_SERVICE)); mUm = IUserManager.Stub.asInterface(ServiceManager.getService(Context.USER_SERVICE)); @@ -134,18 +133,6 @@ public final class Pm { String op = args[0]; mNextArg = 1; - if ("list".equals(op)) { - return runList(); - } - - if ("path".equals(op)) { - return runPath(); - } - - if ("dump".equals(op)) { - return runDump(); - } - if ("install".equals(op)) { return runInstall(); } @@ -166,134 +153,7 @@ public final class Pm { return runInstallAbandon(); } - if ("set-installer".equals(op)) { - return runSetInstaller(); - } - - if ("uninstall".equals(op)) { - return runUninstall(); - } - - if ("clear".equals(op)) { - return runClear(); - } - - if ("enable".equals(op)) { - return runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_ENABLED); - } - - if ("disable".equals(op)) { - return runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_DISABLED); - } - - if ("disable-user".equals(op)) { - return runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER); - } - - if ("disable-until-used".equals(op)) { - return runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED); - } - - if ("default-state".equals(op)) { - return runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT); - } - - if ("hide".equals(op)) { - return runSetHiddenSetting(true); - } - - if ("unhide".equals(op)) { - return runSetHiddenSetting(false); - } - - if ("grant".equals(op)) { - return runGrantRevokePermission(true); - } - - if ("revoke".equals(op)) { - return runGrantRevokePermission(false); - } - - if ("reset-permissions".equals(op)) { - return runResetPermissions(); - } - - if ("set-permission-enforced".equals(op)) { - return runSetPermissionEnforced(); - } - - if ("set-app-link".equals(op)) { - return runSetAppLink(); - } - - if ("get-app-link".equals(op)) { - return runGetAppLink(); - } - - if ("set-install-location".equals(op)) { - return runSetInstallLocation(); - } - - if ("get-install-location".equals(op)) { - return runGetInstallLocation(); - } - - if ("trim-caches".equals(op)) { - return runTrimCaches(); - } - - if ("create-user".equals(op)) { - return runCreateUser(); - } - - if ("remove-user".equals(op)) { - return runRemoveUser(); - } - - if ("get-max-users".equals(op)) { - return runGetMaxUsers(); - } - - if ("force-dex-opt".equals(op)) { - return runForceDexOpt(); - } - - if ("move-package".equals(op)) { - return runMovePackage(); - } - - if ("move-primary-storage".equals(op)) { - return runMovePrimaryStorage(); - } - - if ("set-user-restriction".equals(op)) { - return runSetUserRestriction(); - } - - try { - if (args.length == 1) { - if (args[0].equalsIgnoreCase("-l")) { - validCommand = true; - return runShellCommand("package", new String[] { "list", "package" }); - } else if (args[0].equalsIgnoreCase("-lf")) { - validCommand = true; - return runShellCommand("package", new String[] { "list", "package", "-f" }); - } - } else if (args.length == 2) { - if (args[0].equalsIgnoreCase("-p")) { - validCommand = true; - return displayPackageFilePath(args[1], UserHandle.USER_SYSTEM); - } - } - return 1; - } finally { - if (validCommand == false) { - if (op != null) { - System.err.println("Error: unknown command '" + op + "'"); - } - showUsage(); - } - } + return runShellCommand("package", mArgs); } static final class MyShellCallback extends ShellCallback { @@ -704,59 +564,6 @@ 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() { - final String type = nextArg(); - if ("users".equals(type)) { - return runShellCommand("user", new String[] { "list" }); - } - return runShellCommand("package", mArgs); - } - - private int runUninstall() { - return runShellCommand("package", mArgs); - } - - private int runPath() { - int userId = UserHandle.USER_SYSTEM; - String option = nextOption(); - if (option != null && option.equals("--user")) { - String optionData = nextOptionData(); - if (optionData == null || !isNumber(optionData)) { - System.err.println("Error: no USER_ID specified"); - return showUsage(); - } else { - userId = Integer.parseInt(optionData); - } - } - - String pkg = nextArg(); - if (pkg == null) { - System.err.println("Error: no package specified"); - return 1; - } - return displayPackageFilePath(pkg, userId); - } - - private int runDump() { - String pkg = nextArg(); - if (pkg == null) { - System.err.println("Error: no package specified"); - return 1; - } - ActivityManager.dumpPackageStateStatic(FileDescriptor.out, pkg); - return 0; - } - class LocalPackageInstallObserver extends PackageInstallObserver { boolean finished; int result; @@ -779,403 +586,16 @@ public final class Pm { } } - // pm set-app-link [--user USER_ID] PACKAGE {always|ask|always-ask|never|undefined} - private int runSetAppLink() { - int userId = UserHandle.USER_SYSTEM; - - String opt; - while ((opt = nextOption()) != null) { - if (opt.equals("--user")) { - userId = Integer.parseInt(nextOptionData()); - if (userId < 0) { - System.err.println("Error: user must be >= 0"); - return 1; - } - } else { - System.err.println("Error: unknown option: " + opt); - return showUsage(); - } - } - - // Package name to act on; required - final String pkg = nextArg(); - if (pkg == null) { - System.err.println("Error: no package specified."); - return showUsage(); - } - - // State to apply; {always|ask|never|undefined}, required - final String modeString = nextArg(); - if (modeString == null) { - System.err.println("Error: no app link state specified."); - return showUsage(); - } - - final int newMode; - switch (modeString.toLowerCase()) { - case "undefined": - newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED; - break; - - case "always": - newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS; - break; - - case "ask": - newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK; - break; - - case "always-ask": - newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK; - break; - - case "never": - newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER; - break; - - default: - System.err.println("Error: unknown app link state '" + modeString + "'"); - return 1; - } - - try { - final PackageInfo info = mPm.getPackageInfo(pkg, 0, userId); - if (info == null) { - System.err.println("Error: package " + pkg + " not found."); - return 1; - } - - if ((info.applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS) == 0) { - System.err.println("Error: package " + pkg + " does not handle web links."); - return 1; - } - - if (!mPm.updateIntentVerificationStatus(pkg, newMode, userId)) { - System.err.println("Error: unable to update app link status for " + pkg); - return 1; - } - } catch (Exception e) { - System.err.println(e.toString()); - System.err.println(PM_NOT_RUNNING_ERR); - return 1; - } - - return 0; - } - - // pm get-app-link [--user USER_ID] PACKAGE - private int runGetAppLink() { - int userId = UserHandle.USER_SYSTEM; - - String opt; - while ((opt = nextOption()) != null) { - if (opt.equals("--user")) { - userId = Integer.parseInt(nextOptionData()); - if (userId < 0) { - System.err.println("Error: user must be >= 0"); - return 1; - } - } else { - System.err.println("Error: unknown option: " + opt); - return showUsage(); - } - } - - // Package name to act on; required - final String pkg = nextArg(); - if (pkg == null) { - System.err.println("Error: no package specified."); - return showUsage(); - } - - try { - final PackageInfo info = mPm.getPackageInfo(pkg, 0, userId); - if (info == null) { - System.err.println("Error: package " + pkg + " not found."); - return 1; - } - - if ((info.applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS) == 0) { - System.err.println("Error: package " + pkg + " does not handle web links."); - return 1; - } - - System.out.println(linkStateToString(mPm.getIntentVerificationStatus(pkg, userId))); - } catch (Exception e) { - System.err.println(e.toString()); - System.err.println(PM_NOT_RUNNING_ERR); - return 1; - } - - return 0; - } - - private String linkStateToString(int state) { - switch (state) { - case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED: return "undefined"; - case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK: return "ask"; - case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS: return "always"; - case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER: return "never"; - case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK : return "always ask"; - } - return "Unknown link state: " + state; - } - - private int runSetInstallLocation() { - int loc; - - String arg = nextArg(); - if (arg == null) { - System.err.println("Error: no install location specified."); - return 1; - } - try { - loc = Integer.parseInt(arg); - } catch (NumberFormatException e) { - System.err.println("Error: install location has to be a number."); - return 1; - } - try { - if (!mPm.setInstallLocation(loc)) { - System.err.println("Error: install location has to be a number."); - return 1; - } - return 0; - } catch (RemoteException e) { - System.err.println(e.toString()); - System.err.println(PM_NOT_RUNNING_ERR); - return 1; - } - } - - private int runGetInstallLocation() { - try { - int loc = mPm.getInstallLocation(); - String locStr = "invalid"; - if (loc == PackageHelper.APP_INSTALL_AUTO) { - locStr = "auto"; - } else if (loc == PackageHelper.APP_INSTALL_INTERNAL) { - locStr = "internal"; - } else if (loc == PackageHelper.APP_INSTALL_EXTERNAL) { - locStr = "external"; - } - System.out.println(loc + "[" + locStr + "]"); - return 0; - } catch (RemoteException e) { - System.err.println(e.toString()); - System.err.println(PM_NOT_RUNNING_ERR); - return 1; - } - } - - private int runSetInstaller() throws RemoteException { - final String targetPackage = nextArg(); - final String installerPackageName = nextArg(); - - if (targetPackage == null || installerPackageName == null) { - throw new IllegalArgumentException( - "must provide both target and installer package names"); - } - - mPm.setInstallerPackageName(targetPackage, installerPackageName); - System.out.println("Success"); - return 0; - } - - public int runCreateUser() { - String name; - int userId = -1; - int flags = 0; - String opt; - while ((opt = nextOption()) != null) { - if ("--profileOf".equals(opt)) { - String optionData = nextOptionData(); - if (optionData == null || !isNumber(optionData)) { - System.err.println("Error: no USER_ID specified"); - return showUsage(); - } else { - userId = Integer.parseInt(optionData); - } - } else if ("--managed".equals(opt)) { - flags |= UserInfo.FLAG_MANAGED_PROFILE; - } else if ("--restricted".equals(opt)) { - flags |= UserInfo.FLAG_RESTRICTED; - } else if ("--ephemeral".equals(opt)) { - flags |= UserInfo.FLAG_EPHEMERAL; - } else if ("--guest".equals(opt)) { - flags |= UserInfo.FLAG_GUEST; - } else if ("--demo".equals(opt)) { - flags |= UserInfo.FLAG_DEMO; - } else { - System.err.println("Error: unknown option " + opt); - return showUsage(); - } - } - String arg = nextArg(); - if (arg == null) { - System.err.println("Error: no user name specified."); - return 1; - } - name = arg; - try { - UserInfo info; - if ((flags & UserInfo.FLAG_RESTRICTED) != 0) { - // In non-split user mode, userId can only be SYSTEM - int parentUserId = userId >= 0 ? userId : UserHandle.USER_SYSTEM; - info = mUm.createRestrictedProfile(name, parentUserId); - mAm.addSharedAccountsFromParentUser(parentUserId, userId, - (Process.myUid() == Process.ROOT_UID) ? "root" : "com.android.shell"); - } else if (userId < 0) { - info = mUm.createUser(name, flags); - } else { - info = mUm.createProfileForUser(name, flags, userId, null); - } - - if (info != null) { - System.out.println("Success: created user id " + info.id); - return 0; - } else { - System.err.println("Error: couldn't create User."); - return 1; - } - } catch (RemoteException e) { - System.err.println(e.toString()); - System.err.println(PM_NOT_RUNNING_ERR); - return 1; - } - } - - public int runRemoveUser() { - int userId; - String arg = nextArg(); - if (arg == null) { - System.err.println("Error: no user id specified."); - return 1; - } - try { - userId = Integer.parseInt(arg); - } catch (NumberFormatException e) { - System.err.println("Error: user id '" + arg + "' is not a number."); - return 1; - } - try { - if (mUm.removeUser(userId)) { - System.out.println("Success: removed user"); - return 0; - } else { - System.err.println("Error: couldn't remove user id " + userId); - return 1; - } - } 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; - } - - public int runForceDexOpt() { - final String packageName = nextArg(); - try { - mPm.forceDexOpt(packageName); - return 0; - } catch (RemoteException e) { - throw e.rethrowAsRuntimeException(); - } - } - - public int runMovePackage() { - final String packageName = nextArg(); - String volumeUuid = nextArg(); - if ("internal".equals(volumeUuid)) { - volumeUuid = null; - } - - try { - final int moveId = mPm.movePackage(packageName, volumeUuid); - - int status = mPm.getMoveStatus(moveId); - while (!PackageManager.isMoveStatusFinished(status)) { - SystemClock.sleep(DateUtils.SECOND_IN_MILLIS); - status = mPm.getMoveStatus(moveId); - } - - if (status == PackageManager.MOVE_SUCCEEDED) { - System.out.println("Success"); - return 0; - } else { - System.err.println("Failure [" + status + "]"); - return 1; - } - } catch (RemoteException e) { - throw e.rethrowAsRuntimeException(); - } - } - - public int runMovePrimaryStorage() { - String volumeUuid = nextArg(); - if ("internal".equals(volumeUuid)) { - volumeUuid = null; - } - - try { - final int moveId = mPm.movePrimaryStorage(volumeUuid); - - int status = mPm.getMoveStatus(moveId); - while (!PackageManager.isMoveStatusFinished(status)) { - SystemClock.sleep(DateUtils.SECOND_IN_MILLIS); - status = mPm.getMoveStatus(moveId); - } - - if (status == PackageManager.MOVE_SUCCEEDED) { - System.out.println("Success"); - return 0; - } else { - System.err.println("Failure [" + status + "]"); - return 1; - } - } catch (RemoteException e) { - throw e.rethrowAsRuntimeException(); - } - } - - public int runSetUserRestriction() { - int userId = UserHandle.USER_SYSTEM; - String opt = nextOption(); - if (opt != null && "--user".equals(opt)) { - String arg = nextArg(); - if (arg == null || !isNumber(arg)) { - System.err.println("Error: valid userId not specified"); - return 1; - } - userId = Integer.parseInt(arg); - } - - String restriction = nextArg(); - String arg = nextArg(); - boolean value; - if ("1".equals(arg)) { - value = true; - } else if ("0".equals(arg)) { - value = false; - } else { - System.err.println("Error: valid value not specified"); - return 1; - } + private static boolean isNumber(String s) { try { - mUm.setUserRestriction(restriction, value, userId); - return 0; - } catch (RemoteException e) { - System.err.println(e.toString()); - return 1; + Integer.parseInt(s); + } catch (NumberFormatException nfe) { + return false; } + return true; } - static class ClearDataObserver extends IPackageDataObserver.Stub { + static class ClearCacheObserver extends IPackageDataObserver.Stub { boolean finished; boolean result; @@ -1187,242 +607,10 @@ public final class Pm { notifyAll(); } } - } - - private int runClear() { - int userId = UserHandle.USER_SYSTEM; - String option = nextOption(); - if (option != null && option.equals("--user")) { - String optionData = nextOptionData(); - if (optionData == null || !isNumber(optionData)) { - System.err.println("Error: no USER_ID specified"); - return showUsage(); - } else { - userId = Integer.parseInt(optionData); - } - } - - String pkg = nextArg(); - if (pkg == null) { - System.err.println("Error: no package specified"); - return showUsage(); - } - - ClearDataObserver obs = new ClearDataObserver(); - try { - ActivityManager.getService().clearApplicationUserData(pkg, obs, userId); - synchronized (obs) { - while (!obs.finished) { - try { - obs.wait(); - } catch (InterruptedException e) { - } - } - } - - if (obs.result) { - System.out.println("Success"); - return 0; - } else { - System.err.println("Failed"); - return 1; - } - } catch (RemoteException e) { - System.err.println(e.toString()); - System.err.println(PM_NOT_RUNNING_ERR); - return 1; - } - } - - private static String enabledSettingToString(int state) { - switch (state) { - case PackageManager.COMPONENT_ENABLED_STATE_DEFAULT: - return "default"; - case PackageManager.COMPONENT_ENABLED_STATE_ENABLED: - return "enabled"; - case PackageManager.COMPONENT_ENABLED_STATE_DISABLED: - return "disabled"; - case PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER: - return "disabled-user"; - case PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED: - return "disabled-until-used"; - } - return "unknown"; - } - - private static boolean isNumber(String s) { - try { - Integer.parseInt(s); - } catch (NumberFormatException nfe) { - return false; - } - return true; - } - - private int runSetEnabledSetting(int state) { - int userId = UserHandle.USER_SYSTEM; - String option = nextOption(); - if (option != null && option.equals("--user")) { - String optionData = nextOptionData(); - if (optionData == null || !isNumber(optionData)) { - System.err.println("Error: no USER_ID specified"); - return showUsage(); - } else { - userId = Integer.parseInt(optionData); - } - } - - String pkg = nextArg(); - if (pkg == null) { - System.err.println("Error: no package or component specified"); - return showUsage(); - } - ComponentName cn = ComponentName.unflattenFromString(pkg); - if (cn == null) { - try { - mPm.setApplicationEnabledSetting(pkg, state, 0, userId, - "shell:" + android.os.Process.myUid()); - System.out.println("Package " + pkg + " new state: " - + enabledSettingToString( - mPm.getApplicationEnabledSetting(pkg, userId))); - return 0; - } catch (RemoteException e) { - System.err.println(e.toString()); - System.err.println(PM_NOT_RUNNING_ERR); - return 1; - } - } else { - try { - mPm.setComponentEnabledSetting(cn, state, 0, userId); - System.out.println("Component " + cn.toShortString() + " new state: " - + enabledSettingToString( - mPm.getComponentEnabledSetting(cn, userId))); - return 0; - } catch (RemoteException e) { - System.err.println(e.toString()); - System.err.println(PM_NOT_RUNNING_ERR); - return 1; - } - } - } - - private int runSetHiddenSetting(boolean state) { - int userId = UserHandle.USER_SYSTEM; - String option = nextOption(); - if (option != null && option.equals("--user")) { - String optionData = nextOptionData(); - if (optionData == null || !isNumber(optionData)) { - System.err.println("Error: no USER_ID specified"); - return showUsage(); - } else { - userId = Integer.parseInt(optionData); - } - } - - String pkg = nextArg(); - if (pkg == null) { - System.err.println("Error: no package or component specified"); - return showUsage(); - } - try { - mPm.setApplicationHiddenSettingAsUser(pkg, state, userId); - System.out.println("Package " + pkg + " new hidden state: " - + mPm.getApplicationHiddenSettingAsUser(pkg, userId)); - return 0; - } catch (RemoteException e) { - System.err.println(e.toString()); - System.err.println(PM_NOT_RUNNING_ERR); - return 1; - } - } - - private int runGrantRevokePermission(boolean grant) { - int userId = UserHandle.USER_SYSTEM; - - String opt = null; - while ((opt = nextOption()) != null) { - if (opt.equals("--user")) { - userId = Integer.parseInt(nextArg()); - } - } - - String pkg = nextArg(); - if (pkg == null) { - System.err.println("Error: no package specified"); - return showUsage(); - } - String perm = nextArg(); - if (perm == null) { - System.err.println("Error: no permission specified"); - return showUsage(); - } - - try { - if (grant) { - mPm.grantRuntimePermission(pkg, perm, userId); - } else { - mPm.revokeRuntimePermission(pkg, perm, userId); - } - return 0; - } catch (RemoteException e) { - System.err.println(e.toString()); - System.err.println(PM_NOT_RUNNING_ERR); - return 1; - } catch (IllegalArgumentException e) { - System.err.println("Bad argument: " + e.toString()); - return showUsage(); - } catch (SecurityException e) { - System.err.println("Operation not allowed: " + e.toString()); - return 1; - } - } - - private int runResetPermissions() { - try { - mPm.resetRuntimePermissions(); - return 0; - } catch (RemoteException e) { - System.err.println(e.toString()); - System.err.println(PM_NOT_RUNNING_ERR); - return 1; - } catch (IllegalArgumentException e) { - System.err.println("Bad argument: " + e.toString()); - return showUsage(); - } catch (SecurityException e) { - System.err.println("Operation not allowed: " + e.toString()); - return 1; - } - } - private int runSetPermissionEnforced() { - final String permission = nextArg(); - if (permission == null) { - System.err.println("Error: no permission specified"); - return showUsage(); - } - final String enforcedRaw = nextArg(); - if (enforcedRaw == null) { - System.err.println("Error: no enforcement specified"); - return showUsage(); - } - final boolean enforced = Boolean.parseBoolean(enforcedRaw); - try { - mPm.setPermissionEnforced(permission, enforced); - return 0; - } catch (RemoteException e) { - System.err.println(e.toString()); - System.err.println(PM_NOT_RUNNING_ERR); - return 1; - } catch (IllegalArgumentException e) { - System.err.println("Bad argument: " + e.toString()); - return showUsage(); - } catch (SecurityException e) { - System.err.println("Operation not allowed: " + e.toString()); - return 1; - } } - static class ClearCacheObserver extends IPackageDataObserver.Stub { + static class ClearDataObserver extends IPackageDataObserver.Stub { boolean finished; boolean result; @@ -1434,66 +622,6 @@ public final class Pm { notifyAll(); } } - - } - - private int runTrimCaches() { - String size = nextArg(); - if (size == null) { - System.err.println("Error: no size specified"); - return showUsage(); - } - long multiplier = 1; - int len = size.length(); - char c = size.charAt(len - 1); - if (c < '0' || c > '9') { - if (c == 'K' || c == 'k') { - multiplier = 1024L; - } else if (c == 'M' || c == 'm') { - multiplier = 1024L*1024L; - } else if (c == 'G' || c == 'g') { - multiplier = 1024L*1024L*1024L; - } else { - System.err.println("Invalid suffix: " + c); - return showUsage(); - } - size = size.substring(0, len-1); - } - long sizeVal; - try { - sizeVal = Long.parseLong(size) * multiplier; - } catch (NumberFormatException e) { - System.err.println("Error: expected number at: " + size); - return showUsage(); - } - String volumeUuid = nextArg(); - if ("internal".equals(volumeUuid)) { - volumeUuid = null; - } - ClearDataObserver obs = new ClearDataObserver(); - try { - mPm.freeStorageAndNotify(volumeUuid, sizeVal, - StorageManager.FLAG_ALLOCATE_DEFY_ALL_RESERVED, obs); - synchronized (obs) { - while (!obs.finished) { - try { - obs.wait(); - } catch (InterruptedException e) { - } - } - } - return 0; - } catch (RemoteException e) { - System.err.println(e.toString()); - System.err.println(PM_NOT_RUNNING_ERR); - return 1; - } catch (IllegalArgumentException e) { - System.err.println("Bad argument: " + e.toString()); - return showUsage(); - } catch (SecurityException e) { - System.err.println("Operation not allowed: " + e.toString()); - return 1; - } } /** diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk index 8505d4c47aba..db634d4a6601 100644 --- a/cmds/statsd/Android.mk +++ b/cmds/statsd/Android.mk @@ -15,6 +15,7 @@ LOCAL_PATH:= $(call my-dir) statsd_common_src := \ + ../../core/java/android/os/IStatsCallbacks.aidl \ ../../core/java/android/os/IStatsCompanionService.aidl \ ../../core/java/android/os/IStatsManager.aidl \ src/stats_log.proto \ @@ -37,6 +38,8 @@ statsd_common_src := \ src/matchers/matcher_util.cpp \ src/matchers/SimpleLogMatchingTracker.cpp \ src/metrics/CountAnomalyTracker.cpp \ + src/metrics/MetricProducer.cpp \ + src/metrics/EventMetricProducer.cpp \ src/metrics/CountMetricProducer.cpp \ src/metrics/DurationMetricProducer.cpp \ src/metrics/MetricsManager.cpp \ @@ -64,7 +67,8 @@ statsd_common_shared_libraries := \ libselinux \ libutils \ libservices \ - libandroidfw + libandroidfw \ + libprotoutil # ========= # statsd diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp index e7825cf75159..68f48a4f4c37 100644 --- a/cmds/statsd/src/StatsLogProcessor.cpp +++ b/cmds/statsd/src/StatsLogProcessor.cpp @@ -33,8 +33,9 @@ namespace android { namespace os { namespace statsd { -StatsLogProcessor::StatsLogProcessor(const sp<UidMap>& uidMap) - : m_dropbox_writer("all-logs"), mUidMap(uidMap) { +StatsLogProcessor::StatsLogProcessor(const sp<UidMap>& uidMap, + const std::function<void(const vector<uint8_t>&)>& pushLog) + : m_dropbox_writer("all-logs"), mUidMap(uidMap), mPushLog(pushLog) { } StatsLogProcessor::~StatsLogProcessor() { @@ -91,6 +92,41 @@ void StatsLogProcessor::OnConfigRemoved(const ConfigKey& key) { } } +void StatsLogProcessor::addEventMetricData(const EventMetricData& eventMetricData) { + // TODO: Replace this code when MetricsManager.onDumpReport() is ready to + // get a list of byte arrays. + flushIfNecessary(eventMetricData); + const int numBytes = eventMetricData.ByteSize(); + char buffer[numBytes]; + eventMetricData.SerializeToArray(&buffer[0], numBytes); + string bufferString(buffer, numBytes); + mEvents.push_back(bufferString); + mBufferSize += eventMetricData.ByteSize(); +} + +void StatsLogProcessor::flushIfNecessary(const EventMetricData& eventMetricData) { + if (eventMetricData.ByteSize() + mBufferSize > kMaxSerializedBytes) { + flush(); + } +} + +void StatsLogProcessor::flush() { + StatsLogReport logReport; + for (string eventBuffer : mEvents) { + EventMetricData eventFromBuffer; + eventFromBuffer.ParseFromString(eventBuffer); + EventMetricData* newEntry = logReport.mutable_event_metrics()->add_data(); + newEntry->CopyFrom(eventFromBuffer); + } + + const int numBytes = logReport.ByteSize(); + vector<uint8_t> logReportBuffer(numBytes); + logReport.SerializeToArray(&logReportBuffer[0], numBytes); + mPushLog(logReportBuffer); + mEvents.clear(); + mBufferSize = 0; +} + } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h index 3cefd29fa026..08090c11f724 100644 --- a/cmds/statsd/src/StatsLogProcessor.h +++ b/cmds/statsd/src/StatsLogProcessor.h @@ -33,7 +33,8 @@ namespace statsd { class StatsLogProcessor : public ConfigListener { public: - StatsLogProcessor(const sp<UidMap>& uidMap); + StatsLogProcessor(const sp<UidMap>& uidMap, + const std::function<void(const vector<uint8_t>&)>& pushLog); virtual ~StatsLogProcessor(); virtual void OnLogEvent(const LogEvent& event); @@ -44,6 +45,9 @@ public: // TODO: Once we have the ProtoOutputStream in c++, we can just return byte array. std::vector<StatsLogReport> onDumpReport(const ConfigKey& key); + /* Request a flush through a binder call. */ + void flush(); + private: // TODO: use EventMetrics to log the events. DropboxWriter m_dropbox_writer; @@ -51,6 +55,33 @@ private: std::unordered_map<ConfigKey, std::unique_ptr<MetricsManager>> mMetricsManagers; sp<UidMap> mUidMap; // Reference to the UidMap to lookup app name and version for each uid. + + /* Max *serialized* size of the logs kept in memory before flushing through binder call. + Proto lite does not implement the SpaceUsed() function which gives the in memory byte size. + So we cap memory usage by limiting the serialized size. Note that protobuf's in memory size + is higher than its serialized size. + */ + static const size_t kMaxSerializedBytes = 16 * 1024; + + /* List of data that was captured for a single metric over a given interval of time. */ + vector<string> mEvents; + + /* Current *serialized* size of the logs kept in memory. + To save computation, we will not calculate the size of the StatsLogReport every time when a + new entry is added, which would recursively call ByteSize() on every log entry. Instead, we + keep the sum of all individual stats log entry sizes. The size of a proto is approximately + the sum of the size of all member protos. + */ + size_t mBufferSize = 0; + + /* Check if the buffer size exceeds the max buffer size when the new entry is added, and flush + the logs to dropbox if true. */ + void flushIfNecessary(const EventMetricData& eventMetricData); + + /* Append event metric data to StatsLogReport. */ + void addEventMetricData(const EventMetricData& eventMetricData); + + std::function<void(const vector<uint8_t>&)> mPushLog; }; } // namespace statsd diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp index 1faeee0a6554..604753ef54a0 100644 --- a/cmds/statsd/src/StatsService.cpp +++ b/cmds/statsd/src/StatsService.cpp @@ -68,7 +68,9 @@ StatsService::StatsService(const sp<Looper>& handlerLooper) mStatsPullerManager = new StatsPullerManager(); mUidMap = new UidMap(); mConfigManager = new ConfigManager(); - mProcessor = new StatsLogProcessor(mUidMap); + mProcessor = new StatsLogProcessor(mUidMap, [this](const vector<uint8_t>& log) { + pushLog(log); + }); mConfigManager->AddListener(mProcessor); @@ -507,6 +509,40 @@ void StatsService::OnLogEvent(const LogEvent& event) { mProcessor->OnLogEvent(event); } +Status StatsService::requestPush() { + mProcessor->flush(); + return Status::ok(); +} + +Status StatsService::pushLog(const vector<uint8_t>& log) { + std::lock_guard<std::mutex> lock(mLock); + for (size_t i = 0; i < mCallbacks.size(); i++) { + mCallbacks[i]->onReceiveLogs((vector<uint8_t>*)&log); + } + return Status::ok(); +} + +Status StatsService::subscribeStatsLog(const sp<IStatsCallbacks>& callback) { + std::lock_guard<std::mutex> lock(mLock); + for (size_t i = 0; i < mCallbacks.size(); i++) { + if (mCallbacks[i] == callback) { + return Status::fromStatusT(-errno); + } + } + mCallbacks.add(callback); + IInterface::asBinder(callback)->linkToDeath(this); + return Status::ok(); +} + +void StatsService::binderDied(const wp<IBinder>& who) { + for (size_t i = 0; i < mCallbacks.size(); i++) { + if (IInterface::asBinder(mCallbacks[i]) == who) { + mCallbacks.removeAt(i); + break; + } + } +} + } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h index 449a2b84bfd7..7f046584b2d0 100644 --- a/cmds/statsd/src/StatsService.h +++ b/cmds/statsd/src/StatsService.h @@ -24,6 +24,7 @@ #include "packages/UidMap.h" #include <android/os/BnStatsManager.h> +#include <android/os/IStatsCallbacks.h> #include <android/os/IStatsCompanionService.h> #include <binder/IResultReceiver.h> #include <binder/IShellCallback.h> @@ -42,7 +43,7 @@ namespace android { namespace os { namespace statsd { -class StatsService : public BnStatsManager, public LogListener { +class StatsService : public BnStatsManager, public LogListener, public IBinder::DeathRecipient { public: StatsService(const sp<Looper>& handlerLooper); virtual ~StatsService(); @@ -70,6 +71,22 @@ public: */ virtual void OnLogEvent(const LogEvent& event); + /** + * Binder call to force trigger pushLog. This would be called by callback + * clients. + */ + virtual Status requestPush() override; + + /** + * Pushes stats log entries from statsd to callback clients. + */ + Status pushLog(const vector<uint8_t>& log); + + /** + * Binder call to listen to statsd to send stats log entries. + */ + virtual Status subscribeStatsLog(const sp<IStatsCallbacks>& callbacks) override; + // TODO: public for testing since statsd doesn't run when system starts. Change to private // later. /** Inform statsCompanion that statsd is ready. */ @@ -78,6 +95,9 @@ public: /** Fetches and returns the StatsCompanionService. */ static sp<IStatsCompanionService> getStatsCompanionService(); + /** IBinder::DeathRecipient */ + virtual void binderDied(const wp<IBinder>& who) override; + private: /** * Load system properties at init. @@ -159,6 +179,16 @@ private: * Whether this is an eng build. */ bool mEngBuild; + + /** + * Lock for callback handling. + */ + std::mutex mLock; + + /** + * Vector maintaining the list of callbacks for clients. + */ + Vector< sp<IStatsCallbacks> > mCallbacks; }; } // namespace statsd diff --git a/cmds/statsd/src/condition/SimpleConditionTracker.cpp b/cmds/statsd/src/condition/SimpleConditionTracker.cpp index bde3846ab66b..aff476814c94 100644 --- a/cmds/statsd/src/condition/SimpleConditionTracker.cpp +++ b/cmds/statsd/src/condition/SimpleConditionTracker.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#define DEBUG true // STOPSHIP if true +#define DEBUG false // STOPSHIP if true #include "Log.h" #include "SimpleConditionTracker.h" diff --git a/cmds/statsd/src/config/ConfigManager.cpp b/cmds/statsd/src/config/ConfigManager.cpp index 038edd313081..c16971aeb8a8 100644 --- a/cmds/statsd/src/config/ConfigManager.cpp +++ b/cmds/statsd/src/config/ConfigManager.cpp @@ -120,7 +120,7 @@ static StatsdConfig build_fake_config() { int WAKE_LOCK_TAG_ID = 11; int WAKE_LOCK_UID_KEY_ID = 1; - int WAKE_LOCK_STATE_KEY = 2; + int WAKE_LOCK_STATE_KEY = 3; int WAKE_LOCK_ACQUIRE_VALUE = 1; int WAKE_LOCK_RELEASE_VALUE = 0; @@ -130,12 +130,12 @@ static StatsdConfig build_fake_config() { int APP_USAGE_FOREGROUND = 1; int APP_USAGE_BACKGROUND = 0; - int SCREEN_EVENT_TAG_ID = 2; + int SCREEN_EVENT_TAG_ID = 29; int SCREEN_EVENT_STATE_KEY = 1; int SCREEN_EVENT_ON_VALUE = 2; int SCREEN_EVENT_OFF_VALUE = 1; - int UID_PROCESS_STATE_TAG_ID = 3; + int UID_PROCESS_STATE_TAG_ID = 27; int UID_PROCESS_STATE_UID_KEY = 1; // Count Screen ON events. @@ -144,6 +144,12 @@ static StatsdConfig build_fake_config() { metric->set_what("SCREEN_TURNED_ON"); metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L); + // Anomaly threshold for screen-on count. + Alert* alert = metric->add_alerts(); + alert->set_number_of_buckets(6); + alert->set_trigger_if_sum_gt(10); + alert->set_refractory_period_secs(30); + // Count process state changes, slice by uid. metric = config.add_count_metric(); metric->set_metric_id(2); @@ -161,7 +167,7 @@ static StatsdConfig build_fake_config() { keyMatcher->set_key(UID_PROCESS_STATE_UID_KEY); metric->set_condition("SCREEN_IS_OFF"); - // Count wake lock, slice by uid, while SCREEN_IS_OFF and app in background + // Count wake lock, slice by uid, while SCREEN_IS_ON and app in background metric = config.add_count_metric(); metric->set_metric_id(4); metric->set_what("APP_GET_WL"); @@ -189,6 +195,11 @@ static StatsdConfig build_fake_config() { link->add_key_in_main()->set_key(WAKE_LOCK_UID_KEY_ID); link->add_key_in_condition()->set_key(APP_USAGE_UID_KEY_ID); + // Add an EventMetric to log process state change events. + EventMetric* eventMetric = config.add_event_metric(); + eventMetric->set_metric_id(6); + eventMetric->set_what("SCREEN_TURNED_ON"); + // Event matchers............ LogEntryMatcher* eventMatcher = config.add_log_entry_matcher(); eventMatcher->set_name("SCREEN_TURNED_ON"); diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp index fb992c11aad7..1a039f6d61c5 100644 --- a/cmds/statsd/src/logd/LogEvent.cpp +++ b/cmds/statsd/src/logd/LogEvent.cpp @@ -17,6 +17,7 @@ #include "logd/LogEvent.h" #include <sstream> +#include "stats_util.h" namespace android { namespace os { @@ -24,6 +25,7 @@ namespace statsd { using std::ostringstream; using std::string; +using android::util::ProtoOutputStream; // We need to keep a copy of the android_log_event_list owned by this instance so that the char* // for strings is not cleared before we can read them. @@ -203,30 +205,24 @@ string LogEvent::ToString() const { return result.str(); } -void LogEvent::ToProto(EventMetricData* out) const { - // TODO: Implement this when we have the ProtoOutputStream version. - - // set timestamp of the event. - out->set_timestamp_nanos(mTimestampNs); - - // uint64_t token = proto->StartObject(EventMetricData.FIELD); +void LogEvent::ToProto(ProtoOutputStream& proto) const { + long long atomToken = proto.start(TYPE_MESSAGE + mTagId); const size_t N = mElements.size(); for (size_t i=0; i<N; i++) { const int key = i + 1; const android_log_list_element& elem = mElements[i]; if (elem.type == EVENT_TYPE_INT) { - // proto->Write(key, elem.data.int32); + proto.write(TYPE_INT32 + key, elem.data.int32); } else if (elem.type == EVENT_TYPE_LONG) { - // proto->Write(key, elem.data.int64); + proto.write(TYPE_INT64 + key, (long long)elem.data.int64); } else if (elem.type == EVENT_TYPE_FLOAT) { - // proto->Write(key, elem.data.float32); + proto.write(TYPE_FLOAT + key, elem.data.float32); } else if (elem.type == EVENT_TYPE_STRING) { - // proto->Write(key, elem.data.string); + proto.write(TYPE_STRING + key, elem.data.string); } } - - //proto->EndObject(token); + proto.end(atomToken); } } // namespace statsd diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h index 410267507430..9ef20ea6968c 100644 --- a/cmds/statsd/src/logd/LogEvent.h +++ b/cmds/statsd/src/logd/LogEvent.h @@ -18,9 +18,10 @@ #include "frameworks/base/cmds/statsd/src/stats_log.pb.h" -#include <utils/Errors.h> +#include <android/util/ProtoOutputStream.h> #include <log/log_event_list.h> #include <log/log_read.h> +#include <utils/Errors.h> #include <memory> #include <string> @@ -80,10 +81,9 @@ public: string ToString() const; /** - * Write this object as an EventMetricData proto object. - * TODO: Use the streaming output generator to do this instead of this proto lite object? + * Write this object to a ProtoOutputStream. */ - void ToProto(EventMetricData* out) const; + void ToProto(android::util::ProtoOutputStream& out) const; /* * Get a KeyValuePair proto object. diff --git a/cmds/statsd/src/metrics/CountAnomalyTracker.cpp b/cmds/statsd/src/metrics/CountAnomalyTracker.cpp index e1c2b8b3a90e..7aa748fdeb77 100644 --- a/cmds/statsd/src/metrics/CountAnomalyTracker.cpp +++ b/cmds/statsd/src/metrics/CountAnomalyTracker.cpp @@ -17,23 +17,22 @@ #define DEBUG true // STOPSHIP if true #include "Log.h" -#define VLOG(...) \ - if (DEBUG) ALOGD(__VA_ARGS__); - #include "CountAnomalyTracker.h" +#include <time.h> + namespace android { namespace os { namespace statsd { -CountAnomalyTracker::CountAnomalyTracker(size_t numBuckets, int thresholdGt) - : mNumPastBuckets(numBuckets > 0 ? numBuckets - 1 : 0), - mPastBuckets(mNumPastBuckets > 0 ? (new int[mNumPastBuckets]) : nullptr), - mThresholdGt(thresholdGt) { +CountAnomalyTracker::CountAnomalyTracker(const Alert& alert) + : mAlert(alert), + mNumPastBuckets(alert.number_of_buckets() > 0 ? alert.number_of_buckets() - 1 : 0), + mPastBuckets(mNumPastBuckets > 0 ? (new int[mNumPastBuckets]) : nullptr) { VLOG("CountAnomalyTracker() called"); - if (numBuckets < 1) { - ALOGE("Cannot create CountAnomalyTracker with %zu buckets", numBuckets); + if (alert.number_of_buckets() < 1) { + ALOGE("Cannot create CountAnomalyTracker with %d buckets", alert.number_of_buckets()); } reset(); // initialization } @@ -84,22 +83,45 @@ void CountAnomalyTracker::reset() { } void CountAnomalyTracker::checkAnomaly(int currentCount) { - // Note that this works even if mNumPastBuckets < 1 (since then - // mSumPastCounters = 0 so the comparison is based only on currentCount). + // Skip the check if in refractory period. + if (time(nullptr) < mRefractoryPeriodEndsSec) { + VLOG("Skipping anomaly check since within refractory period"); + return; + } // TODO: Remove these extremely verbose debugging log. - VLOG("Checking whether %d + %d > %d", - mSumPastCounters, currentCount, mThresholdGt); + VLOG("Checking whether %d + %d > %lld", + mSumPastCounters, currentCount, mAlert.trigger_if_sum_gt()); - if (mSumPastCounters + currentCount > mThresholdGt) { + // Note that this works even if mNumPastBuckets < 1 (since then + // mSumPastCounters = 0 so the comparison is based only on currentCount). + if (mAlert.has_trigger_if_sum_gt() && + mSumPastCounters + currentCount > mAlert.trigger_if_sum_gt()) { declareAnomaly(); } } void CountAnomalyTracker::declareAnomaly() { - // TODO: check that not in refractory period. - // TODO: Do something. - ALOGI("An anomaly has occurred!"); + // TODO(guardrail): Consider guarding against too short refractory periods. + time_t currTime = time(nullptr); + mRefractoryPeriodEndsSec = currTime + mAlert.refractory_period_secs(); + + // TODO: If we had access to the bucket_size_millis, consider calling reset() + // if (mAlert.refractory_period_secs() > mNumPastBuckets * bucket_size_millis * 1000). + + if (mAlert.has_incidentd_details()) { + const Alert_IncidentdDetails& incident = mAlert.incidentd_details(); + if (incident.has_alert_name()) { + ALOGW("An anomaly (%s) has occurred! Informing incidentd.", + incident.alert_name().c_str()); + } else { + // TODO: Can construct a name based on the criteria (and/or relay the criteria). + ALOGW("An anomaly (nameless) has occurred! Informing incidentd."); + } + // TODO: Send incidentd_details.name and incidentd_details.incidentd_sections to incidentd + } else { + ALOGW("An anomaly has occurred! (But informing incidentd not requested.)"); + } } } // namespace statsd diff --git a/cmds/statsd/src/metrics/CountAnomalyTracker.h b/cmds/statsd/src/metrics/CountAnomalyTracker.h index 449dee90e0a6..79c47d2a6bab 100644 --- a/cmds/statsd/src/metrics/CountAnomalyTracker.h +++ b/cmds/statsd/src/metrics/CountAnomalyTracker.h @@ -17,16 +17,20 @@ #ifndef COUNT_ANOMALY_TRACKER_H #define COUNT_ANOMALY_TRACKER_H -#include <stdlib.h> +#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" // Alert + #include <memory> // unique_ptr +#include <stdlib.h> namespace android { namespace os { namespace statsd { +// TODO: Can probably be used for Count, Value, and Gauge. If so, rename to ValueAnomalyTracker. +// (caveat: currently, the value cannot be negative. Probably fine for P.) class CountAnomalyTracker { public: - CountAnomalyTracker(size_t numBuckets, int thresholdGt); + CountAnomalyTracker(const Alert& alert); virtual ~CountAnomalyTracker(); @@ -43,6 +47,9 @@ public: void checkAnomaly(int currentCount); private: + // statsd_config.proto Alert message that defines this tracker. + const Alert mAlert; + // Number of past buckets. One less than the total number of buckets needed // for the anomaly detection (since the current bucket is not in the past). const size_t mNumPastBuckets; @@ -59,8 +66,9 @@ private: // Index of the oldest bucket (i.e. the next bucket to be overwritten). size_t mOldestBucketIndex = 0; - // If mSumPastCounters + currentCount > mThresholdGt --> Anomaly! - const int mThresholdGt; + // Timestamp that the refractory period (if this anomaly was declared) ends, in seconds. + // If an anomaly was never declared, set to 0. + time_t mRefractoryPeriodEndsSec = 0; void declareAnomaly(); diff --git a/cmds/statsd/src/metrics/CountMetricProducer.cpp b/cmds/statsd/src/metrics/CountMetricProducer.cpp index 1f07914175d2..7bb9c8a502c1 100644 --- a/cmds/statsd/src/metrics/CountMetricProducer.cpp +++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp @@ -39,9 +39,7 @@ CountMetricProducer::CountMetricProducer(const CountMetric& metric, const int co const sp<ConditionWizard>& wizard) // TODO: Pass in the start time from MetricsManager, instead of calling time() here. : MetricProducer((time(nullptr) * NANO_SECONDS_IN_A_SECOND), conditionIndex, wizard), - mMetric(metric), - // TODO: read mAnomalyTracker parameters from config file. - mAnomalyTracker(6, 10) { + mMetric(metric) { // TODO: evaluate initial conditions. and set mConditionMet. if (metric.has_bucket() && metric.bucket().has_bucket_size_millis()) { mBucketSizeNs = metric.bucket().bucket_size_millis() * 1000 * 1000; @@ -49,6 +47,17 @@ CountMetricProducer::CountMetricProducer(const CountMetric& metric, const int co mBucketSizeNs = LLONG_MAX; } + mAnomalyTrackers.reserve(metric.alerts_size()); + for (int i = 0; i < metric.alerts_size(); i++) { + const Alert& alert = metric.alerts(i); + if (alert.trigger_if_sum_gt() > 0 && alert.number_of_buckets() > 0) { + mAnomalyTrackers.push_back(std::make_unique<CountAnomalyTracker>(alert)); + } else { + ALOGW("Ignoring invalid count metric alert: threshold=%lld num_buckets= %d", + alert.trigger_if_sum_gt(), alert.number_of_buckets()); + } + } + // TODO: use UidMap if uid->pkg_name is required mDimension.insert(mDimension.begin(), metric.dimension().begin(), metric.dimension().end()); @@ -124,52 +133,23 @@ void CountMetricProducer::onConditionChanged(const bool conditionMet) { mCondition = conditionMet; } -void CountMetricProducer::onMatchedLogEvent(const size_t matcherIndex, const LogEvent& event) { +void CountMetricProducer::onMatchedLogEventInternal( + const size_t matcherIndex, const HashableDimensionKey& eventKey, + const map<string, HashableDimensionKey>& conditionKey, bool condition, + const LogEvent& event) { uint64_t eventTimeNs = event.GetTimestampNs(); - // this is old event, maybe statsd restarted? - if (eventTimeNs < mStartTimeNs) { - return; - } flushCounterIfNeeded(eventTimeNs); - if (mConditionSliced) { - map<string, HashableDimensionKey> conditionKeys; - for (const auto& link : mConditionLinks) { - VLOG("Condition link key_in_main size %d", link.key_in_main_size()); - HashableDimensionKey conditionKey = getDimensionKeyForCondition(event, link); - conditionKeys[link.condition()] = conditionKey; - } - if (mWizard->query(mConditionTrackerIndex, conditionKeys) != ConditionState::kTrue) { - VLOG("metric %lld sliced condition not met", mMetric.metric_id()); - return; - } - } else { - if (!mCondition) { - VLOG("metric %lld condition not met", mMetric.metric_id()); - return; - } - } - - HashableDimensionKey hashableKey; - - if (mDimension.size() > 0) { - vector<KeyValuePair> key = getDimensionKey(event, mDimension); - hashableKey = getHashableKey(key); - // Add the HashableDimensionKey->vector<KeyValuePair> to the map, because StatsLogReport - // expects vector<KeyValuePair>. - if (mDimensionKeyMap.find(hashableKey) == mDimensionKeyMap.end()) { - mDimensionKeyMap[hashableKey] = key; - } - } else { - hashableKey = DEFAULT_DIMENSION_KEY; + if (condition == false) { + return; } - auto it = mCurrentSlicedCounter.find(hashableKey); + auto it = mCurrentSlicedCounter.find(eventKey); if (it == mCurrentSlicedCounter.end()) { // create a counter for the new key - mCurrentSlicedCounter[hashableKey] = 1; + mCurrentSlicedCounter[eventKey] = 1; } else { // increment the existing value @@ -177,8 +157,13 @@ void CountMetricProducer::onMatchedLogEvent(const size_t matcherIndex, const Log count++; } - VLOG("metric %lld %s->%d", mMetric.metric_id(), hashableKey.c_str(), - mCurrentSlicedCounter[hashableKey]); + // TODO: Re-add anomaly detection (similar to): + // for (auto& tracker : mAnomalyTrackers) { + // tracker->checkAnomaly(mCounter); + // } + + VLOG("metric %lld %s->%d", mMetric.metric_id(), eventKey.c_str(), + mCurrentSlicedCounter[eventKey]); } // When a new matched event comes in, we check if event falls into the current @@ -205,6 +190,11 @@ void CountMetricProducer::flushCounterIfNeeded(const uint64_t eventTimeNs) { counter.second); } + // TODO: Re-add anomaly detection (similar to): + // for (auto& tracker : mAnomalyTrackers) { + // tracker->addPastBucket(mCounter, numBucketsForward); + //} + // Reset counters mCurrentSlicedCounter.clear(); @@ -215,4 +205,4 @@ void CountMetricProducer::flushCounterIfNeeded(const uint64_t eventTimeNs) { } // namespace statsd } // namespace os -} // namespace android +} // namespace android
\ No newline at end of file diff --git a/cmds/statsd/src/metrics/CountMetricProducer.h b/cmds/statsd/src/metrics/CountMetricProducer.h index f0d60255af51..340c8309b7fa 100644 --- a/cmds/statsd/src/metrics/CountMetricProducer.h +++ b/cmds/statsd/src/metrics/CountMetricProducer.h @@ -41,8 +41,6 @@ public: virtual ~CountMetricProducer(); - void onMatchedLogEvent(const size_t matcherIndex, const LogEvent& event) override; - void onConditionChanged(const bool conditionMet) override; void finish() override; @@ -54,17 +52,22 @@ public: // TODO: Implement this later. virtual void notifyAppUpgrade(const string& apk, const int uid, const int version) override{}; +protected: + void onMatchedLogEventInternal(const size_t matcherIndex, const HashableDimensionKey& eventKey, + const std::map<std::string, HashableDimensionKey>& conditionKey, + bool condition, const LogEvent& event) override; + private: const CountMetric mMetric; - CountAnomalyTracker mAnomalyTracker; - // Save the past buckets and we can clear when the StatsLogReport is dumped. std::unordered_map<HashableDimensionKey, std::vector<CountBucketInfo>> mPastBuckets; // The current bucket. std::unordered_map<HashableDimensionKey, int> mCurrentSlicedCounter; + vector<unique_ptr<CountAnomalyTracker>> mAnomalyTrackers; + void flushCounterIfNeeded(const uint64_t newEventTime); }; diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp index aa597f421cb6..38e55fde40d5 100644 --- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp +++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp @@ -137,11 +137,10 @@ StatsLogReport DurationMetricProducer::onDumpReport() { return report; }; -void DurationMetricProducer::onMatchedLogEvent(const size_t matcherIndex, const LogEvent& event) { - if (event.GetTimestampNs() < mStartTimeNs) { - return; - } - +void DurationMetricProducer::onMatchedLogEventInternal( + const size_t matcherIndex, const HashableDimensionKey& eventKey, + const map<string, HashableDimensionKey>& conditionKeys, bool condition, + const LogEvent& event) { flushDurationIfNeeded(event.GetTimestampNs()); if (matcherIndex == mStopAllIndex) { @@ -149,49 +148,20 @@ void DurationMetricProducer::onMatchedLogEvent(const size_t matcherIndex, const return; } - HashableDimensionKey hashableKey; - if (mDimension.size() > 0) { - // hook up sliced counter with AnomalyMonitor. - vector<KeyValuePair> key = getDimensionKey(event, mDimension); - hashableKey = getHashableKey(key); - // Add the HashableDimensionKey->DimensionKey to the map, because StatsLogReport expects - // vector<KeyValuePair>. - if (mDimensionKeyMap.find(hashableKey) == mDimensionKeyMap.end()) { - mDimensionKeyMap[hashableKey] = key; - } - } else { - hashableKey = DEFAULT_DIMENSION_KEY; - } - - if (mCurrentSlicedDuration.find(hashableKey) == mCurrentSlicedDuration.end() && - mConditionSliced) { + if (mCurrentSlicedDuration.find(eventKey) == mCurrentSlicedDuration.end() && mConditionSliced) { // add the durationInfo for the current bucket. - auto& durationInfo = mCurrentSlicedDuration[hashableKey]; - auto& conditionKeys = durationInfo.conditionKeys; - // get and cache the keys for query condition. - for (const auto& link : mConditionLinks) { - HashableDimensionKey conditionKey = getDimensionKeyForCondition(event, link); - conditionKeys[link.condition()] = conditionKey; - } - } - - bool conditionMet; - if (mConditionSliced) { - const auto& conditionKeys = mCurrentSlicedDuration[hashableKey].conditionKeys; - conditionMet = - mWizard->query(mConditionTrackerIndex, conditionKeys) == ConditionState::kTrue; - } else { - conditionMet = mCondition; + auto& durationInfo = mCurrentSlicedDuration[eventKey]; + durationInfo.conditionKeys = conditionKeys; } if (matcherIndex == mStartIndex) { - VLOG("Metric %lld Key: %s Start, Condition %d", mMetric.metric_id(), hashableKey.c_str(), - conditionMet); - noteStart(hashableKey, conditionMet, event.GetTimestampNs()); + VLOG("Metric %lld Key: %s Start, Condition %d", mMetric.metric_id(), eventKey.c_str(), + condition); + noteStart(eventKey, condition, event.GetTimestampNs()); } else if (matcherIndex == mStopIndex) { - VLOG("Metric %lld Key: %s Stop, Condition %d", mMetric.metric_id(), hashableKey.c_str(), - conditionMet); - noteStop(hashableKey, event.GetTimestampNs()); + VLOG("Metric %lld Key: %s Stop, Condition %d", mMetric.metric_id(), eventKey.c_str(), + condition); + noteStop(eventKey, event.GetTimestampNs()); } } diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h index 44c325417bd7..19e2437ca538 100644 --- a/cmds/statsd/src/metrics/DurationMetricProducer.h +++ b/cmds/statsd/src/metrics/DurationMetricProducer.h @@ -61,8 +61,6 @@ public: virtual ~DurationMetricProducer(); - void onMatchedLogEvent(const size_t matcherIndex, const LogEvent& event) override; - void onConditionChanged(const bool conditionMet) override; void finish() override; @@ -74,6 +72,11 @@ public: // TODO: Implement this later. virtual void notifyAppUpgrade(const string& apk, const int uid, const int version) override{}; +protected: + void onMatchedLogEventInternal(const size_t matcherIndex, const HashableDimensionKey& eventKey, + const std::map<std::string, HashableDimensionKey>& conditionKeys, + bool condition, const LogEvent& event) override; + private: const DurationMetric mMetric; diff --git a/cmds/statsd/src/metrics/EventMetricProducer.cpp b/cmds/statsd/src/metrics/EventMetricProducer.cpp new file mode 100644 index 000000000000..8b3f405eec80 --- /dev/null +++ b/cmds/statsd/src/metrics/EventMetricProducer.cpp @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2017 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. + */ + +#define DEBUG true // STOPSHIP if true +#include "Log.h" + +#include "EventMetricProducer.h" +#include "stats_util.h" + +#include <cutils/log.h> +#include <limits.h> +#include <stdlib.h> + +using android::util::ProtoOutputStream; +using std::map; +using std::string; +using std::unordered_map; +using std::vector; + +namespace android { +namespace os { +namespace statsd { + +// for StatsLogReport +const int FIELD_ID_METRIC_ID = 1; +const int FIELD_ID_START_REPORT_NANOS = 2; +const int FIELD_ID_END_REPORT_NANOS = 2; +const int FIELD_ID_EVENT_METRICS = 4; +// for EventMetricData +const int FIELD_ID_TIMESTAMP_NANOS = 1; +const int FIELD_ID_STATS_EVENTS = 2; +// for CountMetricDataWrapper +const int FIELD_ID_DATA = 1; + +EventMetricProducer::EventMetricProducer(const EventMetric& metric, const int conditionIndex, + const sp<ConditionWizard>& wizard) + // TODO: Pass in the start time from MetricsManager, instead of calling time() here. + : MetricProducer((time(nullptr) * NANO_SECONDS_IN_A_SECOND), conditionIndex, wizard), + mMetric(metric) { + if (metric.links().size() > 0) { + mConditionLinks.insert(mConditionLinks.begin(), metric.links().begin(), + metric.links().end()); + mConditionSliced = true; + } + + startNewProtoOutputStream(mStartTimeNs); + + VLOG("metric %lld created. bucket size %lld start_time: %lld", metric.metric_id(), + (long long)mBucketSizeNs, (long long)mStartTimeNs); +} + +EventMetricProducer::~EventMetricProducer() { + VLOG("~EventMetricProducer() called"); +} + +void EventMetricProducer::startNewProtoOutputStream(long long startTime) { + mProto = std::make_unique<ProtoOutputStream>(); + // TODO: We need to auto-generate the field IDs for StatsLogReport, EventMetricData, + // and StatsEvent. + mProto->write(TYPE_INT32 + FIELD_ID_METRIC_ID, mMetric.metric_id()); + mProto->write(TYPE_INT64 + FIELD_ID_START_REPORT_NANOS, startTime); + mProtoToken = mProto->start(TYPE_MESSAGE + FIELD_ID_EVENT_METRICS); +} + +void EventMetricProducer::finish() { +} + +void EventMetricProducer::onSlicedConditionMayChange() { +} + +StatsLogReport EventMetricProducer::onDumpReport() { + long long endTime = time(nullptr) * NANO_SECONDS_IN_A_SECOND; + mProto->end(mProtoToken); + mProto->write(TYPE_INT64 + FIELD_ID_END_REPORT_NANOS, endTime); + + size_t bufferSize = mProto->size(); + VLOG("metric %lld dump report now... proto size: %zu ", mMetric.metric_id(), bufferSize); + std::unique_ptr<uint8_t[]> buffer(new uint8_t[bufferSize]); + size_t pos = 0; + auto it = mProto->data(); + while (it.readBuffer() != NULL) { + size_t toRead = it.currentToRead(); + std::memcpy(&buffer[pos], it.readBuffer(), toRead); + pos += toRead; + it.rp()->move(toRead); + } + + startNewProtoOutputStream(endTime); + + // TODO: Once we migrate all MetricProducers to use ProtoOutputStream, we should return this: + // return std::move(buffer); + return StatsLogReport(); +} + +void EventMetricProducer::onConditionChanged(const bool conditionMet) { + VLOG("Metric %lld onConditionChanged", mMetric.metric_id()); + mCondition = conditionMet; +} + +void EventMetricProducer::onMatchedLogEventInternal( + const size_t matcherIndex, const HashableDimensionKey& eventKey, + const std::map<std::string, HashableDimensionKey>& conditionKey, bool condition, + const LogEvent& event) { + + if (!condition) { + return; + } + + long long wrapperToken = mProto->start(TYPE_MESSAGE + FIELD_ID_DATA); + mProto->write(TYPE_INT64 + FIELD_ID_TIMESTAMP_NANOS, (long long)event.GetTimestampNs()); + long long eventToken = mProto->start(TYPE_MESSAGE + FIELD_ID_STATS_EVENTS); + event.ToProto(*mProto); + mProto->end(eventToken); + mProto->end(wrapperToken); +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/cmds/statsd/src/metrics/EventMetricProducer.h b/cmds/statsd/src/metrics/EventMetricProducer.h new file mode 100644 index 000000000000..879175cc25ff --- /dev/null +++ b/cmds/statsd/src/metrics/EventMetricProducer.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2017 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 EVENT_METRIC_PRODUCER_H +#define EVENT_METRIC_PRODUCER_H + +#include <unordered_map> + +#include <android/util/ProtoOutputStream.h> +#include "../condition/ConditionTracker.h" +#include "../matchers/matcher_util.h" +#include "MetricProducer.h" +#include "frameworks/base/cmds/statsd/src/stats_log.pb.h" +#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" +#include "stats_util.h" + +namespace android { +namespace os { +namespace statsd { + +class EventMetricProducer : public MetricProducer { +public: + // TODO: Pass in the start time from MetricsManager, it should be consistent for all metrics. + EventMetricProducer(const EventMetric& eventMetric, const int conditionIndex, + const sp<ConditionWizard>& wizard); + + virtual ~EventMetricProducer(); + + void onMatchedLogEventInternal(const size_t matcherIndex, const HashableDimensionKey& eventKey, + const std::map<std::string, HashableDimensionKey>& conditionKey, + bool condition, const LogEvent& event) override; + + void onConditionChanged(const bool conditionMet) override; + + void finish() override; + + StatsLogReport onDumpReport() override; + + void onSlicedConditionMayChange() override; + + // TODO: Implement this later. + virtual void notifyAppUpgrade(const string& apk, const int uid, const int version) override{}; + +private: + const EventMetric mMetric; + + std::unique_ptr<android::util::ProtoOutputStream> mProto; + + long long mProtoToken; + + void startNewProtoOutputStream(long long timestamp); +}; + +} // namespace statsd +} // namespace os +} // namespace android +#endif // EVENT_METRIC_PRODUCER_H diff --git a/cmds/statsd/src/metrics/MetricProducer.cpp b/cmds/statsd/src/metrics/MetricProducer.cpp new file mode 100644 index 000000000000..3c8ce6e7dd1c --- /dev/null +++ b/cmds/statsd/src/metrics/MetricProducer.cpp @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2017 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 "MetricProducer.h" + +namespace android { +namespace os { +namespace statsd { + +using std::map; + +void MetricProducer::onMatchedLogEvent(const size_t matcherIndex, const LogEvent& event) { + uint64_t eventTimeNs = event.GetTimestampNs(); + // this is old event, maybe statsd restarted? + if (eventTimeNs < mStartTimeNs) { + return; + } + + HashableDimensionKey eventKey; + + if (mDimension.size() > 0) { + vector<KeyValuePair> key = getDimensionKey(event, mDimension); + eventKey = getHashableKey(key); + // Add the HashableDimensionKey->vector<KeyValuePair> to the map, because StatsLogReport + // expects vector<KeyValuePair>. + if (mDimensionKeyMap.find(eventKey) == mDimensionKeyMap.end()) { + mDimensionKeyMap[eventKey] = key; + } + } else { + eventKey = DEFAULT_DIMENSION_KEY; + } + + bool condition; + + map<string, HashableDimensionKey> conditionKeys; + if (mConditionSliced) { + for (const auto& link : mConditionLinks) { + HashableDimensionKey conditionKey = getDimensionKeyForCondition(event, link); + conditionKeys[link.condition()] = conditionKey; + } + if (mWizard->query(mConditionTrackerIndex, conditionKeys) != ConditionState::kTrue) { + condition = false; + } else { + condition = true; + } + } else { + condition = mCondition; + } + + onMatchedLogEventInternal(matcherIndex, eventKey, conditionKeys, condition, event); +} + +} // namespace statsd +} // namespace os +} // namespace android
\ No newline at end of file diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h index afaab648c09f..496b1453e46c 100644 --- a/cmds/statsd/src/metrics/MetricProducer.h +++ b/cmds/statsd/src/metrics/MetricProducer.h @@ -48,7 +48,7 @@ public: virtual ~MetricProducer(){}; // Consume the parsed stats log entry that already matched the "what" of the metric. - virtual void onMatchedLogEvent(const size_t matcherIndex, const LogEvent& event) = 0; + void onMatchedLogEvent(const size_t matcherIndex, const LogEvent& event); virtual void onConditionChanged(const bool condition) = 0; @@ -86,6 +86,26 @@ protected: std::unordered_map<HashableDimensionKey, std::vector<KeyValuePair>> mDimensionKeyMap; std::vector<EventConditionLink> mConditionLinks; + + /* + * Individual metrics can implement their own business logic here. All pre-processing is done. + * + * [matcherIndex]: the index of the matcher which matched this event. This is interesting to + * DurationMetric, because it has start/stop/stop_all 3 matchers. + * [eventKey]: the extracted dimension key for the final output. if the metric doesn't have + * dimensions, it will be DEFAULT_DIMENSION_KEY + * [conditionKey]: the keys of conditions which should be used to query the condition for this + * target event (from EventConditionLink). This is passed to individual metrics + * because DurationMetric needs it to be cached. + * [condition]: whether condition is met. If condition is sliced, this is the result coming from + * query with ConditionWizard; If condition is not sliced, this is the + * nonSlicedCondition. + * [event]: the log event, just in case the metric needs its data, e.g., EventMetric. + */ + virtual void onMatchedLogEventInternal( + const size_t matcherIndex, const HashableDimensionKey& eventKey, + const std::map<std::string, HashableDimensionKey>& conditionKey, bool condition, + const LogEvent& event) = 0; }; } // namespace statsd diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp index c19d46298de5..1ffa58b8c862 100644 --- a/cmds/statsd/src/metrics/MetricsManager.cpp +++ b/cmds/statsd/src/metrics/MetricsManager.cpp @@ -43,7 +43,7 @@ MetricsManager::MetricsManager(const StatsdConfig& config) { } MetricsManager::~MetricsManager() { - VLOG("~MetricManager()"); + VLOG("~MetricsManager()"); } bool MetricsManager::isConfigValid() const { diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h index 56f57d3d3654..2f91460061fa 100644 --- a/cmds/statsd/src/metrics/MetricsManager.h +++ b/cmds/statsd/src/metrics/MetricsManager.h @@ -51,11 +51,11 @@ private: std::set<int> mTagIds; // We only store the sp of LogMatchingTracker, MetricProducer, and ConditionTracker in - // MetricManager. There are relationship between them, and the relationship are denoted by index - // instead of pointers. The reasons for this are: (1) the relationship between them are - // complicated, store index instead of pointers reduce the risk of A holds B's sp, and B holds - // A's sp. (2) When we evaluate matcher results, or condition results, we can quickly get the - // related results from a cache using the index. + // MetricsManager. There are relationships between them, and the relationships are denoted by + // index instead of pointers. The reasons for this are: (1) the relationship between them are + // complicated, so storing index instead of pointers reduces the risk that A holds B's sp, and B + // holds A's sp. (2) When we evaluate matcher results, or condition results, we can quickly get + // the related results from a cache using the index. // Hold all the log entry matchers from the config. std::vector<sp<LogMatchingTracker>> mAllLogEntryMatchers; diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp index 23071aa0899c..e90f998a7179 100644 --- a/cmds/statsd/src/metrics/metrics_manager_util.cpp +++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp @@ -20,6 +20,7 @@ #include "../matchers/SimpleLogMatchingTracker.h" #include "CountMetricProducer.h" #include "DurationMetricProducer.h" +#include "EventMetricProducer.h" #include "stats_util.h" using std::set; @@ -31,13 +32,51 @@ namespace android { namespace os { namespace statsd { -int getTrackerIndex(const string& name, const unordered_map<string, int>& logTrackerMap) { - auto logTrackerIt = logTrackerMap.find(name); +bool handleMetricWithLogTrackers(const string what, const int metricIndex, + const unordered_map<string, int>& logTrackerMap, + unordered_map<int, std::vector<int>>& trackerToMetricMap, + int& logTrackerIndex) { + auto logTrackerIt = logTrackerMap.find(what); if (logTrackerIt == logTrackerMap.end()) { - ALOGW("cannot find the LogEventMatcher %s in config", name.c_str()); - return MATCHER_NOT_FOUND; + ALOGW("cannot find the LogEntryMatcher %s in config", what.c_str()); + return false; + } + logTrackerIndex = logTrackerIt->second; + auto& metric_list = trackerToMetricMap[logTrackerIndex]; + metric_list.push_back(metricIndex); + return true; +} + +bool handleMetricWithConditions( + const string condition, const int metricIndex, + const unordered_map<string, int>& conditionTrackerMap, + const ::google::protobuf::RepeatedPtrField<::android::os::statsd::EventConditionLink>& + links, + vector<sp<ConditionTracker>>& allConditionTrackers, int& conditionIndex, + unordered_map<int, std::vector<int>>& conditionToMetricMap) { + auto condition_it = conditionTrackerMap.find(condition); + if (condition_it == conditionTrackerMap.end()) { + ALOGW("cannot find the Condition %s in the config", condition.c_str()); + return false; } - return logTrackerIt->second; + + for (const auto& link : links) { + auto it = conditionTrackerMap.find(link.condition()); + if (it == conditionTrackerMap.end()) { + ALOGW("cannot find the Condition %s in the config", link.condition().c_str()); + return false; + } + allConditionTrackers[condition_it->second]->setSliced(true); + allConditionTrackers[it->second]->setSliced(true); + allConditionTrackers[it->second]->addDimensions( + vector<KeyMatcher>(link.key_in_condition().begin(), link.key_in_condition().end())); + } + conditionIndex = condition_it->second; + + // will create new vector if not exist before. + auto& metricList = conditionToMetricMap[condition_it->second]; + metricList.push_back(metricIndex); + return true; } bool initLogTrackers(const StatsdConfig& config, unordered_map<string, int>& logTrackerMap, @@ -142,7 +181,8 @@ bool initMetrics(const StatsdConfig& config, const unordered_map<string, int>& l unordered_map<int, std::vector<int>>& conditionToMetricMap, unordered_map<int, std::vector<int>>& trackerToMetricMap) { sp<ConditionWizard> wizard = new ConditionWizard(allConditionTrackers); - const int allMetricsCount = config.count_metric_size() + config.duration_metric_size(); + const int allMetricsCount = + config.count_metric_size() + config.duration_metric_size() + config.event_metric_size(); allMetricProducers.reserve(allMetricsCount); // Build MetricProducers for each metric defined in config. @@ -155,100 +195,52 @@ bool initMetrics(const StatsdConfig& config, const unordered_map<string, int>& l } int metricIndex = allMetricProducers.size(); - - auto logTrackerIt = logTrackerMap.find(metric.what()); - if (logTrackerIt == logTrackerMap.end()) { - ALOGW("cannot find the LogEntryMatcher %s in config", metric.what().c_str()); + int trackerIndex; + if (!handleMetricWithLogTrackers(metric.what(), metricIndex, logTrackerMap, + trackerToMetricMap, trackerIndex)) { return false; } - int logTrackerIndex = logTrackerIt->second; - auto& metric_list = trackerToMetricMap[logTrackerIndex]; - metric_list.push_back(metricIndex); - - sp<MetricProducer> countProducer; + int conditionIndex = -1; if (metric.has_condition()) { - auto condition_it = conditionTrackerMap.find(metric.condition()); - if (condition_it == conditionTrackerMap.end()) { - ALOGW("cannot find the Condition %s in the config", metric.condition().c_str()); - return false; - } - - for (const auto& link : metric.links()) { - auto it = conditionTrackerMap.find(link.condition()); - if (it == conditionTrackerMap.end()) { - ALOGW("cannot find the Condition %s in the config", link.condition().c_str()); - return false; - } - allConditionTrackers[condition_it->second]->setSliced(true); - allConditionTrackers[it->second]->setSliced(true); - allConditionTrackers[it->second]->addDimensions(vector<KeyMatcher>( - link.key_in_condition().begin(), link.key_in_condition().end())); - } - - countProducer = new CountMetricProducer(metric, condition_it->second, wizard); - // will create new vector if not exist before. - auto& metricList = conditionToMetricMap[condition_it->second]; - metricList.push_back(metricIndex); - } else { - countProducer = new CountMetricProducer(metric, -1 /*no condition*/, wizard); + handleMetricWithConditions(metric.condition(), metricIndex, conditionTrackerMap, + metric.links(), allConditionTrackers, conditionIndex, + conditionToMetricMap); } + + sp<MetricProducer> countProducer = new CountMetricProducer(metric, conditionIndex, wizard); allMetricProducers.push_back(countProducer); } for (int i = 0; i < config.duration_metric_size(); i++) { int metricIndex = allMetricProducers.size(); - const DurationMetric metric = config.duration_metric(i); - if (!metric.has_start()) { - ALOGW("cannot find start in DurationMetric %lld", metric.metric_id()); + const DurationMetric& metric = config.duration_metric(i); + int trackerIndices[3] = {-1, -1, -1}; + if (!metric.has_start() || + !handleMetricWithLogTrackers(metric.start(), metricIndex, logTrackerMap, + trackerToMetricMap, trackerIndices[0])) { + ALOGE("Duration metrics must specify a valid the start event matcher"); return false; } - int trackerIndices[] = {-1, -1, -1}; - trackerIndices[0] = getTrackerIndex(metric.start(), logTrackerMap); - - if (metric.has_stop()) { - trackerIndices[1] = getTrackerIndex(metric.stop(), logTrackerMap); - } - - if (metric.has_stop_all()) { - trackerIndices[2] = getTrackerIndex(metric.stop_all(), logTrackerMap); + if (metric.has_stop() && + !handleMetricWithLogTrackers(metric.stop(), metricIndex, logTrackerMap, + trackerToMetricMap, trackerIndices[1])) { + return false; } - for (const int& index : trackerIndices) { - if (index == MATCHER_NOT_FOUND) { - return false; - } - if (index >= 0) { - auto& metric_list = trackerToMetricMap[index]; - metric_list.push_back(metricIndex); - } + if (metric.has_stop_all() && + !handleMetricWithLogTrackers(metric.stop_all(), metricIndex, logTrackerMap, + trackerToMetricMap, trackerIndices[2])) { + return false; } int conditionIndex = -1; if (metric.has_predicate()) { - auto condition_it = conditionTrackerMap.find(metric.predicate()); - if (condition_it == conditionTrackerMap.end()) { - ALOGW("cannot find the Condition %s in the config", metric.predicate().c_str()); - return false; - } - conditionIndex = condition_it->second; - - for (const auto& link : metric.links()) { - auto it = conditionTrackerMap.find(link.condition()); - if (it == conditionTrackerMap.end()) { - ALOGW("cannot find the Condition %s in the config", link.condition().c_str()); - return false; - } - allConditionTrackers[condition_it->second]->setSliced(true); - allConditionTrackers[it->second]->setSliced(true); - allConditionTrackers[it->second]->addDimensions(vector<KeyMatcher>( - link.key_in_condition().begin(), link.key_in_condition().end())); - } - - auto& metricList = conditionToMetricMap[conditionIndex]; - metricList.push_back(metricIndex); + handleMetricWithConditions(metric.predicate(), metricIndex, conditionTrackerMap, + metric.links(), allConditionTrackers, conditionIndex, + conditionToMetricMap); } sp<MetricProducer> durationMetric = @@ -257,6 +249,32 @@ bool initMetrics(const StatsdConfig& config, const unordered_map<string, int>& l allMetricProducers.push_back(durationMetric); } + + for (int i = 0; i < config.event_metric_size(); i++) { + int metricIndex = allMetricProducers.size(); + const EventMetric& metric = config.event_metric(i); + if (!metric.has_metric_id() || !metric.has_what()) { + ALOGW("cannot find the metric id or what in config"); + return false; + } + int trackerIndex; + if (!handleMetricWithLogTrackers(metric.what(), metricIndex, logTrackerMap, + trackerToMetricMap, trackerIndex)) { + return false; + } + + int conditionIndex = -1; + if (metric.has_condition()) { + handleMetricWithConditions(metric.condition(), metricIndex, conditionTrackerMap, + metric.links(), allConditionTrackers, conditionIndex, + conditionToMetricMap); + } + + sp<MetricProducer> eventMetric = new EventMetricProducer(metric, conditionIndex, wizard); + + allMetricProducers.push_back(eventMetric); + } + return true; } diff --git a/cmds/statsd/src/metrics/metrics_manager_util.h b/cmds/statsd/src/metrics/metrics_manager_util.h index 38149a6aecd6..6722eb3cfe72 100644 --- a/cmds/statsd/src/metrics/metrics_manager_util.h +++ b/cmds/statsd/src/metrics/metrics_manager_util.h @@ -22,7 +22,6 @@ #include "../condition/ConditionTracker.h" #include "../matchers/LogMatchingTracker.h" -#include "CountMetricProducer.h" namespace android { namespace os { @@ -81,7 +80,7 @@ bool initMetrics( std::unordered_map<int, std::vector<int>>& conditionToMetricMap, std::unordered_map<int, std::vector<int>>& trackerToMetricMap); -// Initialize MetricManager from StatsdConfig. +// Initialize MetricsManager from StatsdConfig. // Parameters are the members of MetricsManager. See MetricsManager for declaration. bool initStatsdConfig(const StatsdConfig& config, std::set<int>& allTagIds, std::vector<sp<LogMatchingTracker>>& allLogEntryMatchers, @@ -91,8 +90,6 @@ bool initStatsdConfig(const StatsdConfig& config, std::set<int>& allTagIds, std::unordered_map<int, std::vector<int>>& trackerToMetricMap, std::unordered_map<int, std::vector<int>>& trackerToConditionMap); -int getTrackerIndex(const std::string& name, const std::unordered_map<string, int>& logTrackerMap); - } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/src/stats_events.proto b/cmds/statsd/src/stats_events.proto index cd00ba8e8898..74ee3325a943 100644 --- a/cmds/statsd/src/stats_events.proto +++ b/cmds/statsd/src/stats_events.proto @@ -36,9 +36,41 @@ option java_outer_classname = "StatsEventProto"; */ message StatsEvent { oneof event { - ScreenStateChanged screen_state_changed = 1; - ProcessStateChanged process_state_changed = 2; - WakeLockChanged wakelock_changed = 3; + // For StatsLog reasons, 1 is illegal and will not work. Must start at 2. + BleScanStateChanged ble_scan_state_changed = 2; + BleUnoptimizedScanStateChanged ble_unoptimized_scan_state_changed = 3; + BleScanResultReceived ble_scan_result_received = 4; + SensorStateChanged sensor_state_changed = 5; + GpsScanStateChanged gps_scan_state_changed = 6; // TODO: untested + SyncStateChanged sync_state_changed = 7; + ScheduledJobStateChanged scheduled_job_state_changed = 8; + ScreenBrightnessChanged screen_brightness_changed = 9; + // 10-20 are temporarily reserved for wakelocks etc. + WakelockStateChanged wakelock_state_changed = 10; + UidWakelockStateChanged uid_wakelock_state_changed = 11; + LongPartialWakelockStateChanged long_partial_wakelock_state_changed = 12; + BatterySaverModeStateChanged battery_saver_mode_state_changed = 21; + DeviceIdleModeStateChanged device_idle_mode_state_changed = 22; + AudioStateChanged audio_state_changed = 23; + MediaCodecActivityChanged media_codec_activity_changed = 24; + CameraStateChanged camera_state_changed = 25; + FlashlightStateChanged flashlight_state_changed = 26; + UidProcessStateChanged uid_process_state_changed = 27; + ProcessLifeCycleStateChanged process_life_cycle_state_changed = 28; + ScreenStateChanged screen_state_changed = 29; + BatteryLevelChanged battery_level_changed = 30; + ChargingStateChanged charging_state_changed = 31; + PluggedStateChanged plugged_state_changed = 32; + DeviceTemperatureReported device_temperature_reported = 33; + DeviceOnStatusChanged device_on_status_changed = 34; + WakeupAlarmOccurred wakeup_alarm_occurred = 35; + KernelWakeupReported kernel_wakeup_reported = 36; + WifiLockStateChanged wifi_lock_state_changed = 37; + WifiSignalStrengthChanged wifi_signal_strength_changed = 38; + WifiScanStateChanged wifi_scan_state_changed = 39; + PhoneSignalStrengthChanged phone_signal_strength_changed = 40; + + // TODO: Reorder the numbering so that the most frequent occur events occur in the first 15. } } @@ -72,7 +104,7 @@ message WorkSource { * and those UIDs will be translated in xxx to those strings. * * CONVENTIONS: - * - Events are past tense. e.g. ScreenStateChanged, not ScreenStateChange + * - Events are past tense. e.g. ScreenStateChanged, not ScreenStateChange. * - If there is a UID, it goes first. Think in an object-oriented fashion. * ***************************************************************************** */ @@ -98,33 +130,519 @@ message ScreenStateChanged { } /** - * Logs that the state of a process state, as per the activity manager has changed. + * Logs that the state of a process state, as per the activity manager, has changed. * * Logged from: * frameworks/base/services/core/java/com/android/server/am/BatteryStatsService.java */ -message ProcessStateChanged { - // TODO: Use the real (mapped) process states. +message UidProcessStateChanged { optional int32 uid = 1; // TODO: should be a string tagged w/ uid annotation // The state. + // TODO: Use the real (mapped) process states. optional int32 state = 2; } /** - * Logs that the state of a wakelock has changed. + * Logs that a process started, finished, crashed, or ANRed. + * + * Logged from: + * frameworks/base/services/core/java/com/android/server/am/BatteryStatsService.java + */ +message ProcessLifeCycleStateChanged { + // TODO: Use the real (mapped) process states. + optional int32 uid = 1; // TODO: should be a string tagged w/ uid annotation + + // TODO: What is this? + optional string name = 2; + + // The state. + // TODO: Use an enum. + optional int32 event = 3; +} + + + +/** + * Logs when the ble scan state changes. + * + * Logged from: + * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java + */ +message BleScanStateChanged { + // TODO: Add attribution instead of uid. + optional int32 uid = 1; + + enum State { + OFF = 0; + ON = 1; + } + optional State state = 2; +} + +/** + * Logs when an unoptimized ble scan state changes. + * + * Logged from: + * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java + */ +// TODO: Consider changing to tracking per-scanner-id (log from AppScanStats). +message BleUnoptimizedScanStateChanged { + // TODO: Add attribution instead of uid. + optional int32 uid = 1; + + enum State { + OFF = 0; + ON = 1; + } + optional State state = 2; +} + +/** + * Logs reporting of a ble scan finding results. + * + * Logged from: + * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java + */ +// TODO: Consider changing to tracking per-scanner-id (log from AppScanStats). +message BleScanResultReceived { + // TODO: Add attribution instead of uid. + optional int32 uid = 1; + + // Number of ble scan results returned. + optional int32 num_of_results = 2; +} + +/** + * Logs when a sensor state changes. + * + * Logged from: + * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java + */ +message SensorStateChanged { + // TODO: Add attribution instead of uid. + optional int32 uid = 1; + + // TODO: Is there a way to get the actual name of the sensor? + // The id (int) of the sensor. + optional int32 sensor_id = 2; + + enum State { + OFF = 0; + ON = 1; + } + optional State state = 3; +} + + +/** + * Logs when GPS state changes. + * + * Logged from: + * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java + */ +message GpsScanStateChanged { + // TODO: Add attribution instead of uid. + optional int32 uid = 1; + + enum State { + OFF = 0; + ON = 1; + } + optional State state = 2; +} + + +/** + * Logs when a sync manager sync state changes. + * + * Logged from: + * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java + */ +message SyncStateChanged { + // TODO: Add attribution instead of uid. + optional int32 uid = 1; + + // Name of the sync (as named in the app) + optional string name = 2; + + enum State { + OFF = 0; + ON = 1; + } + optional State state = 3; +} + +/** + * Logs when a job scheduler job state changes. + * + * Logged from: + * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java + */ +message ScheduledJobStateChanged { + // TODO: Add attribution instead of uid. + optional int32 uid = 1; + + // Name of the job (as named in the app) + optional string name = 2; + + enum State { + OFF = 0; + ON = 1; + } + optional State state = 3; + + // TODO: Consider adding the stopReason (int) +} + +/** + * Logs when the audio state changes. + * + * Logged from: + * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java + */ +message AudioStateChanged { + // TODO: Add attribution instead of uid. + optional int32 uid = 1; + + enum State { + OFF = 0; + ON = 1; + } + optional State state = 2; +} + +/** + * Logs when the video codec state changes. + * + * Logged from: + * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java + */ +message MediaCodecActivityChanged { + // TODO: Add attribution instead of uid. + optional int32 uid = 1; + + enum State { + OFF = 0; + ON = 1; + } + optional State state = 2; +} + +/** + * Logs when the flashlight state changes. + * + * Logged from: + * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java + */ +message FlashlightStateChanged { + // TODO: Add attribution instead of uid. + optional int32 uid = 1; + + enum State { + OFF = 0; + ON = 1; + } + optional State state = 2; +} + +/** + * Logs when the camera state changes. + * + * Logged from: + * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java + */ +message CameraStateChanged { + // TODO: Add attribution instead of uid. + optional int32 uid = 1; + + enum State { + OFF = 0; + ON = 1; + } + optional State state = 2; +} + +/** + * Logs that the state of a wakelock (per app and per wakelock name) has changed. * * Logged from: * TODO */ -message WakeLockChanged { +message WakelockStateChanged { // TODO: Add attribution instead of uid. optional int32 uid = 1; + // Type of wakelock. + enum Type { + PARTIAL = 0; + FULL = 1; + WINDOW = 2; + } + optional int32 type = 2; + + // The wakelock tag (Called tag in the Java API, sometimes name elsewhere). + optional string tag = 3; + + enum State { + OFF = 0; + ON = 1; + } + optional State state = 4; +} + +/** + * Logs when an app is holding a wakelock, regardless of the wakelock's name. + * + * Logged from: + * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java + */ +message UidWakelockStateChanged { + // TODO: Add attribution instead of uid. + optional int32 uid = 1; + + // Type of wakelock. + enum Type { + PARTIAL = 0; + FULL = 1; + WINDOW = 2; + } + optional int32 type = 2; + + enum State { + OFF = 0; + ON = 1; + } + optional State state = 3; +} + +/** + * Logs when a partial wakelock is considered 'long' (over 1 min). + * + * Logged from: + * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java + */ +message LongPartialWakelockStateChanged { + // TODO: Add attribution instead of uid? + optional int32 uid = 1; + // The wakelock tag (Called tag in the Java API, sometimes name elsewhere). optional string tag = 2; - // TODO: Use a constant instead of boolean? - optional bool state = 3; + // TODO: I have no idea what this is. + optional string history_tag = 3; + + enum State { + OFF = 0; + ON = 1; + } + optional State state = 4; +} + +/** + * Logs Battery Saver state change. + * + * Logged from: + * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java + */ +message BatterySaverModeStateChanged { + enum State { + OFF = 0; + ON = 1; + } + optional State state = 1; +} + +/** + * Logs Doze mode state change. + * + * Logged from: + * frameworks/base/services/core/java/com/android/server/am/BatteryStatsService.java + */ +message DeviceIdleModeStateChanged { + // TODO: Use the enum matching BatteryStats.DEVICE_IDLE_MODE_. + optional int32 state = 1; } +/** + * Logs screen brightness level. + * + * Logged from: + * frameworks/base/services/core/java/com/android/server/am/BatteryStatsService.java + */ +message ScreenBrightnessChanged { + // Screen brightness level. Should be in [-1, 255] according to PowerManager.java. + optional int32 level = 1; +} + +/** + * Logs battery level (percent full, from 0 to 100). + * + * Logged from: + * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java + */ +message BatteryLevelChanged { + // Battery level. Should be in [0, 100]. + optional int32 battery_level = 1; +} + +/** + * Logs change in charging status of the device. + * + * Logged from: + * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java + */ +message ChargingStateChanged { + // TODO: Link directly to BatteryManager.java's constants (via a proto). + enum State { + BATTERY_STATUS_UNKNOWN = 1; + BATTERY_STATUS_CHARGING = 2; + BATTERY_STATUS_DISCHARGING = 3; + BATTERY_STATUS_NOT_CHARGING = 4; + BATTERY_STATUS_FULL = 5; + } + optional State charging_state = 1; +} + +/** + * Logs whether the device is plugged in, and what power source it is using. + * + * Logged from: + * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java + */ +message PluggedStateChanged { + // TODO: Link directly to BatteryManager.java's constants (via a proto). + enum State { + // Note that NONE is not in BatteryManager.java's constants. + BATTERY_PLUGGED_NONE = 0; + // Power source is an AC charger. + BATTERY_PLUGGED_AC = 1; + // Power source is a USB port. + BATTERY_PLUGGED_USB = 2; + // Power source is wireless. + BATTERY_PLUGGED_WIRELESS = 4; + } + optional State plugged_state = 1; +} + +/** + * Logs the temperature of the device, in tenths of a degree Celsius. + * + * Logged from: + * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java + */ +message DeviceTemperatureReported { + // Temperature in tenths of a degree C. + optional int32 temperature = 1; +} + +// TODO: Define this more precisely. +// TODO: Log the ON state somewhere. It isn't currently logged anywhere. +/** + * Logs when the device turns off or on. + * + * Logged from: + * frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java + */ +message DeviceOnStatusChanged { + enum State { + OFF = 0; + ON = 1; + } + optional State state = 1; +} + +/** + * Logs when an app's wakeup alarm fires. + * + * Logged from: + * frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java + */ +message WakeupAlarmOccurred { + // TODO: Add attribution instead of uid? + optional int32 uid = 1; +} + +/** + * Logs kernel wakeup reasons and aborts. + * + * Logged from: + * frameworks/base/services/core/java/com/android/server/am/BatteryStatsService.java + */ +message KernelWakeupReported { + // Name of the kernel wakeup reason (or abort). + optional string wakeup_reason_name = 1; + + // Duration (in microseconds) for the wake-up interrupt to be serviced. + optional int64 duration_usec = 2; +} + +/** + * Logs wifi locks held by an app. + * + * Logged from: + * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java + */ +message WifiLockStateChanged { + // TODO: Add attribution instead of uid. + optional int32 uid = 1; + + enum State { + OFF = 0; + ON = 1; + } + optional State state = 2; +} + +/** + * Logs wifi signal strength changes. + * + * Logged from: + * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java + */ +message WifiSignalStrengthChanged { + // TODO: Reference the actual telephony/java/android/telephony/SignalStrength.java states. + enum SignalStrength { + SIGNAL_STRENGTH_NONE_OR_UNKNOWN = 0; + SIGNAL_STRENGTH_POOR = 1; + SIGNAL_STRENGTH_MODERATE = 2; + SIGNAL_STRENGTH_GOOD = 3; + SIGNAL_STRENGTH_GREAT = 4; + } + optional SignalStrength signal_strength = 1; +} + +/** + * Logs wifi scans performed by an app. + * + * Logged from: + * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java + */ +message WifiScanStateChanged { + // TODO: Add attribution instead of uid. + optional int32 uid = 1; + + enum State { + OFF = 0; + ON = 1; + } + optional State state = 2; +} + +/** + * Logs phone signal strength changes. + * + * Logged from: + * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java + */ +message PhoneSignalStrengthChanged { + // TODO: Reference the actual telephony/java/android/telephony/SignalStrength.java states. + enum SignalStrength { + SIGNAL_STRENGTH_NONE_OR_UNKNOWN = 0; + SIGNAL_STRENGTH_POOR = 1; + SIGNAL_STRENGTH_MODERATE = 2; + SIGNAL_STRENGTH_GOOD = 3; + SIGNAL_STRENGTH_GREAT = 4; + } + optional SignalStrength signal_strength = 1; +}
\ No newline at end of file diff --git a/cmds/statsd/src/stats_util.h b/cmds/statsd/src/stats_util.h index 575588b3ba72..39c1d59c88b7 100644 --- a/cmds/statsd/src/stats_util.h +++ b/cmds/statsd/src/stats_util.h @@ -30,6 +30,14 @@ namespace statsd { #define MATCHER_NOT_FOUND -2 #define NANO_SECONDS_IN_A_SECOND (1000 * 1000 * 1000) +// TODO: Remove the following constants once they are exposed in ProtOutputStream.h +const uint64_t FIELD_TYPE_SHIFT = 32; +const uint64_t TYPE_MESSAGE = 11ULL << FIELD_TYPE_SHIFT; +const uint64_t TYPE_INT64 = 3ULL << FIELD_TYPE_SHIFT; +const uint64_t TYPE_INT32 = 5ULL << FIELD_TYPE_SHIFT; +const uint64_t TYPE_FLOAT = 2ULL << FIELD_TYPE_SHIFT; +const uint64_t TYPE_STRING = 9ULL << FIELD_TYPE_SHIFT; + typedef std::string HashableDimensionKey; EventMetricData parse(log_msg msg); diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto index afb3f2b0db25..113ac62699d2 100644 --- a/cmds/statsd/src/statsd_config.proto +++ b/cmds/statsd/src/statsd_config.proto @@ -114,7 +114,7 @@ message Alert { optional int32 refractory_period_secs = 4; - optional int64 trigger_if_gt = 5; + optional int64 trigger_if_sum_gt = 5; } message EventMetric { diff --git a/config/compiled-classes-phone b/config/compiled-classes-phone index f32c0d68c851..47e148df1023 100644 --- a/config/compiled-classes-phone +++ b/config/compiled-classes-phone @@ -709,9 +709,9 @@ android.bluetooth.BluetoothHeadset$1 android.bluetooth.BluetoothHeadset$2 android.bluetooth.BluetoothHeadset$3 android.bluetooth.BluetoothHealthAppConfiguration -android.bluetooth.BluetoothInputDevice -android.bluetooth.BluetoothInputDevice$1 -android.bluetooth.BluetoothInputDevice$2 +android.bluetooth.BluetoothHidHost +android.bluetooth.BluetoothHidHost$1 +android.bluetooth.BluetoothHidHost$2 android.bluetooth.BluetoothInputStream android.bluetooth.BluetoothManager android.bluetooth.BluetoothMap @@ -754,9 +754,9 @@ android.bluetooth.IBluetoothHeadsetPhone$Stub$Proxy android.bluetooth.IBluetoothHealth android.bluetooth.IBluetoothHealth$Stub android.bluetooth.IBluetoothHealthCallback -android.bluetooth.IBluetoothInputDevice -android.bluetooth.IBluetoothInputDevice$Stub -android.bluetooth.IBluetoothInputDevice$Stub$Proxy +android.bluetooth.IBluetoothHidHost +android.bluetooth.IBluetoothHidHost$Stub +android.bluetooth.IBluetoothHidHost$Stub$Proxy android.bluetooth.IBluetoothManager android.bluetooth.IBluetoothManager$Stub android.bluetooth.IBluetoothManager$Stub$Proxy diff --git a/core/java/Android.bp b/core/java/Android.bp new file mode 100644 index 000000000000..42b0f6bad0ae --- /dev/null +++ b/core/java/Android.bp @@ -0,0 +1,4 @@ +filegroup { + name: "IKeyAttestationApplicationIdProvider.aidl", + srcs: ["android/security/keymaster/IKeyAttestationApplicationIdProvider.aidl"], +} diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index fc4c8d7f0666..8bc2073c3cda 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -46,6 +46,7 @@ import android.graphics.Matrix; import android.graphics.Point; import android.graphics.Rect; import android.os.BatteryStats; +import android.os.Binder; import android.os.Build; import android.os.Build.VERSION_CODES; import android.os.Bundle; @@ -500,7 +501,7 @@ public class ActivityManager { public static final int PROCESS_STATE_SERVICE = 11; /** @hide Process is in the background running a receiver. Note that from the - * perspective of oom_adj receivers run at a higher foreground level, but for our + * perspective of oom_adj, receivers run at a higher foreground level, but for our * prioritization here that is not necessary and putting them below services means * many fewer changes in some process states as they receive broadcasts. */ public static final int PROCESS_STATE_RECEIVER = 12; @@ -524,6 +525,20 @@ public class ActivityManager { /** @hide Process does not exist. */ public static final int PROCESS_STATE_NONEXISTENT = 18; + // NOTE: If PROCESS_STATEs are added or changed, then new fields must be added + // to frameworks/base/core/proto/android/app/activitymanager.proto and the following method must + // be updated to correctly map between them. + /** + * Maps ActivityManager.PROCESS_STATE_ values to ActivityManagerProto.ProcessState enum. + * + * @param amInt a process state of the form ActivityManager.PROCESS_STATE_ + * @return the value of the corresponding android.app.ActivityManagerProto's ProcessState enum. + * @hide + */ + public static final int processStateAmToProto(int amInt) { + return amInt * 100; + } + /** @hide The lowest process state number */ public static final int MIN_PROCESS_STATE = PROCESS_STATE_PERSISTENT; @@ -1596,6 +1611,9 @@ public class ActivityManager { public List<RecentTaskInfo> getRecentTasks(int maxNum, int flags) throws SecurityException { try { + if (maxNum < 0) { + throw new IllegalArgumentException("The requested number of tasks should be >= 0"); + } return getService().getRecentTasks(maxNum, flags, UserHandle.myUserId()).getList(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -3882,21 +3900,36 @@ public class ActivityManager { IBinder service = ServiceManager.checkService(name); if (service == null) { pw.println(" (Service not found)"); + pw.flush(); return; } - TransferPipe tp = null; - try { - pw.flush(); - tp = new TransferPipe(); - tp.setBufferPrefix(" "); - service.dumpAsync(tp.getWriteFd().getFileDescriptor(), args); - tp.go(fd, 10000); - } catch (Throwable e) { - if (tp != null) { - tp.kill(); + pw.flush(); + if (service instanceof Binder) { + // If this is a local object, it doesn't make sense to do an async dump with it, + // just directly dump. + try { + service.dump(fd, args); + } catch (Throwable e) { + pw.println("Failure dumping service:"); + e.printStackTrace(pw); + pw.flush(); + } + } else { + // Otherwise, it is remote, do the dump asynchronously to avoid blocking. + TransferPipe tp = null; + try { + pw.flush(); + tp = new TransferPipe(); + tp.setBufferPrefix(" "); + service.dumpAsync(tp.getWriteFd().getFileDescriptor(), args); + tp.go(fd, 10000); + } catch (Throwable e) { + if (tp != null) { + tp.kill(); + } + pw.println("Failure dumping service:"); + e.printStackTrace(pw); } - pw.println("Failure dumping service:"); - e.printStackTrace(pw); } } diff --git a/core/java/android/app/AlarmManager.java b/core/java/android/app/AlarmManager.java index 2813e8b9707e..55f9e289f52d 100644 --- a/core/java/android/app/AlarmManager.java +++ b/core/java/android/app/AlarmManager.java @@ -33,6 +33,7 @@ import android.os.WorkSource; import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; +import android.util.proto.ProtoOutputStream; import libcore.util.ZoneInfoDB; @@ -48,7 +49,7 @@ import java.lang.annotation.RetentionPolicy; * if it is not already running. Registered alarms are retained while the * device is asleep (and can optionally wake the device up if they go off * during that time), but will be cleared if it is turned off and rebooted. - * + * * <p>The Alarm Manager holds a CPU wake lock as long as the alarm receiver's * onReceive() method is executing. This guarantees that the phone will not sleep * until you have finished handling the broadcast. Once onReceive() returns, the @@ -296,7 +297,7 @@ public class AlarmManager { * {@link Intent#EXTRA_ALARM_COUNT Intent.EXTRA_ALARM_COUNT} that indicates * how many past alarm events have been accumulated into this intent * broadcast. Recurring alarms that have gone undelivered because the - * phone was asleep may have a count greater than one when delivered. + * phone was asleep may have a count greater than one when delivered. * * <div class="note"> * <p> @@ -396,10 +397,10 @@ public class AlarmManager { * set a recurring alarm for the top of every hour but the phone was asleep * from 7:45 until 8:45, an alarm will be sent as soon as the phone awakens, * then the next alarm will be sent at 9:00. - * - * <p>If your application wants to allow the delivery times to drift in + * + * <p>If your application wants to allow the delivery times to drift in * order to guarantee that at least a certain time interval always elapses - * between alarms, then the approach to take is to use one-time alarms, + * between alarms, then the approach to take is to use one-time alarms, * scheduling the next one yourself when handling each alarm delivery. * * <p class="note"> @@ -1056,7 +1057,7 @@ public class AlarmManager { /** * Creates a new alarm clock description. * - * @param triggerTime time at which the underlying alarm is triggered in wall time + * @param triggerTime time at which the underlying alarm is triggered in wall time * milliseconds since the epoch * @param showIntent an intent that can be used to show or edit details of * the alarm clock. @@ -1089,7 +1090,7 @@ public class AlarmManager { * Returns an intent that can be used to show or edit details of the alarm clock in * the application that scheduled it. * - * <p class="note">Beware that any application can retrieve and send this intent, + * <p class="note">Beware that any application can retrieve and send this intent, * potentially with additional fields filled in. See * {@link PendingIntent#send(android.content.Context, int, android.content.Intent) * PendingIntent.send()} and {@link android.content.Intent#fillIn Intent.fillIn()} @@ -1121,5 +1122,13 @@ public class AlarmManager { return new AlarmClockInfo[size]; } }; + + /** @hide */ + public void writeToProto(ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + proto.write(AlarmClockInfoProto.TRIGGER_TIME_MS, mTriggerTime); + mShowIntent.writeToProto(proto, AlarmClockInfoProto.SHOW_INTENT); + proto.end(token); + } } } diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java index a25c2267214e..baeabc39e3fb 100644 --- a/core/java/android/app/PendingIntent.java +++ b/core/java/android/app/PendingIntent.java @@ -33,6 +33,7 @@ import android.os.Parcelable; import android.os.RemoteException; import android.os.UserHandle; import android.util.AndroidException; +import android.util.proto.ProtoOutputStream; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -1081,7 +1082,16 @@ public final class PendingIntent implements Parcelable { sb.append('}'); return sb.toString(); } - + + /** @hide */ + public void writeToProto(ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + if (mTarget != null) { + proto.write(PendingIntentProto.TARGET, mTarget.asBinder().toString()); + } + proto.end(token); + } + public int describeContents() { return 0; } diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 3c5306331716..ab8edee724f2 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -6533,6 +6533,52 @@ public class DevicePolicyManager { } /** + * Called by device owner to set the system wall clock time. This only takes effect if called + * when {@link android.provider.Settings.Global#AUTO_TIME} is 0, otherwise {@code false} will be + * returned. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with + * @param millis time in milliseconds since the Epoch + * @return {@code true} if set time succeeded, {@code false} otherwise. + * @throws SecurityException if {@code admin} is not a device owner. + */ + public boolean setTime(@NonNull ComponentName admin, long millis) { + throwIfParentInstance("setTime"); + if (mService != null) { + try { + return mService.setTime(admin, millis); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + return false; + } + + /** + * Called by device owner to set the system's persistent default time zone. This only takes + * effect if called when {@link android.provider.Settings.Global#AUTO_TIME_ZONE} is 0, otherwise + * {@code false} will be returned. + * + * @see android.app.AlarmManager#setTimeZone(String) + * @param admin Which {@link DeviceAdminReceiver} this request is associated with + * @param timeZone one of the Olson ids from the list returned by + * {@link java.util.TimeZone#getAvailableIDs} + * @return {@code true} if set timezone succeeded, {@code false} otherwise. + * @throws SecurityException if {@code admin} is not a device owner. + */ + public boolean setTimeZone(@NonNull ComponentName admin, String timeZone) { + throwIfParentInstance("setTimeZone"); + if (mService != null) { + try { + return mService.setTimeZone(admin, timeZone); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + return false; + } + + /** * Called by profile or device owners to update {@link android.provider.Settings.Secure} * settings. Validation that the value of the setting is in the correct form for the setting * type should be performed by the caller. diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 8865a0525292..e77c18636594 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -229,6 +229,9 @@ interface IDevicePolicyManager { void setGlobalSetting(in ComponentName who, in String setting, in String value); void setSecureSetting(in ComponentName who, in String setting, in String value); + boolean setTime(in ComponentName who, long millis); + boolean setTimeZone(in ComponentName who, String timeZone); + void setMasterVolumeMuted(in ComponentName admin, boolean on); boolean isMasterVolumeMuted(in ComponentName admin); diff --git a/core/java/android/app/job/JobInfo.java b/core/java/android/app/job/JobInfo.java index 1434c9baadf3..1cde73a0af61 100644 --- a/core/java/android/app/job/JobInfo.java +++ b/core/java/android/app/job/JobInfo.java @@ -909,12 +909,21 @@ public class JobInfo implements Parcelable { } /** - * Set some description of the kind of network type your job needs to have. - * Not calling this function means the network is not necessary, as the default is - * {@link #NETWORK_TYPE_NONE}. - * Bear in mind that calling this function defines network as a strict requirement for your - * job. If the network requested is not available your job will never run. See - * {@link #setOverrideDeadline(long)} to change this behaviour. + * Set some description of the kind of network type your job needs to + * have. Not calling this function means the network is not necessary, + * as the default is {@link #NETWORK_TYPE_NONE}. Bear in mind that + * calling this function defines network as a strict requirement for + * your job. If the network requested is not available your job will + * never run. See {@link #setOverrideDeadline(long)} to change this + * behaviour. + * <p class="note"> + * Note: When your job executes in + * {@link JobService#onStartJob(JobParameters)}, be sure to use the + * specific network returned by {@link JobParameters#getNetwork()}, + * otherwise you'll use the default network which may not meet this + * constraint. + * + * @see JobParameters#getNetwork() */ public Builder setRequiredNetworkType(@NetworkType int networkType) { mNetworkType = networkType; diff --git a/core/java/android/app/job/JobParameters.java b/core/java/android/app/job/JobParameters.java index a6f6be22809c..5053dc6fdf05 100644 --- a/core/java/android/app/job/JobParameters.java +++ b/core/java/android/app/job/JobParameters.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.job.IJobCallback; import android.content.ClipData; +import android.net.Network; import android.net.Uri; import android.os.Bundle; import android.os.IBinder; @@ -66,6 +67,7 @@ public class JobParameters implements Parcelable { private final boolean overrideDeadlineExpired; private final Uri[] mTriggeredContentUris; private final String[] mTriggeredContentAuthorities; + private final Network network; private int stopReason; // Default value of stopReason is REASON_CANCELED @@ -73,7 +75,7 @@ public class JobParameters implements Parcelable { public JobParameters(IBinder callback, int jobId, PersistableBundle extras, Bundle transientExtras, ClipData clipData, int clipGrantFlags, boolean overrideDeadlineExpired, Uri[] triggeredContentUris, - String[] triggeredContentAuthorities) { + String[] triggeredContentAuthorities, Network network) { this.jobId = jobId; this.extras = extras; this.transientExtras = transientExtras; @@ -83,6 +85,7 @@ public class JobParameters implements Parcelable { this.overrideDeadlineExpired = overrideDeadlineExpired; this.mTriggeredContentUris = triggeredContentUris; this.mTriggeredContentAuthorities = triggeredContentAuthorities; + this.network = network; } /** @@ -171,6 +174,28 @@ public class JobParameters implements Parcelable { } /** + * Return the network that should be used to perform any network requests + * for this job. + * <p> + * Devices may have multiple active network connections simultaneously, or + * they may not have a default network route at all. To correctly handle all + * situations like this, your job should always use the network returned by + * this method instead of implicitly using the default network route. + * <p> + * Note that the system may relax the constraints you originally requested, + * such as allowing a {@link JobInfo#NETWORK_TYPE_UNMETERED} job to run over + * a metered network when there is a surplus of metered data available. + * + * @return the network that should be used to perform any network requests + * for this job, or {@code null} if this job didn't set any required + * network type. + * @see JobInfo.Builder#setRequiredNetworkType(int) + */ + public @Nullable Network getNetwork() { + return network; + } + + /** * Dequeue the next pending {@link JobWorkItem} from these JobParameters associated with their * currently running job. Calling this method when there is no more work available and all * previously dequeued work has been completed will result in the system taking care of @@ -257,6 +282,11 @@ public class JobParameters implements Parcelable { overrideDeadlineExpired = in.readInt() == 1; mTriggeredContentUris = in.createTypedArray(Uri.CREATOR); mTriggeredContentAuthorities = in.createStringArray(); + if (in.readInt() != 0) { + network = Network.CREATOR.createFromParcel(in); + } else { + network = null; + } stopReason = in.readInt(); } @@ -286,6 +316,12 @@ public class JobParameters implements Parcelable { dest.writeInt(overrideDeadlineExpired ? 1 : 0); dest.writeTypedArray(mTriggeredContentUris, flags); dest.writeStringArray(mTriggeredContentAuthorities); + if (network != null) { + dest.writeInt(1); + network.writeToParcel(dest, flags); + } else { + dest.writeInt(0); + } dest.writeInt(stopReason); } diff --git a/core/java/android/app/slice/SliceProvider.java b/core/java/android/app/slice/SliceProvider.java index df87b4556ee5..33825b4b5f41 100644 --- a/core/java/android/app/slice/SliceProvider.java +++ b/core/java/android/app/slice/SliceProvider.java @@ -156,27 +156,34 @@ public abstract class SliceProvider extends ContentProvider { } private Slice handleBindSlice(Uri sliceUri) { - Slice[] output = new Slice[1]; - CountDownLatch latch = new CountDownLatch(1); - Handler mainHandler = new Handler(Looper.getMainLooper()); - mainHandler.post(() -> { - ThreadPolicy oldPolicy = StrictMode.getThreadPolicy(); - try { - StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() - .detectAll() - .penaltyDeath() - .build()); - output[0] = onBindSlice(sliceUri); - } finally { - StrictMode.setThreadPolicy(oldPolicy); + if (Looper.myLooper() == Looper.getMainLooper()) { + return onBindSliceStrict(sliceUri); + } else { + CountDownLatch latch = new CountDownLatch(1); + Slice[] output = new Slice[1]; + Handler.getMain().post(() -> { + output[0] = onBindSliceStrict(sliceUri); latch.countDown(); + }); + try { + latch.await(); + return output[0]; + } catch (InterruptedException e) { + throw new RuntimeException(e); } - }); + } + } + + private Slice onBindSliceStrict(Uri sliceUri) { + ThreadPolicy oldPolicy = StrictMode.getThreadPolicy(); try { - latch.await(); - return output[0]; - } catch (InterruptedException e) { - throw new RuntimeException(e); + StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() + .detectAll() + .penaltyDeath() + .build()); + return onBindSlice(sliceUri); + } finally { + StrictMode.setThreadPolicy(oldPolicy); } } } diff --git a/core/java/android/app/usage/AppStandby.java b/core/java/android/app/usage/AppStandby.java new file mode 100644 index 000000000000..6f9fc2fa5d36 --- /dev/null +++ b/core/java/android/app/usage/AppStandby.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2017 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.app.usage; + +import android.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Set of constants for app standby buckets and reasons. Apps will be moved into different buckets + * that affect how frequently they can run in the background or perform other battery-consuming + * actions. Buckets will be assigned based on how frequently or when the system thinks the user + * is likely to use the app. + * @hide + */ +public class AppStandby { + + /** The app was used very recently, currently in use or likely to be used very soon. */ + public static final int STANDBY_BUCKET_ACTIVE = 0; + + // Leave some gap in case we want to increase the number of buckets + + /** The app was used recently and/or likely to be used in the next few hours */ + public static final int STANDBY_BUCKET_WORKING_SET = 3; + + // Leave some gap in case we want to increase the number of buckets + + /** The app was used in the last few days and/or likely to be used in the next few days */ + public static final int STANDBY_BUCKET_FREQUENT = 6; + + // Leave some gap in case we want to increase the number of buckets + + /** The app has not be used for several days and/or is unlikely to be used for several days */ + public static final int STANDBY_BUCKET_RARE = 9; + + // Leave some gap in case we want to increase the number of buckets + + /** The app has never been used. */ + public static final int STANDBY_BUCKET_NEVER = 12; + + /** Reason for bucketing -- default initial state */ + public static final String REASON_DEFAULT = "default"; + + /** Reason for bucketing -- timeout */ + public static final String REASON_TIMEOUT = "timeout"; + + /** Reason for bucketing -- usage */ + public static final String REASON_USAGE = "usage"; + + /** Reason for bucketing -- forced by user / shell command */ + public static final String REASON_FORCED = "forced"; + + /** + * Reason for bucketing -- predicted. This is a prefix and the UID of the bucketeer will + * be appended. + */ + public static final String REASON_PREDICTED = "predicted"; + + @IntDef(flag = false, value = { + STANDBY_BUCKET_ACTIVE, + STANDBY_BUCKET_WORKING_SET, + STANDBY_BUCKET_FREQUENT, + STANDBY_BUCKET_RARE, + STANDBY_BUCKET_NEVER, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface StandbyBuckets {} +} diff --git a/core/java/android/app/usage/IUsageStatsManager.aidl b/core/java/android/app/usage/IUsageStatsManager.aidl index 31b235977a04..4fbbdf2a9281 100644 --- a/core/java/android/app/usage/IUsageStatsManager.aidl +++ b/core/java/android/app/usage/IUsageStatsManager.aidl @@ -36,4 +36,6 @@ interface IUsageStatsManager { void onCarrierPrivilegedAppsChanged(); void reportChooserSelection(String packageName, int userId, String contentType, in String[] annotations, String action); + int getAppStandbyBucket(String packageName, String callingPackage, int userId); + void setAppStandbyBucket(String packageName, int bucket, int userId); } diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java index fd579fce34d8..c827432a8b0b 100644 --- a/core/java/android/app/usage/UsageStatsManager.java +++ b/core/java/android/app/usage/UsageStatsManager.java @@ -19,6 +19,7 @@ package android.app.usage; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; +import android.app.usage.AppStandby.StandbyBuckets; import android.content.Context; import android.content.pm.ParceledListSlice; import android.os.RemoteException; @@ -247,6 +248,29 @@ public final class UsageStatsManager { } /** + * @hide + */ + public @StandbyBuckets int getAppStandbyBucket(String packageName) { + try { + return mService.getAppStandbyBucket(packageName, mContext.getOpPackageName(), + mContext.getUserId()); + } catch (RemoteException e) { + } + return AppStandby.STANDBY_BUCKET_ACTIVE; + } + + /** + * @hide + */ + public void setAppStandbyBucket(String packageName, @StandbyBuckets int bucket) { + try { + mService.setAppStandbyBucket(packageName, bucket, mContext.getUserId()); + } catch (RemoteException e) { + // Nothing to do + } + } + + /** * {@hide} * Temporarily whitelist the specified app for a short duration. This is to allow an app * receiving a high priority message to be able to access the network and acquire wakelocks diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java index dc9970a7ca42..ab0eb92e1726 100644 --- a/core/java/android/appwidget/AppWidgetHostView.java +++ b/core/java/android/appwidget/AppWidgetHostView.java @@ -19,8 +19,6 @@ package android.appwidget; import android.content.ComponentName; import android.content.Context; import android.content.pm.ApplicationInfo; -import android.content.pm.LauncherApps; -import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Resources; import android.graphics.Color; @@ -66,11 +64,8 @@ public class AppWidgetHostView extends FrameLayout { // When we're inflating the initialLayout for a AppWidget, we only allow // views that are allowed in RemoteViews. - static final LayoutInflater.Filter sInflaterFilter = new LayoutInflater.Filter() { - public boolean onLoadClass(Class clazz) { - return clazz.isAnnotationPresent(RemoteViews.RemoteView.class); - } - }; + private static final LayoutInflater.Filter INFLATER_FILTER = + (clazz) -> clazz.isAnnotationPresent(RemoteViews.RemoteView.class); Context mContext; Context mRemoteContext; @@ -136,13 +131,19 @@ public class AppWidgetHostView extends FrameLayout { mAppWidgetId = appWidgetId; mInfo = info; + // We add padding to the AppWidgetHostView if necessary + Rect padding = getDefaultPadding(); + setPadding(padding.left, padding.top, padding.right, padding.bottom); + // Sometimes the AppWidgetManager returns a null AppWidgetProviderInfo object for // a widget, eg. for some widgets in safe mode. if (info != null) { - // We add padding to the AppWidgetHostView if necessary - Rect padding = getDefaultPaddingForWidget(mContext, info.provider, null); - setPadding(padding.left, padding.top, padding.right, padding.bottom); - updateContentDescription(info); + String description = info.loadLabel(getContext().getPackageManager()); + if ((info.providerInfo.applicationInfo.flags & ApplicationInfo.FLAG_SUSPENDED) != 0) { + description = Resources.getSystem().getString( + com.android.internal.R.string.suspended_widget_accessibility, description); + } + setContentDescription(description); } } @@ -164,23 +165,23 @@ public class AppWidgetHostView extends FrameLayout { */ public static Rect getDefaultPaddingForWidget(Context context, ComponentName component, Rect padding) { - PackageManager packageManager = context.getPackageManager(); - ApplicationInfo appInfo; + ApplicationInfo appInfo = null; + try { + appInfo = context.getPackageManager().getApplicationInfo(component.getPackageName(), 0); + } catch (NameNotFoundException e) { + // if we can't find the package, ignore + } + return getDefaultPaddingForWidget(context, appInfo, padding); + } + private static Rect getDefaultPaddingForWidget(Context context, ApplicationInfo appInfo, + Rect padding) { if (padding == null) { padding = new Rect(0, 0, 0, 0); } else { padding.set(0, 0, 0, 0); } - - try { - appInfo = packageManager.getApplicationInfo(component.getPackageName(), 0); - } catch (NameNotFoundException e) { - // if we can't find the package, return 0 padding - return padding; - } - - if (appInfo.targetSdkVersion >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + if (appInfo != null && appInfo.targetSdkVersion >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { Resources r = context.getResources(); padding.left = r.getDimensionPixelSize(com.android.internal. R.dimen.default_app_widget_padding_left); @@ -194,6 +195,11 @@ public class AppWidgetHostView extends FrameLayout { return padding; } + private Rect getDefaultPadding() { + return getDefaultPaddingForWidget(mContext, + mInfo == null ? null : mInfo.providerInfo.applicationInfo, null); + } + public int getAppWidgetId() { return mAppWidgetId; } @@ -284,10 +290,7 @@ public class AppWidgetHostView extends FrameLayout { newOptions = new Bundle(); } - Rect padding = new Rect(); - if (mInfo != null) { - padding = getDefaultPaddingForWidget(mContext, mInfo.provider, padding); - } + Rect padding = getDefaultPadding(); float density = getResources().getDisplayMetrics().density; int xPaddingDips = (int) ((padding.left + padding.right) / density); @@ -361,7 +364,7 @@ public class AppWidgetHostView extends FrameLayout { * initial layout. */ void resetAppWidget(AppWidgetProviderInfo info) { - mInfo = info; + setAppWidget(mAppWidgetId, info); mViewMode = VIEW_MODE_NOINIT; updateAppWidget(null); } @@ -433,7 +436,6 @@ public class AppWidgetHostView extends FrameLayout { } applyContent(content, recycled, exception); - updateContentDescription(mInfo); } private void applyContent(View content, boolean recycled, Exception exception) { @@ -460,27 +462,6 @@ public class AppWidgetHostView extends FrameLayout { } } - private void updateContentDescription(AppWidgetProviderInfo info) { - if (info != null) { - LauncherApps launcherApps = getContext().getSystemService(LauncherApps.class); - ApplicationInfo appInfo = null; - try { - appInfo = launcherApps.getApplicationInfo( - info.provider.getPackageName(), 0, info.getProfile()); - } catch (NameNotFoundException e) { - // ignore -- use null. - } - if (appInfo != null && - (appInfo.flags & ApplicationInfo.FLAG_SUSPENDED) != 0) { - setContentDescription( - Resources.getSystem().getString( - com.android.internal.R.string.suspended_widget_accessibility, info.label)); - } else { - setContentDescription(info.label); - } - } - } - private void inflateAsync(RemoteViews remoteViews) { // Prepare a local reference to the remote Context so we're ready to // inflate any requested LayoutParams. @@ -614,7 +595,7 @@ public class AppWidgetHostView extends FrameLayout { LayoutInflater inflater = (LayoutInflater) theirContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); inflater = inflater.cloneInContext(theirContext); - inflater.setFilter(sInflaterFilter); + inflater.setFilter(INFLATER_FILTER); AppWidgetManager manager = AppWidgetManager.getInstance(mContext); Bundle options = manager.getAppWidgetOptions(mAppWidgetId); diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index 3526e1896621..578a5b8b93e1 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -1134,8 +1134,32 @@ public final class BluetoothAdapter { } /** - * Sets the {@link BluetoothClass} Bluetooth Class of Device (CoD) of - * the local Bluetooth adapter. + * Returns the {@link BluetoothClass} Bluetooth Class of Device (CoD) of the local Bluetooth + * adapter. + * + * @return {@link BluetoothClass} Bluetooth CoD of local Bluetooth device. + * + * @hide + */ + @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) + public BluetoothClass getBluetoothClass() { + if (getState() != STATE_ON) return null; + try { + mServiceLock.readLock().lock(); + if (mService != null) return mService.getBluetoothClass(); + } catch (RemoteException e) { + Log.e(TAG, "", e); + } finally { + mServiceLock.readLock().unlock(); + } + return null; + } + + /** + * Sets the {@link BluetoothClass} Bluetooth Class of Device (CoD) of the local Bluetooth + * adapter. + * + * <p>Note: This value persists across system reboot. * * @param bluetoothClass {@link BluetoothClass} to set the local Bluetooth adapter to. * @return true if successful, false if unsuccessful. @@ -2104,8 +2128,8 @@ public final class BluetoothAdapter { } else if (profile == BluetoothProfile.AVRCP_CONTROLLER) { BluetoothAvrcpController avrcp = new BluetoothAvrcpController(context, listener); return true; - } else if (profile == BluetoothProfile.INPUT_DEVICE) { - BluetoothInputDevice iDev = new BluetoothInputDevice(context, listener); + } else if (profile == BluetoothProfile.HID_HOST) { + BluetoothHidHost iDev = new BluetoothHidHost(context, listener); return true; } else if (profile == BluetoothProfile.PAN) { BluetoothPan pan = new BluetoothPan(context, listener); @@ -2128,8 +2152,8 @@ public final class BluetoothAdapter { } else if (profile == BluetoothProfile.MAP_CLIENT) { BluetoothMapClient mapClient = new BluetoothMapClient(context, listener); return true; - } else if (profile == BluetoothProfile.INPUT_HOST) { - BluetoothInputHost iHost = new BluetoothInputHost(context, listener); + } else if (profile == BluetoothProfile.HID_DEVICE) { + BluetoothHidDevice hidDevice = new BluetoothHidDevice(context, listener); return true; } else { return false; @@ -2167,8 +2191,8 @@ public final class BluetoothAdapter { BluetoothAvrcpController avrcp = (BluetoothAvrcpController) proxy; avrcp.close(); break; - case BluetoothProfile.INPUT_DEVICE: - BluetoothInputDevice iDev = (BluetoothInputDevice) proxy; + case BluetoothProfile.HID_HOST: + BluetoothHidHost iDev = (BluetoothHidHost) proxy; iDev.close(); break; case BluetoothProfile.PAN: @@ -2207,9 +2231,9 @@ public final class BluetoothAdapter { BluetoothMapClient mapClient = (BluetoothMapClient) proxy; mapClient.close(); break; - case BluetoothProfile.INPUT_HOST: - BluetoothInputHost iHost = (BluetoothInputHost) proxy; - iHost.close(); + case BluetoothProfile.HID_DEVICE: + BluetoothHidDevice hidDevice = (BluetoothHidDevice) proxy; + hidDevice.close(); break; } } diff --git a/core/java/android/bluetooth/BluetoothInputHost.java b/core/java/android/bluetooth/BluetoothHidDevice.java index e18d9d1be51b..179f36dab32f 100644 --- a/core/java/android/bluetooth/BluetoothInputHost.java +++ b/core/java/android/bluetooth/BluetoothHidDevice.java @@ -33,9 +33,9 @@ import java.util.List; /** * @hide */ -public final class BluetoothInputHost implements BluetoothProfile { +public final class BluetoothHidDevice implements BluetoothProfile { - private static final String TAG = BluetoothInputHost.class.getSimpleName(); + private static final String TAG = BluetoothHidDevice.class.getSimpleName(); /** * Intent used to broadcast the change in connection state of the Input @@ -57,7 +57,7 @@ public final class BluetoothInputHost implements BluetoothProfile { */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_CONNECTION_STATE_CHANGED = - "android.bluetooth.inputhost.profile.action.CONNECTION_STATE_CHANGED"; + "android.bluetooth.hiddevice.profile.action.CONNECTION_STATE_CHANGED"; /** * Constants representing device subclass. @@ -113,7 +113,7 @@ public final class BluetoothInputHost implements BluetoothProfile { private ServiceListener mServiceListener; - private volatile IBluetoothInputHost mService; + private volatile IBluetoothHidDevice mService; private BluetoothAdapter mAdapter; @@ -205,23 +205,23 @@ public final class BluetoothInputHost implements BluetoothProfile { private final ServiceConnection mConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { Log.d(TAG, "onServiceConnected()"); - mService = IBluetoothInputHost.Stub.asInterface(service); + mService = IBluetoothHidDevice.Stub.asInterface(service); if (mServiceListener != null) { - mServiceListener.onServiceConnected(BluetoothProfile.INPUT_HOST, - BluetoothInputHost.this); + mServiceListener.onServiceConnected(BluetoothProfile.HID_DEVICE, + BluetoothHidDevice.this); } } public void onServiceDisconnected(ComponentName className) { Log.d(TAG, "onServiceDisconnected()"); mService = null; if (mServiceListener != null) { - mServiceListener.onServiceDisconnected(BluetoothProfile.INPUT_HOST); + mServiceListener.onServiceDisconnected(BluetoothProfile.HID_DEVICE); } } }; - BluetoothInputHost(Context context, ServiceListener listener) { - Log.v(TAG, "BluetoothInputHost"); + BluetoothHidDevice(Context context, ServiceListener listener) { + Log.v(TAG, "BluetoothHidDevice"); mContext = context; mServiceListener = listener; @@ -240,7 +240,7 @@ public final class BluetoothInputHost implements BluetoothProfile { } boolean doBind() { - Intent intent = new Intent(IBluetoothInputHost.class.getName()); + Intent intent = new Intent(IBluetoothHidDevice.class.getName()); ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0); intent.setComponent(comp); if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0, @@ -285,7 +285,7 @@ public final class BluetoothInputHost implements BluetoothProfile { public List<BluetoothDevice> getConnectedDevices() { Log.v(TAG, "getConnectedDevices()"); - final IBluetoothInputHost service = mService; + final IBluetoothHidDevice service = mService; if (service != null) { try { return service.getConnectedDevices(); @@ -306,7 +306,7 @@ public final class BluetoothInputHost implements BluetoothProfile { public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { Log.v(TAG, "getDevicesMatchingConnectionStates(): states=" + Arrays.toString(states)); - final IBluetoothInputHost service = mService; + final IBluetoothHidDevice service = mService; if (service != null) { try { return service.getDevicesMatchingConnectionStates(states); @@ -327,7 +327,7 @@ public final class BluetoothInputHost implements BluetoothProfile { public int getConnectionState(BluetoothDevice device) { Log.v(TAG, "getConnectionState(): device=" + device); - final IBluetoothInputHost service = mService; + final IBluetoothHidDevice service = mService; if (service != null) { try { return service.getConnectionState(device); @@ -367,7 +367,7 @@ public final class BluetoothInputHost implements BluetoothProfile { return false; } - final IBluetoothInputHost service = mService; + final IBluetoothHidDevice service = mService; if (service != null) { try { BluetoothHidDeviceAppConfiguration config = @@ -401,7 +401,7 @@ public final class BluetoothInputHost implements BluetoothProfile { boolean result = false; - final IBluetoothInputHost service = mService; + final IBluetoothHidDevice service = mService; if (service != null) { try { result = service.unregisterApp(config); @@ -426,7 +426,7 @@ public final class BluetoothInputHost implements BluetoothProfile { public boolean sendReport(BluetoothDevice device, int id, byte[] data) { boolean result = false; - final IBluetoothInputHost service = mService; + final IBluetoothHidDevice service = mService; if (service != null) { try { result = service.sendReport(device, id, data); @@ -454,7 +454,7 @@ public final class BluetoothInputHost implements BluetoothProfile { boolean result = false; - final IBluetoothInputHost service = mService; + final IBluetoothHidDevice service = mService; if (service != null) { try { result = service.replyReport(device, type, id, data); @@ -480,7 +480,7 @@ public final class BluetoothInputHost implements BluetoothProfile { boolean result = false; - final IBluetoothInputHost service = mService; + final IBluetoothHidDevice service = mService; if (service != null) { try { result = service.reportError(device, error); @@ -504,7 +504,7 @@ public final class BluetoothInputHost implements BluetoothProfile { boolean result = false; - final IBluetoothInputHost service = mService; + final IBluetoothHidDevice service = mService; if (service != null) { try { result = service.unplug(device); @@ -529,7 +529,7 @@ public final class BluetoothInputHost implements BluetoothProfile { boolean result = false; - final IBluetoothInputHost service = mService; + final IBluetoothHidDevice service = mService; if (service != null) { try { result = service.connect(device); @@ -553,7 +553,7 @@ public final class BluetoothInputHost implements BluetoothProfile { boolean result = false; - final IBluetoothInputHost service = mService; + final IBluetoothHidDevice service = mService; if (service != null) { try { result = service.disconnect(device); diff --git a/core/java/android/bluetooth/BluetoothInputDevice.java b/core/java/android/bluetooth/BluetoothHidHost.java index 32615761cf8c..8ad0f9d064fd 100644 --- a/core/java/android/bluetooth/BluetoothInputDevice.java +++ b/core/java/android/bluetooth/BluetoothHidHost.java @@ -35,16 +35,16 @@ import java.util.List; * This class provides the public APIs to control the Bluetooth Input * Device Profile. * - * <p>BluetoothInputDevice is a proxy object for controlling the Bluetooth + * <p>BluetoothHidHost is a proxy object for controlling the Bluetooth * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get - * the BluetoothInputDevice proxy object. + * the BluetoothHidHost proxy object. * * <p>Each method is protected with its appropriate permission. * * @hide */ -public final class BluetoothInputDevice implements BluetoothProfile { - private static final String TAG = "BluetoothInputDevice"; +public final class BluetoothHidHost implements BluetoothProfile { + private static final String TAG = "BluetoothHidHost"; private static final boolean DBG = true; private static final boolean VDBG = false; @@ -177,52 +177,52 @@ public final class BluetoothInputDevice implements BluetoothProfile { * @hide */ public static final String EXTRA_PROTOCOL_MODE = - "android.bluetooth.BluetoothInputDevice.extra.PROTOCOL_MODE"; + "android.bluetooth.BluetoothHidHost.extra.PROTOCOL_MODE"; /** * @hide */ public static final String EXTRA_REPORT_TYPE = - "android.bluetooth.BluetoothInputDevice.extra.REPORT_TYPE"; + "android.bluetooth.BluetoothHidHost.extra.REPORT_TYPE"; /** * @hide */ public static final String EXTRA_REPORT_ID = - "android.bluetooth.BluetoothInputDevice.extra.REPORT_ID"; + "android.bluetooth.BluetoothHidHost.extra.REPORT_ID"; /** * @hide */ public static final String EXTRA_REPORT_BUFFER_SIZE = - "android.bluetooth.BluetoothInputDevice.extra.REPORT_BUFFER_SIZE"; + "android.bluetooth.BluetoothHidHost.extra.REPORT_BUFFER_SIZE"; /** * @hide */ - public static final String EXTRA_REPORT = "android.bluetooth.BluetoothInputDevice.extra.REPORT"; + public static final String EXTRA_REPORT = "android.bluetooth.BluetoothHidHost.extra.REPORT"; /** * @hide */ - public static final String EXTRA_STATUS = "android.bluetooth.BluetoothInputDevice.extra.STATUS"; + public static final String EXTRA_STATUS = "android.bluetooth.BluetoothHidHost.extra.STATUS"; /** * @hide */ public static final String EXTRA_VIRTUAL_UNPLUG_STATUS = - "android.bluetooth.BluetoothInputDevice.extra.VIRTUAL_UNPLUG_STATUS"; + "android.bluetooth.BluetoothHidHost.extra.VIRTUAL_UNPLUG_STATUS"; /** * @hide */ public static final String EXTRA_IDLE_TIME = - "android.bluetooth.BluetoothInputDevice.extra.IDLE_TIME"; + "android.bluetooth.BluetoothHidHost.extra.IDLE_TIME"; private Context mContext; private ServiceListener mServiceListener; private BluetoothAdapter mAdapter; - private volatile IBluetoothInputDevice mService; + private volatile IBluetoothHidHost mService; private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback = new IBluetoothStateChangeCallback.Stub() { @@ -254,10 +254,10 @@ public final class BluetoothInputDevice implements BluetoothProfile { }; /** - * Create a BluetoothInputDevice proxy object for interacting with the local + * Create a BluetoothHidHost proxy object for interacting with the local * Bluetooth Service which handles the InputDevice profile */ - /*package*/ BluetoothInputDevice(Context context, ServiceListener l) { + /*package*/ BluetoothHidHost(Context context, ServiceListener l) { mContext = context; mServiceListener = l; mAdapter = BluetoothAdapter.getDefaultAdapter(); @@ -275,7 +275,7 @@ public final class BluetoothInputDevice implements BluetoothProfile { } boolean doBind() { - Intent intent = new Intent(IBluetoothInputDevice.class.getName()); + Intent intent = new Intent(IBluetoothHidHost.class.getName()); ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0); intent.setComponent(comp); if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0, @@ -331,7 +331,7 @@ public final class BluetoothInputDevice implements BluetoothProfile { */ public boolean connect(BluetoothDevice device) { if (DBG) log("connect(" + device + ")"); - final IBluetoothInputDevice service = mService; + final IBluetoothHidHost service = mService; if (service != null && isEnabled() && isValidDevice(device)) { try { return service.connect(device); @@ -371,7 +371,7 @@ public final class BluetoothInputDevice implements BluetoothProfile { */ public boolean disconnect(BluetoothDevice device) { if (DBG) log("disconnect(" + device + ")"); - final IBluetoothInputDevice service = mService; + final IBluetoothHidHost service = mService; if (service != null && isEnabled() && isValidDevice(device)) { try { return service.disconnect(device); @@ -390,7 +390,7 @@ public final class BluetoothInputDevice implements BluetoothProfile { @Override public List<BluetoothDevice> getConnectedDevices() { if (VDBG) log("getConnectedDevices()"); - final IBluetoothInputDevice service = mService; + final IBluetoothHidHost service = mService; if (service != null && isEnabled()) { try { return service.getConnectedDevices(); @@ -409,7 +409,7 @@ public final class BluetoothInputDevice implements BluetoothProfile { @Override public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { if (VDBG) log("getDevicesMatchingStates()"); - final IBluetoothInputDevice service = mService; + final IBluetoothHidHost service = mService; if (service != null && isEnabled()) { try { return service.getDevicesMatchingConnectionStates(states); @@ -428,7 +428,7 @@ public final class BluetoothInputDevice implements BluetoothProfile { @Override public int getConnectionState(BluetoothDevice device) { if (VDBG) log("getState(" + device + ")"); - final IBluetoothInputDevice service = mService; + final IBluetoothHidHost service = mService; if (service != null && isEnabled() && isValidDevice(device)) { try { return service.getConnectionState(device); @@ -458,7 +458,7 @@ public final class BluetoothInputDevice implements BluetoothProfile { */ public boolean setPriority(BluetoothDevice device, int priority) { if (DBG) log("setPriority(" + device + ", " + priority + ")"); - final IBluetoothInputDevice service = mService; + final IBluetoothHidHost service = mService; if (service != null && isEnabled() && isValidDevice(device)) { if (priority != BluetoothProfile.PRIORITY_OFF && priority != BluetoothProfile.PRIORITY_ON) { @@ -490,7 +490,7 @@ public final class BluetoothInputDevice implements BluetoothProfile { */ public int getPriority(BluetoothDevice device) { if (VDBG) log("getPriority(" + device + ")"); - final IBluetoothInputDevice service = mService; + final IBluetoothHidHost service = mService; if (service != null && isEnabled() && isValidDevice(device)) { try { return service.getPriority(device); @@ -506,11 +506,11 @@ public final class BluetoothInputDevice implements BluetoothProfile { private final ServiceConnection mConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { if (DBG) Log.d(TAG, "Proxy object connected"); - mService = IBluetoothInputDevice.Stub.asInterface(Binder.allowBlocking(service)); + mService = IBluetoothHidHost.Stub.asInterface(Binder.allowBlocking(service)); if (mServiceListener != null) { - mServiceListener.onServiceConnected(BluetoothProfile.INPUT_DEVICE, - BluetoothInputDevice.this); + mServiceListener.onServiceConnected(BluetoothProfile.HID_HOST, + BluetoothHidHost.this); } } @@ -518,7 +518,7 @@ public final class BluetoothInputDevice implements BluetoothProfile { if (DBG) Log.d(TAG, "Proxy object disconnected"); mService = null; if (mServiceListener != null) { - mServiceListener.onServiceDisconnected(BluetoothProfile.INPUT_DEVICE); + mServiceListener.onServiceDisconnected(BluetoothProfile.HID_HOST); } } }; @@ -542,7 +542,7 @@ public final class BluetoothInputDevice implements BluetoothProfile { */ public boolean virtualUnplug(BluetoothDevice device) { if (DBG) log("virtualUnplug(" + device + ")"); - final IBluetoothInputDevice service = mService; + final IBluetoothHidHost service = mService; if (service != null && isEnabled() && isValidDevice(device)) { try { return service.virtualUnplug(device); @@ -568,7 +568,7 @@ public final class BluetoothInputDevice implements BluetoothProfile { */ public boolean getProtocolMode(BluetoothDevice device) { if (VDBG) log("getProtocolMode(" + device + ")"); - final IBluetoothInputDevice service = mService; + final IBluetoothHidHost service = mService; if (service != null && isEnabled() && isValidDevice(device)) { try { return service.getProtocolMode(device); @@ -592,7 +592,7 @@ public final class BluetoothInputDevice implements BluetoothProfile { */ public boolean setProtocolMode(BluetoothDevice device, int protocolMode) { if (DBG) log("setProtocolMode(" + device + ")"); - final IBluetoothInputDevice service = mService; + final IBluetoothHidHost service = mService; if (service != null && isEnabled() && isValidDevice(device)) { try { return service.setProtocolMode(device, protocolMode); @@ -623,7 +623,7 @@ public final class BluetoothInputDevice implements BluetoothProfile { log("getReport(" + device + "), reportType=" + reportType + " reportId=" + reportId + "bufferSize=" + bufferSize); } - final IBluetoothInputDevice service = mService; + final IBluetoothHidHost service = mService; if (service != null && isEnabled() && isValidDevice(device)) { try { return service.getReport(device, reportType, reportId, bufferSize); @@ -649,7 +649,7 @@ public final class BluetoothInputDevice implements BluetoothProfile { */ public boolean setReport(BluetoothDevice device, byte reportType, String report) { if (VDBG) log("setReport(" + device + "), reportType=" + reportType + " report=" + report); - final IBluetoothInputDevice service = mService; + final IBluetoothHidHost service = mService; if (service != null && isEnabled() && isValidDevice(device)) { try { return service.setReport(device, reportType, report); @@ -674,7 +674,7 @@ public final class BluetoothInputDevice implements BluetoothProfile { */ public boolean sendData(BluetoothDevice device, String report) { if (DBG) log("sendData(" + device + "), report=" + report); - final IBluetoothInputDevice service = mService; + final IBluetoothHidHost service = mService; if (service != null && isEnabled() && isValidDevice(device)) { try { return service.sendData(device, report); @@ -698,7 +698,7 @@ public final class BluetoothInputDevice implements BluetoothProfile { */ public boolean getIdleTime(BluetoothDevice device) { if (DBG) log("getIdletime(" + device + ")"); - final IBluetoothInputDevice service = mService; + final IBluetoothHidHost service = mService; if (service != null && isEnabled() && isValidDevice(device)) { try { return service.getIdleTime(device); @@ -723,7 +723,7 @@ public final class BluetoothInputDevice implements BluetoothProfile { */ public boolean setIdleTime(BluetoothDevice device, byte idleTime) { if (DBG) log("setIdletime(" + device + "), idleTime=" + idleTime); - final IBluetoothInputDevice service = mService; + final IBluetoothHidHost service = mService; if (service != null && isEnabled() && isValidDevice(device)) { try { return service.setIdleTime(device, idleTime); diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java index bc8fa846087d..46a230b50605 100644 --- a/core/java/android/bluetooth/BluetoothProfile.java +++ b/core/java/android/bluetooth/BluetoothProfile.java @@ -73,11 +73,11 @@ public interface BluetoothProfile { public static final int HEALTH = 3; /** - * Input Device Profile + * HID Host * * @hide */ - public static final int INPUT_DEVICE = 4; + public static final int HID_HOST = 4; /** * PAN Profile @@ -152,11 +152,11 @@ public interface BluetoothProfile { public static final int MAP_CLIENT = 18; /** - * Input Host + * HID Device * * @hide */ - public static final int INPUT_HOST = 19; + public static final int HID_DEVICE = 19; /** * Max profile ID. This value should be updated whenever a new profile is added to match diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index d73f8526843c..20342807f75f 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -375,7 +375,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { * {@code DownloadManager}, {@code MediaPlayer}) will refuse app's requests to use cleartext * traffic. Third-party libraries are encouraged to honor this flag as well. * - * <p>NOTE: {@code WebView} does not honor this flag. + * <p>NOTE: {@code WebView} honors this flag for applications targeting API level 26 and up. * * <p>This flag is ignored on Android N and above if an Android Network Security Config is * present. @@ -1463,98 +1463,84 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { } } - /** - * @hide - */ - public boolean isForwardLocked() { - return (privateFlags & ApplicationInfo.PRIVATE_FLAG_FORWARD_LOCK) != 0; + /** @hide */ + public boolean isDefaultToDeviceProtectedStorage() { + return (privateFlags + & ApplicationInfo.PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE) != 0; } - /** - * @hide - */ - @TestApi - public boolean isSystemApp() { - return (flags & ApplicationInfo.FLAG_SYSTEM) != 0; + /** @hide */ + public boolean isDirectBootAware() { + return (privateFlags & ApplicationInfo.PRIVATE_FLAG_DIRECT_BOOT_AWARE) != 0; } - /** - * @hide - */ - @TestApi - public boolean isPrivilegedApp() { - return (privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0; + /** @hide */ + public boolean isEncryptionAware() { + return isDirectBootAware() || isPartiallyDirectBootAware(); } - /** - * @hide - */ - public boolean isUpdatedSystemApp() { - return (flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0; + /** @hide */ + public boolean isExternal() { + return (flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0; } /** @hide */ - public boolean isInternal() { - return (flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) == 0; + public boolean isExternalAsec() { + return TextUtils.isEmpty(volumeUuid) && isExternal(); } /** @hide */ - public boolean isExternalAsec() { - return TextUtils.isEmpty(volumeUuid) - && (flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0; + public boolean isForwardLocked() { + return (privateFlags & ApplicationInfo.PRIVATE_FLAG_FORWARD_LOCK) != 0; } /** @hide */ - public boolean isDefaultToDeviceProtectedStorage() { - return (privateFlags - & ApplicationInfo.PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE) != 0; + public boolean isInstantApp() { + return (privateFlags & ApplicationInfo.PRIVATE_FLAG_INSTANT) != 0; } /** @hide */ - public boolean isDirectBootAware() { - return (privateFlags & ApplicationInfo.PRIVATE_FLAG_DIRECT_BOOT_AWARE) != 0; + public boolean isInternal() { + return (flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) == 0; } /** @hide */ - public boolean isPartiallyDirectBootAware() { - return (privateFlags & ApplicationInfo.PRIVATE_FLAG_PARTIALLY_DIRECT_BOOT_AWARE) != 0; + public boolean isOem() { + return (privateFlags & ApplicationInfo.PRIVATE_FLAG_OEM) != 0; } /** @hide */ - public boolean isEncryptionAware() { - return isDirectBootAware() || isPartiallyDirectBootAware(); + public boolean isPartiallyDirectBootAware() { + return (privateFlags & ApplicationInfo.PRIVATE_FLAG_PARTIALLY_DIRECT_BOOT_AWARE) != 0; } - /** - * @hide - */ - public boolean isInstantApp() { - return (privateFlags & ApplicationInfo.PRIVATE_FLAG_INSTANT) != 0; + /** @hide */ + @TestApi + public boolean isPrivilegedApp() { + return (privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0; } - /** - * @hide - */ + /** @hide */ public boolean isRequiredForSystemUser() { return (privateFlags & ApplicationInfo.PRIVATE_FLAG_REQUIRED_FOR_SYSTEM_USER) != 0; } - /** - * Returns true if the app has declared in its manifest that it wants its split APKs to be - * loaded into isolated Contexts, with their own ClassLoaders and Resources objects. - * @hide - */ - public boolean requestsIsolatedSplitLoading() { - return (privateFlags & ApplicationInfo.PRIVATE_FLAG_ISOLATED_SPLIT_LOADING) != 0; - } - - /** - * @hide - */ + /** @hide */ public boolean isStaticSharedLibrary() { return (privateFlags & ApplicationInfo.PRIVATE_FLAG_STATIC_SHARED_LIBRARY) != 0; } + /** @hide */ + @TestApi + public boolean isSystemApp() { + return (flags & ApplicationInfo.FLAG_SYSTEM) != 0; + } + + /** @hide */ + public boolean isUpdatedSystemApp() { + return (flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0; + } + /** * Returns whether or not this application was installed as a virtual preload. */ @@ -1563,10 +1549,12 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { } /** + * Returns true if the app has declared in its manifest that it wants its split APKs to be + * loaded into isolated Contexts, with their own ClassLoaders and Resources objects. * @hide */ - public boolean isOem() { - return (privateFlags & ApplicationInfo.PRIVATE_FLAG_OEM) != 0; + public boolean requestsIsolatedSplitLoading() { + return (privateFlags & ApplicationInfo.PRIVATE_FLAG_ISOLATED_SPLIT_LOADING) != 0; } /** diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java index 143c51da5367..14cf8557477b 100644 --- a/core/java/android/content/pm/PackageManagerInternal.java +++ b/core/java/android/content/pm/PackageManagerInternal.java @@ -337,6 +337,12 @@ public abstract class PackageManagerInternal { public abstract boolean isPackagePersistent(String packageName); /** + * Returns whether or not the given package represents a legacy system application released + * prior to runtime permissions. + */ + public abstract boolean isLegacySystemApp(PackageParser.Package pkg); + + /** * Get all overlay packages for a user. * @param userId The user for which to get the overlays. * @return A list of overlay packages. An empty list is returned if the @@ -467,7 +473,4 @@ public abstract class PackageManagerInternal { /** Updates the flags for the given permission. */ public abstract void updatePermissionFlagsTEMP(@NonNull String permName, @NonNull String packageName, int flagMask, int flagValues, int userId); - /** Returns a PermissionGroup. */ - public abstract @Nullable PackageParser.PermissionGroup getPermissionGroupTEMP( - @NonNull String groupName); } diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index ad36139a2da4..b48829cfc87d 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -6221,48 +6221,48 @@ public class PackageParser { return false; } - /** - * @hide - */ + /** @hide */ + public boolean isExternal() { + return applicationInfo.isExternal(); + } + + /** @hide */ public boolean isForwardLocked() { return applicationInfo.isForwardLocked(); } - /** - * @hide - */ - public boolean isSystemApp() { - return applicationInfo.isSystemApp(); + /** @hide */ + public boolean isOem() { + return applicationInfo.isOem(); } - /** - * @hide - */ - public boolean isPrivilegedApp() { + /** @hide */ + public boolean isPrivileged() { return applicationInfo.isPrivilegedApp(); } - /** - * @hide - */ + /** @hide */ + public boolean isSystem() { + return applicationInfo.isSystemApp(); + } + + /** @hide */ public boolean isUpdatedSystemApp() { return applicationInfo.isUpdatedSystemApp(); } - /** - * @hide - */ + /** @hide */ public boolean canHaveOatDir() { // The following app types CANNOT have oat directory // - non-updated system apps // - forward-locked apps or apps installed in ASEC containers - return (!isSystemApp() || isUpdatedSystemApp()) + return (!isSystem() || isUpdatedSystemApp()) && !isForwardLocked() && !applicationInfo.isExternalAsec(); } public boolean isMatch(int flags) { if ((flags & PackageManager.MATCH_SYSTEM_ONLY) != 0) { - return isSystemApp(); + return isSystem(); } return true; } diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java index 595d85715fc9..996824d4393d 100644 --- a/core/java/android/hardware/usb/UsbManager.java +++ b/core/java/android/hardware/usb/UsbManager.java @@ -102,7 +102,7 @@ public class UsbManager { "android.hardware.usb.action.USB_PORT_CHANGED"; /** - * Broadcast Action: A broadcast for USB device attached event. + * Activity intent sent when a USB device is attached. * * This intent is sent when a USB device is attached to the USB bus when in host mode. * <ul> @@ -128,9 +128,8 @@ public class UsbManager { "android.hardware.usb.action.USB_DEVICE_DETACHED"; /** - * Broadcast Action: A broadcast for USB accessory attached event. + * Activity intent sent when a USB accessory is attached. * - * This intent is sent when a USB accessory is attached. * <ul> * <li> {@link #EXTRA_ACCESSORY} containing the {@link android.hardware.usb.UsbAccessory} * for the attached accessory diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java index 2468225fe62f..2c7e51a1db25 100644 --- a/core/java/android/inputmethodservice/IInputMethodWrapper.java +++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java @@ -48,6 +48,7 @@ import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; /** * Implements the internal IInputMethod interface to convert incoming calls @@ -76,6 +77,20 @@ class IInputMethodWrapper extends IInputMethod.Stub final WeakReference<InputMethod> mInputMethod; final int mTargetSdkVersion; + /** + * This is not {@null} only between {@link #bindInput(InputBinding)} and {@link #unbindInput()} + * so that {@link InputConnectionWrapper} can query if {@link #unbindInput()} has already been + * called or not, mainly to avoid unnecessary blocking operations. + * + * <p>This field must be set and cleared only from the binder thread(s), where the system + * guarantees that {@link #bindInput(InputBinding)}, + * {@link #startInput(IBinder, IInputContext, int, EditorInfo, boolean)}, and + * {@link #unbindInput()} are called with the same order as the original calls + * in {@link com.android.server.InputMethodManagerService}. See {@link IBinder#FLAG_ONEWAY} + * for detailed semantics.</p> + */ + AtomicBoolean mIsUnbindIssued = null; + // NOTE: we should have a cache of these. static final class InputMethodSessionCallbackWrapper implements InputMethod.SessionCallback { final Context mContext; @@ -163,8 +178,10 @@ class IInputMethodWrapper extends IInputMethod.Stub final IBinder startInputToken = (IBinder) args.arg1; final IInputContext inputContext = (IInputContext) args.arg2; final EditorInfo info = (EditorInfo) args.arg3; + final AtomicBoolean isUnbindIssued = (AtomicBoolean) args.arg4; final InputConnection ic = inputContext != null - ? new InputConnectionWrapper(mTarget, inputContext, missingMethods) : null; + ? new InputConnectionWrapper( + mTarget, inputContext, missingMethods, isUnbindIssued) : null; info.makeCompatible(mTargetSdkVersion); inputMethod.dispatchStartInputWithToken(ic, info, restarting /* restarting */, startInputToken); @@ -236,10 +253,15 @@ class IInputMethodWrapper extends IInputMethod.Stub @BinderThread @Override public void bindInput(InputBinding binding) { + if (mIsUnbindIssued != null) { + Log.e(TAG, "bindInput must be paired with unbindInput."); + } + mIsUnbindIssued = new AtomicBoolean(); // This IInputContext is guaranteed to implement all the methods. final int missingMethodFlags = 0; InputConnection ic = new InputConnectionWrapper(mTarget, - IInputContext.Stub.asInterface(binding.getConnectionToken()), missingMethodFlags); + IInputContext.Stub.asInterface(binding.getConnectionToken()), missingMethodFlags, + mIsUnbindIssued); InputBinding nu = new InputBinding(ic, binding); mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_INPUT_CONTEXT, nu)); } @@ -247,6 +269,13 @@ class IInputMethodWrapper extends IInputMethod.Stub @BinderThread @Override public void unbindInput() { + if (mIsUnbindIssued != null) { + // Signal the flag then forget it. + mIsUnbindIssued.set(true); + mIsUnbindIssued = null; + } else { + Log.e(TAG, "unbindInput must be paired with bindInput."); + } mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_UNSET_INPUT_CONTEXT)); } @@ -255,8 +284,13 @@ class IInputMethodWrapper extends IInputMethod.Stub public void startInput(IBinder startInputToken, IInputContext inputContext, @InputConnectionInspector.MissingMethodFlags final int missingMethods, EditorInfo attribute, boolean restarting) { - mCaller.executeOrSendMessage(mCaller.obtainMessageIIOOO(DO_START_INPUT, - missingMethods, restarting ? 1 : 0, startInputToken, inputContext, attribute)); + if (mIsUnbindIssued == null) { + Log.e(TAG, "startInput must be called after bindInput."); + mIsUnbindIssued = new AtomicBoolean(); + } + mCaller.executeOrSendMessage(mCaller.obtainMessageIIOOOO(DO_START_INPUT, + missingMethods, restarting ? 1 : 0, startInputToken, inputContext, attribute, + mIsUnbindIssued)); } @BinderThread diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java index 4bb884405360..db12dd9724dc 100644 --- a/core/java/android/net/NetworkCapabilities.java +++ b/core/java/android/net/NetworkCapabilities.java @@ -16,6 +16,7 @@ package android.net; +import android.annotation.IntDef; import android.os.Parcel; import android.os.Parcelable; @@ -23,6 +24,8 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.BitUtils; import com.android.internal.util.Preconditions; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Objects; import java.util.StringJoiner; @@ -77,6 +80,31 @@ public final class NetworkCapabilities implements Parcelable { */ private long mNetworkCapabilities; + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "NET_CAPABILITY_" }, value = { + NET_CAPABILITY_MMS, + NET_CAPABILITY_SUPL, + NET_CAPABILITY_DUN, + NET_CAPABILITY_FOTA, + NET_CAPABILITY_IMS, + NET_CAPABILITY_CBS, + NET_CAPABILITY_WIFI_P2P, + NET_CAPABILITY_IA, + NET_CAPABILITY_RCS, + NET_CAPABILITY_XCAP, + NET_CAPABILITY_EIMS, + NET_CAPABILITY_NOT_METERED, + NET_CAPABILITY_INTERNET, + NET_CAPABILITY_NOT_RESTRICTED, + NET_CAPABILITY_TRUSTED, + NET_CAPABILITY_NOT_VPN, + NET_CAPABILITY_VALIDATED, + NET_CAPABILITY_CAPTIVE_PORTAL, + NET_CAPABILITY_FOREGROUND, + }) + public @interface NetCapability { } + /** * Indicates this is a network that has the ability to reach the * carrier's MMSC for sending and receiving MMS messages. @@ -260,11 +288,11 @@ public final class NetworkCapabilities implements Parcelable { * Multiple capabilities may be applied sequentially. Note that when searching * for a network to satisfy a request, all capabilities requested must be satisfied. * - * @param capability the {@code NetworkCapabilities.NET_CAPABILITY_*} to be added. + * @param capability the capability to be added. * @return This NetworkCapabilities instance, to facilitate chaining. * @hide */ - public NetworkCapabilities addCapability(int capability) { + public NetworkCapabilities addCapability(@NetCapability int capability) { if (capability < MIN_NET_CAPABILITY || capability > MAX_NET_CAPABILITY) { throw new IllegalArgumentException("NetworkCapability out of range"); } @@ -275,11 +303,11 @@ public final class NetworkCapabilities implements Parcelable { /** * Removes (if found) the given capability from this {@code NetworkCapability} instance. * - * @param capability the {@code NetworkCapabilities.NET_CAPABILTIY_*} to be removed. + * @param capability the capability to be removed. * @return This NetworkCapabilities instance, to facilitate chaining. * @hide */ - public NetworkCapabilities removeCapability(int capability) { + public NetworkCapabilities removeCapability(@NetCapability int capability) { if (capability < MIN_NET_CAPABILITY || capability > MAX_NET_CAPABILITY) { throw new IllegalArgumentException("NetworkCapability out of range"); } @@ -290,21 +318,20 @@ public final class NetworkCapabilities implements Parcelable { /** * Gets all the capabilities set on this {@code NetworkCapability} instance. * - * @return an array of {@code NetworkCapabilities.NET_CAPABILITY_*} values - * for this instance. + * @return an array of capability values for this instance. * @hide */ - public int[] getCapabilities() { + public @NetCapability int[] getCapabilities() { return BitUtils.unpackBits(mNetworkCapabilities); } /** * Tests for the presence of a capabilitity on this instance. * - * @param capability the {@code NetworkCapabilities.NET_CAPABILITY_*} to be tested for. + * @param capability the capabilities to be tested for. * @return {@code true} if set on this instance. */ - public boolean hasCapability(int capability) { + public boolean hasCapability(@NetCapability int capability) { if (capability < MIN_NET_CAPABILITY || capability > MAX_NET_CAPABILITY) { return false; } @@ -385,6 +412,19 @@ public final class NetworkCapabilities implements Parcelable { */ private long mTransportTypes; + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "TRANSPORT_" }, value = { + TRANSPORT_CELLULAR, + TRANSPORT_WIFI, + TRANSPORT_BLUETOOTH, + TRANSPORT_ETHERNET, + TRANSPORT_VPN, + TRANSPORT_WIFI_AWARE, + TRANSPORT_LOWPAN, + }) + public @interface Transport { } + /** * Indicates this network uses a Cellular transport. */ @@ -426,7 +466,7 @@ public final class NetworkCapabilities implements Parcelable { public static final int MAX_TRANSPORT = TRANSPORT_LOWPAN; /** @hide */ - public static boolean isValidTransport(int transportType) { + public static boolean isValidTransport(@Transport int transportType) { return (MIN_TRANSPORT <= transportType) && (transportType <= MAX_TRANSPORT); } @@ -449,11 +489,11 @@ public final class NetworkCapabilities implements Parcelable { * to be selected. This is logically different than * {@code NetworkCapabilities.NET_CAPABILITY_*} listed above. * - * @param transportType the {@code NetworkCapabilities.TRANSPORT_*} to be added. + * @param transportType the transport type to be added. * @return This NetworkCapabilities instance, to facilitate chaining. * @hide */ - public NetworkCapabilities addTransportType(int transportType) { + public NetworkCapabilities addTransportType(@Transport int transportType) { checkValidTransportType(transportType); mTransportTypes |= 1 << transportType; setNetworkSpecifier(mNetworkSpecifier); // used for exception checking @@ -463,11 +503,11 @@ public final class NetworkCapabilities implements Parcelable { /** * Removes (if found) the given transport from this {@code NetworkCapability} instance. * - * @param transportType the {@code NetworkCapabilities.TRANSPORT_*} to be removed. + * @param transportType the transport type to be removed. * @return This NetworkCapabilities instance, to facilitate chaining. * @hide */ - public NetworkCapabilities removeTransportType(int transportType) { + public NetworkCapabilities removeTransportType(@Transport int transportType) { checkValidTransportType(transportType); mTransportTypes &= ~(1 << transportType); setNetworkSpecifier(mNetworkSpecifier); // used for exception checking @@ -477,21 +517,20 @@ public final class NetworkCapabilities implements Parcelable { /** * Gets all the transports set on this {@code NetworkCapability} instance. * - * @return an array of {@code NetworkCapabilities.TRANSPORT_*} values - * for this instance. + * @return an array of transport type values for this instance. * @hide */ - public int[] getTransportTypes() { + public @Transport int[] getTransportTypes() { return BitUtils.unpackBits(mTransportTypes); } /** * Tests for the presence of a transport on this instance. * - * @param transportType the {@code NetworkCapabilities.TRANSPORT_*} to be tested for. + * @param transportType the transport type to be tested for. * @return {@code true} if set on this instance. */ - public boolean hasTransport(int transportType) { + public boolean hasTransport(@Transport int transportType) { return isValidTransport(transportType) && ((mTransportTypes & (1 << transportType)) != 0); } @@ -896,7 +935,7 @@ public final class NetworkCapabilities implements Parcelable { /** * @hide */ - public static String capabilityNamesOf(int[] capabilities) { + public static String capabilityNamesOf(@NetCapability int[] capabilities) { StringJoiner joiner = new StringJoiner("|"); if (capabilities != null) { for (int c : capabilities) { @@ -909,7 +948,7 @@ public final class NetworkCapabilities implements Parcelable { /** * @hide */ - public static String capabilityNameOf(int capability) { + public static String capabilityNameOf(@NetCapability int capability) { switch (capability) { case NET_CAPABILITY_MMS: return "MMS"; case NET_CAPABILITY_SUPL: return "SUPL"; @@ -937,7 +976,7 @@ public final class NetworkCapabilities implements Parcelable { /** * @hide */ - public static String transportNamesOf(int[] types) { + public static String transportNamesOf(@Transport int[] types) { StringJoiner joiner = new StringJoiner("|"); if (types != null) { for (int t : types) { @@ -950,14 +989,14 @@ public final class NetworkCapabilities implements Parcelable { /** * @hide */ - public static String transportNameOf(int transport) { + public static String transportNameOf(@Transport int transport) { if (!isValidTransport(transport)) { return "UNKNOWN"; } return TRANSPORT_NAMES[transport]; } - private static void checkValidTransportType(int transport) { + private static void checkValidTransportType(@Transport int transport) { Preconditions.checkArgument( isValidTransport(transport), "Invalid TransportType " + transport); } diff --git a/core/java/android/net/NetworkRequest.java b/core/java/android/net/NetworkRequest.java index 95a8bb472939..25b1705262e8 100644 --- a/core/java/android/net/NetworkRequest.java +++ b/core/java/android/net/NetworkRequest.java @@ -155,14 +155,13 @@ public class NetworkRequest implements Parcelable { * Add the given capability requirement to this builder. These represent * the requested network's required capabilities. Note that when searching * for a network to satisfy a request, all capabilities requested must be - * satisfied. See {@link NetworkCapabilities} for {@code NET_CAPABILITY_*} - * definitions. + * satisfied. * - * @param capability The {@code NetworkCapabilities.NET_CAPABILITY_*} to add. + * @param capability The capability to add. * @return The builder to facilitate chaining * {@code builder.addCapability(...).addCapability();}. */ - public Builder addCapability(int capability) { + public Builder addCapability(@NetworkCapabilities.NetCapability int capability) { mNetworkCapabilities.addCapability(capability); return this; } @@ -170,10 +169,10 @@ public class NetworkRequest implements Parcelable { /** * Removes (if found) the given capability from this builder instance. * - * @param capability The {@code NetworkCapabilities.NET_CAPABILITY_*} to remove. + * @param capability The capability to remove. * @return The builder to facilitate chaining. */ - public Builder removeCapability(int capability) { + public Builder removeCapability(@NetworkCapabilities.NetCapability int capability) { mNetworkCapabilities.removeCapability(capability); return this; } @@ -208,13 +207,12 @@ public class NetworkRequest implements Parcelable { * Adds the given transport requirement to this builder. These represent * the set of allowed transports for the request. Only networks using one * of these transports will satisfy the request. If no particular transports - * are required, none should be specified here. See {@link NetworkCapabilities} - * for {@code TRANSPORT_*} definitions. + * are required, none should be specified here. * - * @param transportType The {@code NetworkCapabilities.TRANSPORT_*} to add. + * @param transportType The transport type to add. * @return The builder to facilitate chaining. */ - public Builder addTransportType(int transportType) { + public Builder addTransportType(@NetworkCapabilities.Transport int transportType) { mNetworkCapabilities.addTransportType(transportType); return this; } @@ -222,10 +220,10 @@ public class NetworkRequest implements Parcelable { /** * Removes (if found) the given transport from this builder instance. * - * @param transportType The {@code NetworkCapabilities.TRANSPORT_*} to remove. + * @param transportType The transport type to remove. * @return The builder to facilitate chaining. */ - public Builder removeTransportType(int transportType) { + public Builder removeTransportType(@NetworkCapabilities.Transport int transportType) { mNetworkCapabilities.removeTransportType(transportType); return this; } diff --git a/core/java/android/net/metrics/ConnectStats.java b/core/java/android/net/metrics/ConnectStats.java index 30b2656227d0..2495cab1adf9 100644 --- a/core/java/android/net/metrics/ConnectStats.java +++ b/core/java/android/net/metrics/ConnectStats.java @@ -20,6 +20,7 @@ import android.net.NetworkCapabilities; import android.system.OsConstants; import android.util.IntArray; import android.util.SparseIntArray; + import com.android.internal.util.BitUtils; import com.android.internal.util.TokenBucket; @@ -43,6 +44,8 @@ public class ConnectStats { public final TokenBucket mLatencyTb; /** Maximum number of latency values recorded. */ public final int mMaxLatencyRecords; + /** Total count of events */ + public int eventCount = 0; /** Total count of successful connects. */ public int connectCount = 0; /** Total count of successful connects done in blocking mode. */ @@ -57,12 +60,15 @@ public class ConnectStats { mMaxLatencyRecords = maxLatencyRecords; } - public void addEvent(int errno, int latencyMs, String ipAddr) { + boolean addEvent(int errno, int latencyMs, String ipAddr) { + eventCount++; if (isSuccess(errno)) { countConnect(errno, ipAddr); countLatency(errno, latencyMs); + return true; } else { countError(errno); + return false; } } @@ -101,7 +107,7 @@ public class ConnectStats { return (errno == 0) || isNonBlocking(errno); } - private static boolean isNonBlocking(int errno) { + static boolean isNonBlocking(int errno) { // On non-blocking TCP sockets, connect() immediately returns EINPROGRESS. // On non-blocking TCP sockets that are connecting, connect() immediately returns EALREADY. return (errno == EINPROGRESS) || (errno == EALREADY); @@ -117,6 +123,7 @@ public class ConnectStats { for (int t : BitUtils.unpackBits(transports)) { builder.append(NetworkCapabilities.transportNameOf(t)).append(", "); } + builder.append(String.format("%d events, ", eventCount)); builder.append(String.format("%d success, ", connectCount)); builder.append(String.format("%d blocking, ", connectBlockingCount)); builder.append(String.format("%d IPv6 dst", ipv6ConnectCount)); diff --git a/core/java/android/net/metrics/DefaultNetworkEvent.java b/core/java/android/net/metrics/DefaultNetworkEvent.java index 28cf42f2fe28..eb61c1532d49 100644 --- a/core/java/android/net/metrics/DefaultNetworkEvent.java +++ b/core/java/android/net/metrics/DefaultNetworkEvent.java @@ -16,73 +16,43 @@ package android.net.metrics; +import static android.net.ConnectivityManager.NETID_UNSET; + import android.net.NetworkCapabilities; -import android.os.Parcel; -import android.os.Parcelable; /** * An event recorded by ConnectivityService when there is a change in the default network. * {@hide} */ -public final class DefaultNetworkEvent implements Parcelable { +public class DefaultNetworkEvent { + // The ID of the network that has become the new default or NETID_UNSET if none. - public final int netId; + public int netId = NETID_UNSET; // The list of transport types of the new default network, for example TRANSPORT_WIFI, as // defined in NetworkCapabilities.java. - public final int[] transportTypes; + public int[] transportTypes = new int[0]; // The ID of the network that was the default before or NETID_UNSET if none. - public final int prevNetId; + public int prevNetId = NETID_UNSET; // Whether the previous network had IPv4/IPv6 connectivity. - public final boolean prevIPv4; - public final boolean prevIPv6; - - public DefaultNetworkEvent(int netId, int[] transportTypes, - int prevNetId, boolean prevIPv4, boolean prevIPv6) { - this.netId = netId; - this.transportTypes = transportTypes; - this.prevNetId = prevNetId; - this.prevIPv4 = prevIPv4; - this.prevIPv6 = prevIPv6; - } - - private DefaultNetworkEvent(Parcel in) { - this.netId = in.readInt(); - this.transportTypes = in.createIntArray(); - this.prevNetId = in.readInt(); - this.prevIPv4 = (in.readByte() > 0); - this.prevIPv6 = (in.readByte() > 0); - } - - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeInt(netId); - out.writeIntArray(transportTypes); - out.writeInt(prevNetId); - out.writeByte(prevIPv4 ? (byte) 1 : (byte) 0); - out.writeByte(prevIPv6 ? (byte) 1 : (byte) 0); - } - - @Override - public int describeContents() { - return 0; - } + public boolean prevIPv4; + public boolean prevIPv6; @Override public String toString() { - String prevNetwork = String.valueOf(prevNetId); - String newNetwork = String.valueOf(netId); - if (prevNetId != 0) { - prevNetwork += ":" + ipSupport(); - } - if (netId != 0) { - newNetwork += ":" + NetworkCapabilities.transportNamesOf(transportTypes); - } - return String.format("DefaultNetworkEvent(%s -> %s)", prevNetwork, newNetwork); + String prevNetwork = String.valueOf(prevNetId); + String newNetwork = String.valueOf(netId); + if (prevNetId != 0) { + prevNetwork += ":" + ipSupport(); + } + if (netId != 0) { + newNetwork += ":" + NetworkCapabilities.transportNamesOf(transportTypes); + } + return String.format("DefaultNetworkEvent(%s -> %s)", prevNetwork, newNetwork); } private String ipSupport() { if (prevIPv4 && prevIPv6) { - return "DUAL"; + return "IPv4v6"; } if (prevIPv6) { return "IPv6"; @@ -92,15 +62,4 @@ public final class DefaultNetworkEvent implements Parcelable { } return "NONE"; } - - public static final Parcelable.Creator<DefaultNetworkEvent> CREATOR - = new Parcelable.Creator<DefaultNetworkEvent>() { - public DefaultNetworkEvent createFromParcel(Parcel in) { - return new DefaultNetworkEvent(in); - } - - public DefaultNetworkEvent[] newArray(int size) { - return new DefaultNetworkEvent[size]; - } - }; } diff --git a/core/java/android/net/metrics/DnsEvent.java b/core/java/android/net/metrics/DnsEvent.java index a4970e4d0d28..81b098bbb38e 100644 --- a/core/java/android/net/metrics/DnsEvent.java +++ b/core/java/android/net/metrics/DnsEvent.java @@ -17,11 +17,13 @@ package android.net.metrics; import android.net.NetworkCapabilities; -import java.util.Arrays; + import com.android.internal.util.BitUtils; +import java.util.Arrays; + /** - * A DNS event recorded by NetdEventListenerService. + * A batch of DNS events recorded by NetdEventListenerService for a specific network. * {@hide} */ final public class DnsEvent { @@ -38,6 +40,8 @@ final public class DnsEvent { // the eventTypes, returnCodes, and latenciesMs arrays have the same length and the i-th event // is spread across the three array at position i. public int eventCount; + // The number of successful DNS queries recorded. + public int successCount; // The types of DNS queries as defined in INetdEventListener. public byte[] eventTypes; // Current getaddrinfo codes go from 1 to EAI_MAX = 15. gethostbyname returns errno, but there @@ -54,10 +58,11 @@ final public class DnsEvent { latenciesMs = new int[initialCapacity]; } - public void addResult(byte eventType, byte returnCode, int latencyMs) { + boolean addResult(byte eventType, byte returnCode, int latencyMs) { + boolean isSuccess = (returnCode == 0); if (eventCount >= SIZE_LIMIT) { // TODO: implement better rate limiting that does not biases metrics. - return; + return isSuccess; } if (eventCount == eventTypes.length) { resize((int) (1.4 * eventCount)); @@ -66,6 +71,10 @@ final public class DnsEvent { returnCodes[eventCount] = returnCode; latenciesMs[eventCount] = latencyMs; eventCount++; + if (isSuccess) { + successCount++; + } + return isSuccess; } public void resize(int newLength) { @@ -80,6 +89,8 @@ final public class DnsEvent { for (int t : BitUtils.unpackBits(transports)) { builder.append(NetworkCapabilities.transportNameOf(t)).append(", "); } - return builder.append(eventCount).append(" events)").toString(); + builder.append(String.format("%d events, ", eventCount)); + builder.append(String.format("%d success)", successCount)); + return builder.toString(); } } diff --git a/core/java/android/net/metrics/NetworkMetrics.java b/core/java/android/net/metrics/NetworkMetrics.java new file mode 100644 index 000000000000..2b662a0c28e2 --- /dev/null +++ b/core/java/android/net/metrics/NetworkMetrics.java @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2017 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.net.metrics; + +import android.net.NetworkCapabilities; + +import com.android.internal.util.BitUtils; +import com.android.internal.util.TokenBucket; + +import java.util.StringJoiner; + +/** + * A class accumulating network metrics received from Netd regarding dns queries and + * connect() calls on a given network. + * + * This class also accumulates running sums of dns and connect latency stats and + * error counts for bug report logging. + * + * @hide + */ +public class NetworkMetrics { + + private static final int INITIAL_DNS_BATCH_SIZE = 100; + private static final int CONNECT_LATENCY_MAXIMUM_RECORDS = 20000; + + // The network id of the Android Network. + public final int netId; + // The transport types bitmap of the Android Network, as defined in NetworkCapabilities.java. + public final long transports; + // Accumulated metrics for connect events. + public final ConnectStats connectMetrics; + // Accumulated metrics for dns events. + public final DnsEvent dnsMetrics; + // Running sums of latencies and error counts for connect and dns events. + public final Summary summary; + // Running sums of the most recent latencies and error counts for connect and dns events. + // Starts null until some events are accumulated. + // Allows to collect periodic snapshot of the running summaries for a given network. + public Summary pendingSummary; + + public NetworkMetrics(int netId, long transports, TokenBucket tb) { + this.netId = netId; + this.transports = transports; + this.connectMetrics = + new ConnectStats(netId, transports, tb, CONNECT_LATENCY_MAXIMUM_RECORDS); + this.dnsMetrics = new DnsEvent(netId, transports, INITIAL_DNS_BATCH_SIZE); + this.summary = new Summary(netId, transports); + } + + /** + * Get currently pending Summary statistics, if any, for this NetworkMetrics, merge them + * into the long running Summary statistics of this NetworkMetrics, and also clear them. + */ + public Summary getPendingStats() { + Summary s = pendingSummary; + pendingSummary = null; + if (s != null) { + summary.merge(s); + } + return s; + } + + /** Accumulate a dns query result reported by netd. */ + public void addDnsResult(int eventType, int returnCode, int latencyMs) { + if (pendingSummary == null) { + pendingSummary = new Summary(netId, transports); + } + boolean isSuccess = dnsMetrics.addResult((byte) eventType, (byte) returnCode, latencyMs); + pendingSummary.dnsLatencies.count(latencyMs); + pendingSummary.dnsErrorRate.count(isSuccess ? 0 : 1); + } + + /** Accumulate a connect query result reported by netd. */ + public void addConnectResult(int error, int latencyMs, String ipAddr) { + if (pendingSummary == null) { + pendingSummary = new Summary(netId, transports); + } + boolean isSuccess = connectMetrics.addEvent(error, latencyMs, ipAddr); + pendingSummary.connectErrorRate.count(isSuccess ? 0 : 1); + if (ConnectStats.isNonBlocking(error)) { + pendingSummary.connectLatencies.count(latencyMs); + } + } + + /** Represents running sums for dns and connect average error counts and average latencies. */ + public static class Summary { + + public final int netId; + public final long transports; + // DNS latencies measured in milliseconds. + public final Metrics dnsLatencies = new Metrics(); + // DNS error rate measured in percentage points. + public final Metrics dnsErrorRate = new Metrics(); + // Blocking connect latencies measured in milliseconds. + public final Metrics connectLatencies = new Metrics(); + // Blocking and non blocking connect error rate measured in percentage points. + public final Metrics connectErrorRate = new Metrics(); + + public Summary(int netId, long transports) { + this.netId = netId; + this.transports = transports; + } + + void merge(Summary that) { + dnsLatencies.merge(that.dnsLatencies); + dnsErrorRate.merge(that.dnsErrorRate); + connectLatencies.merge(that.connectLatencies); + connectErrorRate.merge(that.connectErrorRate); + } + + @Override + public String toString() { + StringJoiner j = new StringJoiner(", ", "{", "}"); + j.add("netId=" + netId); + for (int t : BitUtils.unpackBits(transports)) { + j.add(NetworkCapabilities.transportNameOf(t)); + } + j.add(String.format("dns avg=%dms max=%dms err=%.1f%% tot=%d", + (int) dnsLatencies.average(), (int) dnsLatencies.max, + 100 * dnsErrorRate.average(), dnsErrorRate.count)); + j.add(String.format("connect avg=%dms max=%dms err=%.1f%% tot=%d", + (int) connectLatencies.average(), (int) connectLatencies.max, + 100 * connectErrorRate.average(), connectErrorRate.count)); + return j.toString(); + } + } + + /** Tracks a running sum and returns the average of a metric. */ + static class Metrics { + public double sum; + public double max = Double.MIN_VALUE; + public int count; + + void merge(Metrics that) { + this.count += that.count; + this.sum += that.sum; + this.max = Math.max(this.max, that.max); + } + + void count(double value) { + count++; + sum += value; + max = Math.max(max, value); + } + + double average() { + double a = sum / (double) count; + if (Double.isNaN(a)) { + a = 0; + } + return a; + } + } +} diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index 8682c01ebd85..a8bd940326d6 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -1986,7 +1986,7 @@ public abstract class BatteryStats implements Parcelable { public abstract long getDeviceIdlingTime(int mode, long elapsedRealtimeUs, int which); /** - * Returns the number of times that the devie has started idling. + * Returns the number of times that the device has started idling. * * {@hide} */ @@ -6453,7 +6453,7 @@ public abstract class BatteryStats implements Parcelable { pw.println(); } } - if (!filtering || (flags&(DUMP_CHARGED_ONLY|DUMP_DAILY_ONLY)) != 0) { + if (!filtering || (flags & DUMP_DAILY_ONLY) != 0) { pw.println("Daily stats:"); pw.print(" Current start time: "); pw.println(DateFormat.format("yyyy-MM-dd-HH-mm-ss", diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java index e9e695bbdf10..66e1651997b4 100644 --- a/core/java/android/os/Binder.java +++ b/core/java/android/os/Binder.java @@ -167,7 +167,7 @@ public class Binder implements IBinder { try { if (binder instanceof BinderProxy) { ((BinderProxy) binder).mWarnOnBlocking = false; - } else if (binder != null + } else if (binder != null && binder.getInterfaceDescriptor() != null && binder.queryLocalInterface(binder.getInterfaceDescriptor()) == null) { Log.w(TAG, "Unable to allow blocking on interface " + binder); } @@ -414,7 +414,7 @@ public class Binder implements IBinder { * descriptor. */ public @Nullable IInterface queryLocalInterface(@NonNull String descriptor) { - if (mDescriptor.equals(descriptor)) { + if (mDescriptor != null && mDescriptor.equals(descriptor)) { return mOwner; } return null; diff --git a/core/java/android/view/autofill/AutoFillType.aidl b/core/java/android/os/IStatsCallbacks.aidl index 4606b48e9e10..02e7cd3978c8 100644 --- a/core/java/android/view/autofill/AutoFillType.aidl +++ b/core/java/android/os/IStatsCallbacks.aidl @@ -14,9 +14,12 @@ * limitations under the License. */ -package android.view.autofill; +package android.os; -/* - * TODO(b/35956626): remove once clients use getAutoFilltype() - */ -parcelable AutoFillType;
\ No newline at end of file +/** + * Callback for Statsd to allow binder calls to clients. + * {@hide} + */ +interface IStatsCallbacks { + void onReceiveLogs(out byte[] log); +} diff --git a/core/java/android/os/IStatsManager.aidl b/core/java/android/os/IStatsManager.aidl index daacc4e832f9..480296c12e50 100644 --- a/core/java/android/os/IStatsManager.aidl +++ b/core/java/android/os/IStatsManager.aidl @@ -16,6 +16,8 @@ package android.os; +import android.os.IStatsCallbacks; + /** * Binder interface to communicate with the statistics management service. * {@hide} @@ -61,4 +63,15 @@ interface IStatsManager { * Inform stats that an app was removed. */ oneway void informOnePackageRemoved(in String app, in int uid); + + /** + * Trigger pushLog to force push stats log entries from statsd on client side. + */ + void requestPush(); + + /** + * Listen to statsd to send stats log entries. + * TODO: Limit callbacks with specific configurations. + */ + void subscribeStatsLog(IStatsCallbacks callbacks); } diff --git a/core/java/android/os/ShellCommand.java b/core/java/android/os/ShellCommand.java index e4a12e84e466..6223235e628f 100644 --- a/core/java/android/os/ShellCommand.java +++ b/core/java/android/os/ShellCommand.java @@ -17,6 +17,7 @@ package android.os; import android.util.Slog; + import com.android.internal.util.FastPrintWriter; import java.io.BufferedInputStream; @@ -118,13 +119,33 @@ public abstract class ShellCommand { mErrPrintWriter.flush(); } if (DEBUG) Slog.d(TAG, "Sending command result on " + mTarget); - mResultReceiver.send(res, null); + if (mResultReceiver != null) { + mResultReceiver.send(res, null); + } } if (DEBUG) Slog.d(TAG, "Finished command " + mCmd + " on " + mTarget); return res; } /** + * Adopt the ResultReceiver that was given to this shell command from it, taking + * it over. Primarily used to dispatch to another shell command. Once called, + * this shell command will no longer return its own result when done. + */ + public ResultReceiver adoptResultReceiver() { + ResultReceiver rr = mResultReceiver; + mResultReceiver = null; + return rr; + } + + /** + * Return the raw FileDescriptor for the output stream. + */ + public FileDescriptor getOutFileDescriptor() { + return mOut; + } + + /** * Return direct raw access (not buffered) to the command's output data stream. */ public OutputStream getRawOutputStream() { @@ -145,6 +166,13 @@ public abstract class ShellCommand { } /** + * Return the raw FileDescriptor for the error stream. + */ + public FileDescriptor getErrFileDescriptor() { + return mErr; + } + + /** * Return direct raw access (not buffered) to the command's error output data stream. */ public OutputStream getRawErrorStream() { @@ -168,6 +196,13 @@ public abstract class ShellCommand { } /** + * Return the raw FileDescriptor for the input stream. + */ + public FileDescriptor getInFileDescriptor() { + return mIn; + } + + /** * Return direct raw access (not buffered) to the command's input data stream. */ public InputStream getRawInputStream() { diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java index 826ec1eb129e..ee3e5bc9a001 100644 --- a/core/java/android/os/StrictMode.java +++ b/core/java/android/os/StrictMode.java @@ -20,7 +20,6 @@ import android.annotation.Nullable; import android.annotation.TestApi; import android.app.ActivityManager; import android.app.ActivityThread; -import android.app.ApplicationErrorReport; import android.app.IActivityManager; import android.content.BroadcastReceiver; import android.content.Context; @@ -35,6 +34,7 @@ import android.util.Singleton; import android.util.Slog; import android.view.IWindowManager; +import com.android.internal.annotations.GuardedBy; import com.android.internal.os.RuntimeInit; import com.android.internal.util.FastPrintWriter; import com.android.internal.util.HexDump; @@ -48,8 +48,10 @@ import java.io.PrintWriter; import java.io.StringWriter; import java.net.InetAddress; import java.net.UnknownHostException; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; +import java.util.Deque; import java.util.HashMap; import java.util.concurrent.atomic.AtomicInteger; @@ -352,8 +354,8 @@ public final class StrictMode { } else { msg = "StrictMode policy violation:"; } - if (info.crashInfo != null) { - Log.d(TAG, msg + " " + info.crashInfo.stackTrace); + if (info.hasStackTrace()) { + Log.d(TAG, msg + " " + info.getStackTrace()); } else { Log.d(TAG, msg + " missing stack trace!"); } @@ -1247,28 +1249,6 @@ public final class StrictMode { } } - /** Like parsePolicyFromMessage(), but returns the violation. */ - private static int parseViolationFromMessage(String message) { - if (message == null) { - return 0; - } - int violationIndex = message.indexOf("violation="); - if (violationIndex == -1) { - return 0; - } - int numberStartIndex = violationIndex + "violation=".length(); - int numberEndIndex = message.indexOf(' ', numberStartIndex); - if (numberEndIndex == -1) { - numberEndIndex = message.length(); - } - String violationString = message.substring(numberStartIndex, numberEndIndex); - try { - return Integer.parseInt(violationString); - } catch (NumberFormatException e) { - return 0; - } - } - private static final ThreadLocal<ArrayList<ViolationInfo>> violationsBeingTimed = new ThreadLocal<ArrayList<ViolationInfo>>() { @Override @@ -1516,7 +1496,7 @@ public final class StrictMode { // to people who push/pop temporary policy in regions of code, // hence the policy being passed around. void handleViolation(final ViolationInfo info) { - if (info == null || info.crashInfo == null || info.crashInfo.stackTrace == null) { + if (info == null || !info.hasStackTrace()) { Log.wtf(TAG, "unexpected null stacktrace"); return; } @@ -1530,7 +1510,7 @@ public final class StrictMode { gatheredViolations.set(violations); } for (ViolationInfo previous : violations) { - if (info.crashInfo.stackTrace.equals(previous.crashInfo.stackTrace)) { + if (info.getStackTrace().equals(previous.getStackTrace())) { // Duplicate. Don't log. return; } @@ -1576,8 +1556,7 @@ public final class StrictMode { } if (violationMaskSubset != 0) { - int violationBit = parseViolationFromMessage(info.crashInfo.exceptionMessage); - violationMaskSubset |= violationBit; + violationMaskSubset |= info.getViolationBit(); final int savedPolicyMask = getThreadPolicyMask(); final boolean justDropBox = (info.policy & THREAD_PENALTY_MASK) == PENALTY_DROPBOX; @@ -1622,8 +1601,7 @@ public final class StrictMode { } private static void executeDeathPenalty(ViolationInfo info) { - int violationBit = parseViolationFromMessage(info.crashInfo.exceptionMessage); - throw new StrictModeViolation(info.policy, violationBit, null); + throw new StrictModeViolation(info.policy, info.getViolationBit(), null); } /** @@ -1670,7 +1648,7 @@ public final class StrictMode { private static class AndroidCloseGuardReporter implements CloseGuard.Reporter { public void report(String message, Throwable allocationSite) { - onVmPolicyViolation(message, allocationSite); + onVmPolicyViolation(allocationSite); } } @@ -1709,7 +1687,7 @@ public final class StrictMode { long instances = instanceCounts[i]; if (instances > limit) { Throwable tr = new InstanceCountViolation(klass, instances, limit); - onVmPolicyViolation(tr.getMessage(), tr); + onVmPolicyViolation(tr); } } } @@ -1833,22 +1811,24 @@ public final class StrictMode { /** @hide */ public static void onSqliteObjectLeaked(String message, Throwable originStack) { - onVmPolicyViolation(message, originStack); + Throwable t = new Throwable(message); + t.setStackTrace(originStack.getStackTrace()); + onVmPolicyViolation(t); } /** @hide */ public static void onWebViewMethodCalledOnWrongThread(Throwable originStack) { - onVmPolicyViolation(null, originStack); + onVmPolicyViolation(originStack); } /** @hide */ public static void onIntentReceiverLeaked(Throwable originStack) { - onVmPolicyViolation(null, originStack); + onVmPolicyViolation(originStack); } /** @hide */ public static void onServiceConnectionLeaked(Throwable originStack) { - onVmPolicyViolation(null, originStack); + onVmPolicyViolation(originStack); } /** @hide */ @@ -1857,7 +1837,7 @@ public final class StrictMode { if ((sVmPolicy.mask & PENALTY_DEATH_ON_FILE_URI_EXPOSURE) != 0) { throw new FileUriExposedException(message); } else { - onVmPolicyViolation(null, new Throwable(message)); + onVmPolicyViolation(new Throwable(message)); } } @@ -1869,7 +1849,7 @@ public final class StrictMode { + location + " without permission grant flags; did you forget" + " FLAG_GRANT_READ_URI_PERMISSION?"; - onVmPolicyViolation(null, new Throwable(message)); + onVmPolicyViolation(new Throwable(message)); } /** @hide */ @@ -1899,10 +1879,9 @@ public final class StrictMode { } catch (UnknownHostException ignored) { } } - + msg += HexDump.dumpHexString(firstPacket).trim() + " "; final boolean forceDeath = (sVmPolicy.mask & PENALTY_DEATH_ON_CLEARTEXT_NETWORK) != 0; - onVmPolicyViolation( - HexDump.dumpHexString(firstPacket).trim(), new Throwable(msg), forceDeath); + onVmPolicyViolation(new Throwable(msg), forceDeath); } /** @hide */ @@ -1912,24 +1891,23 @@ public final class StrictMode { /** @hide */ public static void onUntaggedSocket() { - onVmPolicyViolation(null, new Throwable(UNTAGGED_SOCKET_VIOLATION_MESSAGE)); + onVmPolicyViolation(new Throwable(UNTAGGED_SOCKET_VIOLATION_MESSAGE)); } // Map from VM violation fingerprint to uptime millis. private static final HashMap<Integer, Long> sLastVmViolationTime = new HashMap<Integer, Long>(); /** @hide */ - public static void onVmPolicyViolation(String message, Throwable originStack) { - onVmPolicyViolation(message, originStack, false); + public static void onVmPolicyViolation(Throwable originStack) { + onVmPolicyViolation(originStack, false); } /** @hide */ - public static void onVmPolicyViolation( - String message, Throwable originStack, boolean forceDeath) { + public static void onVmPolicyViolation(Throwable originStack, boolean forceDeath) { final boolean penaltyDropbox = (sVmPolicy.mask & PENALTY_DROPBOX) != 0; final boolean penaltyDeath = ((sVmPolicy.mask & PENALTY_DEATH) != 0) || forceDeath; final boolean penaltyLog = (sVmPolicy.mask & PENALTY_LOG) != 0; - final ViolationInfo info = new ViolationInfo(message, originStack, sVmPolicy.mask); + final ViolationInfo info = new ViolationInfo(originStack, sVmPolicy.mask); // Erase stuff not relevant for process-wide violations info.numAnimationsRunning = 0; @@ -2027,21 +2005,14 @@ public final class StrictMode { * read back all the encoded violations. */ /* package */ static void readAndHandleBinderCallViolations(Parcel p) { - // Our own stack trace to append - StringWriter sw = new StringWriter(); - sw.append("# via Binder call with stack:\n"); - PrintWriter pw = new FastPrintWriter(sw, false, 256); - new LogStackTrace().printStackTrace(pw); - pw.flush(); - String ourStack = sw.toString(); - + LogStackTrace localCallSite = new LogStackTrace(); final int policyMask = getThreadPolicyMask(); final boolean currentlyGathering = (policyMask & PENALTY_GATHER) != 0; final int size = p.readInt(); for (int i = 0; i < size; i++) { final ViolationInfo info = new ViolationInfo(p, !currentlyGathering); - info.crashInfo.appendStackTrace(ourStack); + info.addLocalStack(localCallSite); BlockGuard.Policy policy = BlockGuard.getThreadPolicy(); if (policy instanceof AndroidBlockGuardPolicy) { ((AndroidBlockGuardPolicy) policy).handleViolationWithTimingAttempt(info); @@ -2254,7 +2225,7 @@ public final class StrictMode { // StrictMode not enabled. return; } - ((AndroidBlockGuardPolicy) policy).onUnbufferedIO(); + policy.onUnbufferedIO(); } /** @hide */ @@ -2264,7 +2235,7 @@ public final class StrictMode { // StrictMode not enabled. return; } - ((AndroidBlockGuardPolicy) policy).onReadFromDisk(); + policy.onReadFromDisk(); } /** @hide */ @@ -2274,12 +2245,11 @@ public final class StrictMode { // StrictMode not enabled. return; } - ((AndroidBlockGuardPolicy) policy).onWriteToDisk(); + policy.onWriteToDisk(); } - // Guarded by StrictMode.class - private static final HashMap<Class, Integer> sExpectedActivityInstanceCount = - new HashMap<Class, Integer>(); + @GuardedBy("StrictMode.class") + private static final HashMap<Class, Integer> sExpectedActivityInstanceCount = new HashMap<>(); /** * Returns an object that is used to track instances of activites. The activity should store a @@ -2354,7 +2324,7 @@ public final class StrictMode { long instances = VMDebug.countInstancesOfClass(klass, false); if (instances > limit) { Throwable tr = new InstanceCountViolation(klass, instances, limit); - onVmPolicyViolation(tr.getMessage(), tr); + onVmPolicyViolation(tr); } } @@ -2366,10 +2336,15 @@ public final class StrictMode { */ @TestApi public static final class ViolationInfo implements Parcelable { - public final String message; + /** Stack and violation details. */ + @Nullable private final Throwable mThrowable; + + private final Deque<Throwable> mBinderStack = new ArrayDeque<>(); - /** Stack and other stuff info. */ - @Nullable public final ApplicationErrorReport.CrashInfo crashInfo; + /** Memoized stack trace of full violation. */ + @Nullable private String mStackTrace; + /** Memoized violation bit. */ + private int mViolationBit; /** The strict mode policy mask at the time of violation. */ public final int policy; @@ -2404,19 +2379,13 @@ public final class StrictMode { /** Create an uninitialized instance of ViolationInfo */ public ViolationInfo() { - message = null; - crashInfo = null; + mThrowable = null; policy = 0; } - public ViolationInfo(Throwable tr, int policy) { - this(null, tr, policy); - } - /** Create an instance of ViolationInfo initialized from an exception. */ - public ViolationInfo(String message, Throwable tr, int policy) { - this.message = message; - crashInfo = new ApplicationErrorReport.CrashInfo(tr); + public ViolationInfo(Throwable tr, int policy) { + this.mThrowable = tr; violationUptimeMillis = SystemClock.uptimeMillis(); this.policy = policy; this.numAnimationsRunning = ValueAnimator.getCurrentAnimationsCount(); @@ -2446,11 +2415,91 @@ public final class StrictMode { } } + /** Equivalent output to {@link ApplicationErrorReport.CrashInfo#stackTrace}. */ + public String getStackTrace() { + if (mThrowable != null && mStackTrace == null) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new FastPrintWriter(sw, false, 256); + mThrowable.printStackTrace(pw); + for (Throwable t : mBinderStack) { + pw.append("# via Binder call with stack:\n"); + t.printStackTrace(pw); + } + pw.flush(); + pw.close(); + mStackTrace = sw.toString(); + } + return mStackTrace; + } + + /** + * Optional message describing this violation. + * + * @hide + */ + @TestApi + public String getViolationDetails() { + if (mThrowable != null) { + return mThrowable.getMessage(); + } else { + return ""; + } + } + + /** + * If this violation has a useful stack trace. + * + * @hide + */ + public boolean hasStackTrace() { + return mThrowable != null; + } + + /** + * Add a {@link Throwable} from the current process that caused the underlying violation. + * + * @hide + */ + void addLocalStack(Throwable t) { + mBinderStack.addFirst(t); + } + + /** + * Retrieve the type of StrictMode violation. + * + * @hide + */ + int getViolationBit() { + if (mThrowable == null || mThrowable.getMessage() == null) { + return 0; + } + if (mViolationBit != 0) { + return mViolationBit; + } + String message = mThrowable.getMessage(); + int violationIndex = message.indexOf("violation="); + if (violationIndex == -1) { + return 0; + } + int numberStartIndex = violationIndex + "violation=".length(); + int numberEndIndex = message.indexOf(' ', numberStartIndex); + if (numberEndIndex == -1) { + numberEndIndex = message.length(); + } + String violationString = message.substring(numberStartIndex, numberEndIndex); + try { + mViolationBit = Integer.parseInt(violationString); + return mViolationBit; + } catch (NumberFormatException e) { + return 0; + } + } + @Override public int hashCode() { int result = 17; - if (crashInfo != null) { - result = 37 * result + crashInfo.stackTrace.hashCode(); + if (mThrowable != null) { + result = 37 * result + mThrowable.hashCode(); } if (numAnimationsRunning != 0) { result *= 37; @@ -2478,11 +2527,10 @@ public final class StrictMode { * should be removed. */ public ViolationInfo(Parcel in, boolean unsetGatheringBit) { - message = in.readString(); - if (in.readInt() != 0) { - crashInfo = new ApplicationErrorReport.CrashInfo(in); - } else { - crashInfo = null; + mThrowable = (Throwable) in.readSerializable(); + int binderStackSize = in.readInt(); + for (int i = 0; i < binderStackSize; i++) { + mBinderStack.add((Throwable) in.readSerializable()); } int rawPolicy = in.readInt(); if (unsetGatheringBit) { @@ -2502,12 +2550,10 @@ public final class StrictMode { /** Save a ViolationInfo instance to a parcel. */ @Override public void writeToParcel(Parcel dest, int flags) { - dest.writeString(message); - if (crashInfo != null) { - dest.writeInt(1); - crashInfo.writeToParcel(dest, flags); - } else { - dest.writeInt(0); + dest.writeSerializable(mThrowable); + dest.writeInt(mBinderStack.size()); + for (Throwable t : mBinderStack) { + dest.writeSerializable(t); } int start = dest.dataPosition(); dest.writeInt(policy); @@ -2542,8 +2588,8 @@ public final class StrictMode { /** Dump a ViolationInfo instance to a Printer. */ public void dump(Printer pw, String prefix) { - if (crashInfo != null) { - crashInfo.dump(pw, prefix); + if (mThrowable != null) { + pw.println(prefix + "stackTrace: " + getStackTrace()); } pw.println(prefix + "policy: " + policy); if (durationMillis != -1) { diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index c200ae742125..a27df3a70259 100755 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -7615,6 +7615,13 @@ public final class Settings { public static final String AIRPLANE_MODE_TOGGLEABLE_RADIOS = "airplane_mode_toggleable_radios"; /** + * An integer representing the Bluetooth Class of Device (CoD). + * + * @hide + */ + public static final String BLUETOOTH_CLASS_OF_DEVICE = "bluetooth_class_of_device"; + + /** * A Long representing a bitmap of profiles that should be disabled when bluetooth starts. * See {@link android.bluetooth.BluetoothProfile}. * {@hide} @@ -9649,7 +9656,7 @@ public final class Settings { * Get the key that retrieves a bluetooth Input Device's priority. * @hide */ - public static final String getBluetoothInputDevicePriorityKey(String address) { + public static final String getBluetoothHidHostPriorityKey(String address) { return BLUETOOTH_INPUT_DEVICE_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT); } diff --git a/core/java/android/security/NetworkSecurityPolicy.java b/core/java/android/security/NetworkSecurityPolicy.java index 812c956f4c61..0c4eedab2fc0 100644 --- a/core/java/android/security/NetworkSecurityPolicy.java +++ b/core/java/android/security/NetworkSecurityPolicy.java @@ -16,7 +16,6 @@ package android.security; -import android.annotation.TestApi; import android.content.Context; import android.content.pm.PackageManager; import android.security.net.config.ApplicationConfig; @@ -63,7 +62,8 @@ public class NetworkSecurityPolicy { * traffic from applications is handled by higher-level network stacks/components which can * honor this aspect of the policy. * - * <p>NOTE: {@link android.webkit.WebView} does not honor this flag. + * <p>NOTE: {@link android.webkit.WebView} honors this flag for applications targeting API level + * 26 and up. */ public boolean isCleartextTrafficPermitted() { return libcore.net.NetworkSecurityPolicy.getInstance().isCleartextTrafficPermitted(); diff --git a/core/java/android/security/net/config/NetworkSecurityConfig.java b/core/java/android/security/net/config/NetworkSecurityConfig.java index b9e550540217..52f48ef8499b 100644 --- a/core/java/android/security/net/config/NetworkSecurityConfig.java +++ b/core/java/android/security/net/config/NetworkSecurityConfig.java @@ -164,7 +164,8 @@ public final class NetworkSecurityConfig { * <p> * The default configuration has the following properties: * <ol> - * <li>Cleartext traffic is permitted for non-ephemeral apps.</li> + * <li>If the application targets API level 27 (Android O MR1) or lower then cleartext traffic + * is allowed by default.</li> * <li>Cleartext traffic is not permitted for ephemeral apps.</li> * <li>HSTS is not enforced.</li> * <li>No certificate pinning is used.</li> @@ -183,7 +184,8 @@ public final class NetworkSecurityConfig { // System certificate store, does not bypass static pins. .addCertificatesEntryRef( new CertificatesEntryRef(SystemCertificateSource.getInstance(), false)); - final boolean cleartextTrafficPermitted = info.targetSandboxVersion < 2; + final boolean cleartextTrafficPermitted = info.targetSdkVersion < Build.VERSION_CODES.P + && info.targetSandboxVersion < 2; builder.setCleartextTrafficPermitted(cleartextTrafficPermitted); // Applications targeting N and above must opt in into trusting the user added certificate // store. diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java index ac5c2e926874..4d2a9629c83a 100644 --- a/core/java/android/text/Layout.java +++ b/core/java/android/text/Layout.java @@ -1910,7 +1910,7 @@ public abstract class Layout { MeasuredText mt = MeasuredText.obtain(); TextLine tl = TextLine.obtain(); try { - mt.setPara(text, start, end, textDir, null); + mt.setPara(text, start, end, textDir); Directions directions; int dir; if (mt.mEasy) { diff --git a/core/java/android/text/MeasuredText.java b/core/java/android/text/MeasuredText.java index ffc44a72c357..3d9fba71db74 100644 --- a/core/java/android/text/MeasuredText.java +++ b/core/java/android/text/MeasuredText.java @@ -39,7 +39,6 @@ class MeasuredText { private int mPos; private TextPaint mWorkPaint; - private StaticLayout.Builder mBuilder; private MeasuredText() { mWorkPaint = new TextPaint(); @@ -82,7 +81,6 @@ class MeasuredText { void finish() { mText = null; - mBuilder = null; if (mLen > 1000) { mWidths = null; mChars = null; @@ -93,9 +91,7 @@ class MeasuredText { /** * Analyzes text for bidirectional runs. Allocates working buffers. */ - void setPara(CharSequence text, int start, int end, TextDirectionHeuristic textDir, - StaticLayout.Builder builder) { - mBuilder = builder; + void setPara(CharSequence text, int start, int end, TextDirectionHeuristic textDir) { mText = text; mTextStart = start; @@ -159,12 +155,12 @@ class MeasuredText { /** * Apply the style. * - * If StaticLyaout.Builder is not provided in setPara() method, this method measures the styled - * text width. - * If StaticLayout.Builder is provided in setPara() method, this method just passes the style - * information to native code by calling StaticLayout.Builder.addstyleRun() and returns 0. + * If nativeStaticLayoutPtr is 0, this method measures the styled text width. + * If nativeStaticLayoutPtr is not 0, this method just passes the style information to native + * code by calling StaticLayout.addstyleRun() and returns 0. */ - float addStyleRun(TextPaint paint, int len, Paint.FontMetricsInt fm) { + float addStyleRun(TextPaint paint, int len, Paint.FontMetricsInt fm, + long nativeStaticLayoutPtr) { if (fm != null) { paint.getFontMetricsInt(fm); } @@ -174,10 +170,10 @@ class MeasuredText { if (mEasy) { final boolean isRtl = mDir != Layout.DIR_LEFT_TO_RIGHT; - if (mBuilder == null) { + if (nativeStaticLayoutPtr == 0) { return paint.getTextRunAdvances(mChars, p, len, p, len, isRtl, mWidths, p); } else { - mBuilder.addStyleRun(paint, p, p + len, isRtl); + StaticLayout.addStyleRun(nativeStaticLayoutPtr, paint, p, p + len, isRtl); return 0.0f; // Builder.addStyleRun doesn't return the width. } } @@ -187,12 +183,12 @@ class MeasuredText { for (int q = p, i = p + 1, e = p + len;; ++i) { if (i == e || mLevels[i] != level) { final boolean isRtl = (level & 0x1) != 0; - if (mBuilder == null) { + if (nativeStaticLayoutPtr == 0) { totalAdvance += paint.getTextRunAdvances(mChars, q, i - q, q, i - q, isRtl, mWidths, q); } else { // Builder.addStyleRun doesn't return the width. - mBuilder.addStyleRun(paint, q, i, isRtl); + StaticLayout.addStyleRun(nativeStaticLayoutPtr, paint, q, i, isRtl); } if (i == e) { break; @@ -201,11 +197,15 @@ class MeasuredText { level = mLevels[i]; } } - return totalAdvance; // If mBuilder is null, the result is zero. + return totalAdvance; // If nativeStaticLayoutPtr is 0, the result is zero. + } + + float addStyleRun(TextPaint paint, int len, Paint.FontMetricsInt fm) { + return addStyleRun(paint, len, fm, 0 /* native ptr */); } float addStyleRun(TextPaint paint, MetricAffectingSpan[] spans, int len, - Paint.FontMetricsInt fm) { + Paint.FontMetricsInt fm, long nativeStaticLayoutPtr) { TextPaint workPaint = mWorkPaint; workPaint.set(paint); @@ -224,18 +224,18 @@ class MeasuredText { float wid; if (replacement == null) { - wid = addStyleRun(workPaint, len, fm); + wid = addStyleRun(workPaint, len, fm, nativeStaticLayoutPtr); } else { // Use original text. Shouldn't matter. wid = replacement.getSize(workPaint, mText, mTextStart + mPos, mTextStart + mPos + len, fm); - if (mBuilder == null) { + if (nativeStaticLayoutPtr == 0) { float[] w = mWidths; w[mPos] = wid; for (int i = mPos + 1, e = mPos + len; i < e; i++) w[i] = 0; } else { - mBuilder.addReplacementRun(paint, mPos, mPos + len, wid); + StaticLayout.addReplacementRun(nativeStaticLayoutPtr, paint, mPos, mPos + len, wid); } mPos += len; } @@ -253,6 +253,11 @@ class MeasuredText { return wid; } + float addStyleRun(TextPaint paint, MetricAffectingSpan[] spans, int len, + Paint.FontMetricsInt fm) { + return addStyleRun(paint, spans, len, fm, 0 /* native ptr */); + } + int breakText(int limit, boolean forwards, float width) { float[] w = mWidths; if (forwards) { diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index 5c60188db1e4..c0fc44fd8ee1 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -32,6 +32,9 @@ import android.util.Pools.SynchronizedPool; import com.android.internal.util.ArrayUtils; import com.android.internal.util.GrowingArrayUtils; +import dalvik.annotation.optimization.CriticalNative; +import dalvik.annotation.optimization.FastNative; + import java.util.Arrays; /** @@ -57,9 +60,7 @@ public class StaticLayout extends Layout { * default values. */ public final static class Builder { - private Builder() { - mNativePtr = nNewBuilder(); - } + private Builder() {} /** * Obtain a builder for constructing StaticLayout objects. @@ -116,13 +117,11 @@ public class StaticLayout extends Layout { b.mRightIndents = null; b.mLeftPaddings = null; b.mRightPaddings = null; - nFinishBuilder(b.mNativePtr); sPool.release(b); } // release any expensive state /* package */ void finish() { - nFinishBuilder(mNativePtr); mText = null; mPaint = null; mLeftIndents = null; @@ -405,32 +404,6 @@ public class StaticLayout extends Layout { } /** - * Measurement and break iteration is done in native code. The protocol for using - * the native code is as follows. - * - * For each paragraph, do a nSetupParagraph, which sets paragraph text, line width, tab - * stops, break strategy, and hyphenation frequency (and possibly other parameters in the - * future). - * - * Then, for each run within the paragraph: - * - one of the following, depending on the type of run: - * + addStyleRun (a text run, to be measured in native code) - * + addReplacementRun (a replacement run, width is given) - * - * Run nComputeLineBreaks() to obtain line breaks for the paragraph. - * - * After all paragraphs, call finish() to release expensive buffers. - */ - - /* package */ void addStyleRun(TextPaint paint, int start, int end, boolean isRtl) { - nAddStyleRun(mNativePtr, paint.getNativeInstance(), start, end, isRtl); - } - - /* package */ void addReplacementRun(TextPaint paint, int start, int end, float width) { - nAddReplacementRun(mNativePtr, paint.getNativeInstance(), start, end, width); - } - - /** * Build the {@link StaticLayout} after options have been set. * * <p>Note: the builder object must not be reused in any way after calling this @@ -446,17 +419,6 @@ public class StaticLayout extends Layout { return result; } - @Override - protected void finalize() throws Throwable { - try { - nFreeBuilder(mNativePtr); - } finally { - super.finalize(); - } - } - - /* package */ long mNativePtr; - private CharSequence mText; private int mStart; private int mEnd; @@ -694,270 +656,294 @@ public class StaticLayout extends Layout { indents = null; } - int paraEnd; - for (int paraStart = bufStart; paraStart <= bufEnd; paraStart = paraEnd) { - paraEnd = TextUtils.indexOf(source, CHAR_NEW_LINE, paraStart, bufEnd); - if (paraEnd < 0) - paraEnd = bufEnd; - else - paraEnd++; - - int firstWidthLineCount = 1; - int firstWidth = outerWidth; - int restWidth = outerWidth; - - LineHeightSpan[] chooseHt = null; - - if (spanned != null) { - LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd, - LeadingMarginSpan.class); - for (int i = 0; i < sp.length; i++) { - LeadingMarginSpan lms = sp[i]; - firstWidth -= sp[i].getLeadingMargin(true); - restWidth -= sp[i].getLeadingMargin(false); - - // LeadingMarginSpan2 is odd. The count affects all - // leading margin spans, not just this particular one - if (lms instanceof LeadingMarginSpan2) { - LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms; - firstWidthLineCount = Math.max(firstWidthLineCount, - lms2.getLeadingMarginLineCount()); - } + final long nativePtr = nInit( + b.mBreakStrategy, b.mHyphenationFrequency, + // TODO: Support more justification mode, e.g. letter spacing, stretching. + b.mJustificationMode != Layout.JUSTIFICATION_MODE_NONE, + indents, mLeftPaddings, mRightPaddings); + + try { + int paraEnd; + for (int paraStart = bufStart; paraStart <= bufEnd; paraStart = paraEnd) { + paraEnd = TextUtils.indexOf(source, CHAR_NEW_LINE, paraStart, bufEnd); + if (paraEnd < 0) { + paraEnd = bufEnd; + } else { + paraEnd++; } - chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class); - - if (chooseHt.length == 0) { - chooseHt = null; // So that out() would not assume it has any contents - } else { - if (chooseHtv == null || - chooseHtv.length < chooseHt.length) { - chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length); + int firstWidthLineCount = 1; + int firstWidth = outerWidth; + int restWidth = outerWidth; + + LineHeightSpan[] chooseHt = null; + + if (spanned != null) { + LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd, + LeadingMarginSpan.class); + for (int i = 0; i < sp.length; i++) { + LeadingMarginSpan lms = sp[i]; + firstWidth -= sp[i].getLeadingMargin(true); + restWidth -= sp[i].getLeadingMargin(false); + + // LeadingMarginSpan2 is odd. The count affects all + // leading margin spans, not just this particular one + if (lms instanceof LeadingMarginSpan2) { + LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms; + firstWidthLineCount = Math.max(firstWidthLineCount, + lms2.getLeadingMarginLineCount()); + } } - for (int i = 0; i < chooseHt.length; i++) { - int o = spanned.getSpanStart(chooseHt[i]); + chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class); - if (o < paraStart) { - // starts in this layout, before the - // current paragraph + if (chooseHt.length == 0) { + chooseHt = null; // So that out() would not assume it has any contents + } else { + if (chooseHtv == null || chooseHtv.length < chooseHt.length) { + chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length); + } - chooseHtv[i] = getLineTop(getLineForOffset(o)); - } else { - // starts in this paragraph + for (int i = 0; i < chooseHt.length; i++) { + int o = spanned.getSpanStart(chooseHt[i]); + + if (o < paraStart) { + // starts in this layout, before the + // current paragraph - chooseHtv[i] = v; + chooseHtv[i] = getLineTop(getLineForOffset(o)); + } else { + // starts in this paragraph + + chooseHtv[i] = v; + } } } } - } - measured.setPara(source, paraStart, paraEnd, textDir, b); - char[] chs = measured.mChars; - float[] widths = measured.mWidths; - byte[] chdirs = measured.mLevels; - int dir = measured.mDir; - boolean easy = measured.mEasy; - - // tab stop locations - int[] variableTabStops = null; - if (spanned != null) { - TabStopSpan[] spans = getParagraphSpans(spanned, paraStart, - paraEnd, TabStopSpan.class); - if (spans.length > 0) { - int[] stops = new int[spans.length]; - for (int i = 0; i < spans.length; i++) { - stops[i] = spans[i].getTabStop(); + measured.setPara(source, paraStart, paraEnd, textDir); + char[] chs = measured.mChars; + float[] widths = measured.mWidths; + byte[] chdirs = measured.mLevels; + int dir = measured.mDir; + boolean easy = measured.mEasy; + + // tab stop locations + int[] variableTabStops = null; + if (spanned != null) { + TabStopSpan[] spans = getParagraphSpans(spanned, paraStart, + paraEnd, TabStopSpan.class); + if (spans.length > 0) { + int[] stops = new int[spans.length]; + for (int i = 0; i < spans.length; i++) { + stops[i] = spans[i].getTabStop(); + } + Arrays.sort(stops, 0, stops.length); + variableTabStops = stops; } - Arrays.sort(stops, 0, stops.length); - variableTabStops = stops; - } - } - - nSetupParagraph(b.mNativePtr, chs, paraEnd - paraStart, - firstWidth, firstWidthLineCount, restWidth, - variableTabStops, TAB_INCREMENT, b.mBreakStrategy, b.mHyphenationFrequency, - // TODO: Support more justification mode, e.g. letter spacing, stretching. - b.mJustificationMode != Layout.JUSTIFICATION_MODE_NONE, - // TODO: indents and paddings don't need to get passed to native code for every - // paragraph. Pass them to native code just once. - indents, mLeftPaddings, mRightPaddings, mLineCount); - - // measurement has to be done before performing line breaking - // but we don't want to recompute fontmetrics or span ranges the - // second time, so we cache those and then use those stored values - int fmCacheCount = 0; - int spanEndCacheCount = 0; - for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) { - if (fmCacheCount * 4 >= fmCache.length) { - int[] grow = new int[fmCacheCount * 4 * 2]; - System.arraycopy(fmCache, 0, grow, 0, fmCacheCount * 4); - fmCache = grow; - } - - if (spanEndCacheCount >= spanEndCache.length) { - int[] grow = new int[spanEndCacheCount * 2]; - System.arraycopy(spanEndCache, 0, grow, 0, spanEndCacheCount); - spanEndCache = grow; - } - - if (spanned == null) { - spanEnd = paraEnd; - int spanLen = spanEnd - spanStart; - measured.addStyleRun(paint, spanLen, fm); - } else { - spanEnd = spanned.nextSpanTransition(spanStart, paraEnd, - MetricAffectingSpan.class); - int spanLen = spanEnd - spanStart; - MetricAffectingSpan[] spans = - spanned.getSpans(spanStart, spanEnd, MetricAffectingSpan.class); - spans = TextUtils.removeEmptySpans(spans, spanned, MetricAffectingSpan.class); - measured.addStyleRun(paint, spans, spanLen, fm); } - // the order of storage here (top, bottom, ascent, descent) has to match the code below - // where these values are retrieved - fmCache[fmCacheCount * 4 + 0] = fm.top; - fmCache[fmCacheCount * 4 + 1] = fm.bottom; - fmCache[fmCacheCount * 4 + 2] = fm.ascent; - fmCache[fmCacheCount * 4 + 3] = fm.descent; - fmCacheCount++; + // measurement has to be done before performing line breaking + // but we don't want to recompute fontmetrics or span ranges the + // second time, so we cache those and then use those stored values + int fmCacheCount = 0; + int spanEndCacheCount = 0; + for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) { + if (fmCacheCount * 4 >= fmCache.length) { + int[] grow = new int[fmCacheCount * 4 * 2]; + System.arraycopy(fmCache, 0, grow, 0, fmCacheCount * 4); + fmCache = grow; + } - spanEndCache[spanEndCacheCount] = spanEnd; - spanEndCacheCount++; - } + if (spanEndCacheCount >= spanEndCache.length) { + int[] grow = new int[spanEndCacheCount * 2]; + System.arraycopy(spanEndCache, 0, grow, 0, spanEndCacheCount); + spanEndCache = grow; + } - int breakCount = nComputeLineBreaks(b.mNativePtr, lineBreaks, lineBreaks.breaks, - lineBreaks.widths, lineBreaks.ascents, lineBreaks.descents, lineBreaks.flags, - lineBreaks.breaks.length, widths); - - final int[] breaks = lineBreaks.breaks; - final float[] lineWidths = lineBreaks.widths; - final float[] ascents = lineBreaks.ascents; - final float[] descents = lineBreaks.descents; - final int[] flags = lineBreaks.flags; - - final int remainingLineCount = mMaximumVisibleLineCount - mLineCount; - final boolean ellipsisMayBeApplied = ellipsize != null - && (ellipsize == TextUtils.TruncateAt.END - || (mMaximumVisibleLineCount == 1 - && ellipsize != TextUtils.TruncateAt.MARQUEE)); - if (0 < remainingLineCount && remainingLineCount < breakCount - && ellipsisMayBeApplied) { - // Calculate width and flag. - float width = 0; - int flag = 0; // XXX May need to also have starting hyphen edit - for (int i = remainingLineCount - 1; i < breakCount; i++) { - if (i == breakCount - 1) { - width += lineWidths[i]; + if (spanned == null) { + spanEnd = paraEnd; + int spanLen = spanEnd - spanStart; + measured.addStyleRun(paint, spanLen, fm, nativePtr); } else { - for (int j = (i == 0 ? 0 : breaks[i - 1]); j < breaks[i]; j++) { - width += widths[j]; - } + spanEnd = spanned.nextSpanTransition(spanStart, paraEnd, + MetricAffectingSpan.class); + int spanLen = spanEnd - spanStart; + MetricAffectingSpan[] spans = + spanned.getSpans(spanStart, spanEnd, MetricAffectingSpan.class); + spans = TextUtils.removeEmptySpans(spans, spanned, + MetricAffectingSpan.class); + measured.addStyleRun(paint, spans, spanLen, fm, nativePtr); } - flag |= flags[i] & TAB_MASK; - } - // Treat the last line and overflowed lines as a single line. - breaks[remainingLineCount - 1] = breaks[breakCount - 1]; - lineWidths[remainingLineCount - 1] = width; - flags[remainingLineCount - 1] = flag; - breakCount = remainingLineCount; - } + // the order of storage here (top, bottom, ascent, descent) has to match the + // code below where these values are retrieved + fmCache[fmCacheCount * 4 + 0] = fm.top; + fmCache[fmCacheCount * 4 + 1] = fm.bottom; + fmCache[fmCacheCount * 4 + 2] = fm.ascent; + fmCache[fmCacheCount * 4 + 3] = fm.descent; + fmCacheCount++; - // here is the offset of the starting character of the line we are currently measuring - int here = paraStart; - - int fmTop = 0, fmBottom = 0, fmAscent = 0, fmDescent = 0; - int fmCacheIndex = 0; - int spanEndCacheIndex = 0; - int breakIndex = 0; - for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) { - // retrieve end of span - spanEnd = spanEndCache[spanEndCacheIndex++]; - - // retrieve cached metrics, order matches above - fm.top = fmCache[fmCacheIndex * 4 + 0]; - fm.bottom = fmCache[fmCacheIndex * 4 + 1]; - fm.ascent = fmCache[fmCacheIndex * 4 + 2]; - fm.descent = fmCache[fmCacheIndex * 4 + 3]; - fmCacheIndex++; - - if (fm.top < fmTop) { - fmTop = fm.top; - } - if (fm.ascent < fmAscent) { - fmAscent = fm.ascent; - } - if (fm.descent > fmDescent) { - fmDescent = fm.descent; - } - if (fm.bottom > fmBottom) { - fmBottom = fm.bottom; + spanEndCache[spanEndCacheCount] = spanEnd; + spanEndCacheCount++; } - // skip breaks ending before current span range - while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) { - breakIndex++; + int breakCount = nComputeLineBreaks( + nativePtr, + + // Inputs + chs, + paraEnd - paraStart, + firstWidth, + firstWidthLineCount, + restWidth, + variableTabStops, + TAB_INCREMENT, + mLineCount, + + // Outputs + lineBreaks, + lineBreaks.breaks.length, + lineBreaks.breaks, + lineBreaks.widths, + lineBreaks.ascents, + lineBreaks.descents, + lineBreaks.flags, + widths); + + final int[] breaks = lineBreaks.breaks; + final float[] lineWidths = lineBreaks.widths; + final float[] ascents = lineBreaks.ascents; + final float[] descents = lineBreaks.descents; + final int[] flags = lineBreaks.flags; + + final int remainingLineCount = mMaximumVisibleLineCount - mLineCount; + final boolean ellipsisMayBeApplied = ellipsize != null + && (ellipsize == TextUtils.TruncateAt.END + || (mMaximumVisibleLineCount == 1 + && ellipsize != TextUtils.TruncateAt.MARQUEE)); + if (0 < remainingLineCount && remainingLineCount < breakCount + && ellipsisMayBeApplied) { + // Calculate width and flag. + float width = 0; + int flag = 0; // XXX May need to also have starting hyphen edit + for (int i = remainingLineCount - 1; i < breakCount; i++) { + if (i == breakCount - 1) { + width += lineWidths[i]; + } else { + for (int j = (i == 0 ? 0 : breaks[i - 1]); j < breaks[i]; j++) { + width += widths[j]; + } + } + flag |= flags[i] & TAB_MASK; + } + // Treat the last line and overflowed lines as a single line. + breaks[remainingLineCount - 1] = breaks[breakCount - 1]; + lineWidths[remainingLineCount - 1] = width; + flags[remainingLineCount - 1] = flag; + + breakCount = remainingLineCount; } - while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) { - int endPos = paraStart + breaks[breakIndex]; - - boolean moreChars = (endPos < bufEnd); - - final int ascent = fallbackLineSpacing - ? Math.min(fmAscent, Math.round(ascents[breakIndex])) - : fmAscent; - final int descent = fallbackLineSpacing - ? Math.max(fmDescent, Math.round(descents[breakIndex])) - : fmDescent; - v = out(source, here, endPos, - ascent, descent, fmTop, fmBottom, - v, spacingmult, spacingadd, chooseHt, chooseHtv, fm, flags[breakIndex], - needMultiply, chdirs, dir, easy, bufEnd, includepad, trackpad, - addLastLineSpacing, chs, widths, paraStart, ellipsize, - ellipsizedWidth, lineWidths[breakIndex], paint, moreChars); - - if (endPos < spanEnd) { - // preserve metrics for current span + // here is the offset of the starting character of the line we are currently + // measuring + int here = paraStart; + + int fmTop = 0, fmBottom = 0, fmAscent = 0, fmDescent = 0; + int fmCacheIndex = 0; + int spanEndCacheIndex = 0; + int breakIndex = 0; + for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) { + // retrieve end of span + spanEnd = spanEndCache[spanEndCacheIndex++]; + + // retrieve cached metrics, order matches above + fm.top = fmCache[fmCacheIndex * 4 + 0]; + fm.bottom = fmCache[fmCacheIndex * 4 + 1]; + fm.ascent = fmCache[fmCacheIndex * 4 + 2]; + fm.descent = fmCache[fmCacheIndex * 4 + 3]; + fmCacheIndex++; + + if (fm.top < fmTop) { fmTop = fm.top; - fmBottom = fm.bottom; + } + if (fm.ascent < fmAscent) { fmAscent = fm.ascent; + } + if (fm.descent > fmDescent) { fmDescent = fm.descent; - } else { - fmTop = fmBottom = fmAscent = fmDescent = 0; } + if (fm.bottom > fmBottom) { + fmBottom = fm.bottom; + } + + // skip breaks ending before current span range + while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) { + breakIndex++; + } + + while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) { + int endPos = paraStart + breaks[breakIndex]; + + boolean moreChars = (endPos < bufEnd); + + final int ascent = fallbackLineSpacing + ? Math.min(fmAscent, Math.round(ascents[breakIndex])) + : fmAscent; + final int descent = fallbackLineSpacing + ? Math.max(fmDescent, Math.round(descents[breakIndex])) + : fmDescent; + v = out(source, here, endPos, + ascent, descent, fmTop, fmBottom, + v, spacingmult, spacingadd, chooseHt, chooseHtv, fm, + flags[breakIndex], needMultiply, chdirs, dir, easy, bufEnd, + includepad, trackpad, addLastLineSpacing, chs, widths, paraStart, + ellipsize, ellipsizedWidth, lineWidths[breakIndex], paint, + moreChars); + + if (endPos < spanEnd) { + // preserve metrics for current span + fmTop = fm.top; + fmBottom = fm.bottom; + fmAscent = fm.ascent; + fmDescent = fm.descent; + } else { + fmTop = fmBottom = fmAscent = fmDescent = 0; + } - here = endPos; - breakIndex++; + here = endPos; + breakIndex++; - if (mLineCount >= mMaximumVisibleLineCount && mEllipsized) { - return; + if (mLineCount >= mMaximumVisibleLineCount && mEllipsized) { + return; + } } } - } - if (paraEnd == bufEnd) - break; - } + if (paraEnd == bufEnd) { + break; + } + } - if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE) && - mLineCount < mMaximumVisibleLineCount) { - measured.setPara(source, bufEnd, bufEnd, textDir, b); - - paint.getFontMetricsInt(fm); - - v = out(source, - bufEnd, bufEnd, fm.ascent, fm.descent, - fm.top, fm.bottom, - v, - spacingmult, spacingadd, null, - null, fm, 0, - needMultiply, measured.mLevels, measured.mDir, measured.mEasy, bufEnd, - includepad, trackpad, addLastLineSpacing, null, - null, bufStart, ellipsize, - ellipsizedWidth, 0, paint, false); + if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE) + && mLineCount < mMaximumVisibleLineCount) { + measured.setPara(source, bufEnd, bufEnd, textDir); + + paint.getFontMetricsInt(fm); + + v = out(source, + bufEnd, bufEnd, fm.ascent, fm.descent, + fm.top, fm.bottom, + v, + spacingmult, spacingadd, null, + null, fm, 0, + needMultiply, measured.mLevels, measured.mDir, measured.mEasy, bufEnd, + includepad, trackpad, addLastLineSpacing, null, + null, bufStart, ellipsize, + ellipsizedWidth, 0, paint, false); + } + } finally { + nFinish(nativePtr); } } @@ -1487,26 +1473,51 @@ public class StaticLayout extends Layout { mMaxLineHeight : super.getHeight(); } - private static native long nNewBuilder(); - private static native void nFreeBuilder(long nativePtr); - private static native void nFinishBuilder(long nativePtr); - - // Set up paragraph text and settings; done as one big method to minimize jni crossings - private static native void nSetupParagraph( - /* non zero */ long nativePtr, @NonNull char[] text, @IntRange(from = 0) int length, - @FloatRange(from = 0.0f) float firstWidth, @IntRange(from = 0) int firstWidthLineCount, - @FloatRange(from = 0.0f) float restWidth, @Nullable int[] variableTabStops, - int defaultTabStop, @BreakStrategy int breakStrategy, - @HyphenationFrequency int hyphenationFrequency, boolean isJustified, - @Nullable int[] indents, @Nullable int[] leftPaddings, @Nullable int[] rightPaddings, - @IntRange(from = 0) int indentsOffset); - - // TODO: Make this method CriticalNative once native code defers doing layouts. + /** + * Measurement and break iteration is done in native code. The protocol for using + * the native code is as follows. + * + * First, call nInit to setup native line breaker object. Then, for each paragraph, do the + * following: + * + * - Call one of the following methods for each run within the paragraph depending on the type + * of run: + * + addStyleRun (a text run, to be measured in native code) + * + addReplacementRun (a replacement run, width is given) + * + * - Run nComputeLineBreaks() to obtain line breaks for the paragraph. + * + * After all paragraphs, call finish() to release expensive buffers. + */ + + /* package */ static void addStyleRun(long nativePtr, TextPaint paint, int start, int end, + boolean isRtl) { + nAddStyleRun(nativePtr, paint.getNativeInstance(), start, end, isRtl); + } + + /* package */ static void addReplacementRun(long nativePtr, TextPaint paint, int start, int end, + float width) { + nAddReplacementRun(nativePtr, paint.getNativeInstance(), start, end, width); + } + + @FastNative + private static native long nInit( + @BreakStrategy int breakStrategy, + @HyphenationFrequency int hyphenationFrequency, + boolean isJustified, + @Nullable int[] indents, + @Nullable int[] leftPaddings, + @Nullable int[] rightPaddings); + + @CriticalNative + private static native void nFinish(long nativePtr); + + @CriticalNative private static native void nAddStyleRun( /* non-zero */ long nativePtr, /* non-zero */ long nativePaint, @IntRange(from = 0) int start, @IntRange(from = 0) int end, boolean isRtl); - // TODO: Make this method CriticalNative once native code defers doing layouts. + @CriticalNative private static native void nAddReplacementRun( /* non-zero */ long nativePtr, /* non-zero */ long nativePaint, @IntRange(from = 0) int start, @IntRange(from = 0) int end, @@ -1519,10 +1530,28 @@ public class StaticLayout extends Layout { // arrays do not have to be resized // The individual character widths will be returned in charWidths. The length of charWidths must // be at least the length of the text. - private static native int nComputeLineBreaks(long nativePtr, LineBreaks recycle, - int[] recycleBreaks, float[] recycleWidths, float[] recycleAscents, - float[] recycleDescents, int[] recycleFlags, int recycleLength, - float[] charWidths); + private static native int nComputeLineBreaks( + /* non zero */ long nativePtr, + + // Inputs + @NonNull char[] text, + @IntRange(from = 0) int length, + @FloatRange(from = 0.0f) float firstWidth, + @IntRange(from = 0) int firstWidthLineCount, + @FloatRange(from = 0.0f) float restWidth, + @Nullable int[] variableTabStops, + int defaultTabStop, + @IntRange(from = 0) int indentsOffset, + + // Outputs + @NonNull LineBreaks recycle, + @IntRange(from = 0) int recycleLength, + @NonNull int[] recycleBreaks, + @NonNull float[] recycleWidths, + @NonNull float[] recycleAscents, + @NonNull float[] recycleDescents, + @NonNull int[] recycleFlags, + @NonNull float[] charWidths); private int mLineCount; private int mTopPadding, mBottomPadding; diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java index 20c0ed87285a..86cc0141b0a4 100644 --- a/core/java/android/text/TextLine.java +++ b/core/java/android/text/TextLine.java @@ -28,6 +28,7 @@ import android.text.style.MetricAffectingSpan; import android.text.style.ReplacementSpan; import android.util.Log; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import java.util.ArrayList; @@ -44,7 +45,8 @@ import java.util.ArrayList; * * @hide */ -class TextLine { +@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) +public class TextLine { private static final boolean DEBUG = false; private TextPaint mPaint; @@ -82,7 +84,8 @@ class TextLine { * * @return an uninitialized TextLine */ - static TextLine obtain() { + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public static TextLine obtain() { TextLine tl; synchronized (sCached) { for (int i = sCached.length; --i >= 0;) { @@ -107,7 +110,8 @@ class TextLine { * @return null, as a convenience from clearing references to the provided * TextLine */ - static TextLine recycle(TextLine tl) { + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public static TextLine recycle(TextLine tl) { tl.mText = null; tl.mPaint = null; tl.mDirections = null; @@ -142,7 +146,8 @@ class TextLine { * @param hasTabs true if the line might contain tabs * @param tabStops the tabStops. Can be null. */ - void set(TextPaint paint, CharSequence text, int start, int limit, int dir, + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public void set(TextPaint paint, CharSequence text, int start, int limit, int dir, Directions directions, boolean hasTabs, TabStops tabStops) { mPaint = paint; mText = text; @@ -196,7 +201,8 @@ class TextLine { /** * Justify the line to the given width. */ - void justify(float justifyWidth) { + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public void justify(float justifyWidth) { int end = mLen; while (end > 0 && isLineEndSpace(mText.charAt(mStart + end - 1))) { end--; @@ -277,7 +283,8 @@ class TextLine { * @param fmi receives font metrics information, can be null * @return the signed width of the line */ - float metrics(FontMetricsInt fmi) { + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public float metrics(FontMetricsInt fmi) { return measure(mLen, false, fmi); } @@ -1165,23 +1172,18 @@ class TextLine { } private boolean isStretchableWhitespace(int ch) { - // TODO: Support other stretchable whitespace. (Bug: 34013491) - return ch == 0x0020 || ch == 0x00A0; - } - - private int nextStretchableSpace(int start, int end) { - for (int i = start; i < end; i++) { - final char c = mCharsValid ? mChars[i] : mText.charAt(i + mStart); - if (isStretchableWhitespace(c)) return i; - } - return end; + // TODO: Support NBSP and other stretchable whitespace (b/34013491 and b/68204709). + return ch == 0x0020; } /* Return the number of spaces in the text line, for the purpose of justification */ private int countStretchableSpaces(int start, int end) { int count = 0; - for (int i = start; i < end; i = nextStretchableSpace(i + 1, end)) { - count++; + for (int i = start; i < end; i++) { + final char c = mCharsValid ? mChars[i] : mText.charAt(i + mStart); + if (isStretchableWhitespace(c)) { + count++; + } } return count; } diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java index 68afeecfc996..cbdaa69b5aa0 100644 --- a/core/java/android/text/TextUtils.java +++ b/core/java/android/text/TextUtils.java @@ -1519,7 +1519,7 @@ public class TextUtils { } // XXX this is probably ok, but need to look at it more - tempMt.setPara(format, 0, format.length(), textDir, null); + tempMt.setPara(format, 0, format.length(), textDir); float moreWid = tempMt.addStyleRun(p, tempMt.mLen, null); if (w + moreWid <= avail) { @@ -1541,7 +1541,7 @@ public class TextUtils { private static float setPara(MeasuredText mt, TextPaint paint, CharSequence text, int start, int end, TextDirectionHeuristic textDir) { - mt.setPara(text, start, end, textDir, null); + mt.setPara(text, start, end, textDir); float width; Spanned sp = text instanceof Spanned ? (Spanned) text : null; diff --git a/core/java/android/view/TouchDelegate.java b/core/java/android/view/TouchDelegate.java index cf36f4360c3b..dc50fa1d6036 100644 --- a/core/java/android/view/TouchDelegate.java +++ b/core/java/android/view/TouchDelegate.java @@ -44,7 +44,7 @@ public class TouchDelegate { /** * mBounds inflated to include some slop. This rect is to track whether the motion events - * should be considered to be be within the delegate view. + * should be considered to be within the delegate view. */ private Rect mSlopBounds; @@ -64,14 +64,12 @@ public class TouchDelegate { public static final int BELOW = 2; /** - * The touchable region of the View extends to the left of its - * actual extent. + * The touchable region of the View extends to the left of its actual extent. */ public static final int TO_LEFT = 4; /** - * The touchable region of the View extends to the right of its - * actual extent. + * The touchable region of the View extends to the right of its actual extent. */ public static final int TO_RIGHT = 8; @@ -108,28 +106,24 @@ public class TouchDelegate { boolean handled = false; switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: - Rect bounds = mBounds; - - if (bounds.contains(x, y)) { - mDelegateTargeted = true; - sendToDelegate = true; - } - break; - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_MOVE: - sendToDelegate = mDelegateTargeted; - if (sendToDelegate) { - Rect slopBounds = mSlopBounds; - if (!slopBounds.contains(x, y)) { - hit = false; + case MotionEvent.ACTION_DOWN: + mDelegateTargeted = mBounds.contains(x, y); + sendToDelegate = mDelegateTargeted; + break; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_MOVE: + sendToDelegate = mDelegateTargeted; + if (sendToDelegate) { + Rect slopBounds = mSlopBounds; + if (!slopBounds.contains(x, y)) { + hit = false; + } } - } - break; - case MotionEvent.ACTION_CANCEL: - sendToDelegate = mDelegateTargeted; - mDelegateTargeted = false; - break; + break; + case MotionEvent.ACTION_CANCEL: + sendToDelegate = mDelegateTargeted; + mDelegateTargeted = false; + break; } if (sendToDelegate) { final View delegateView = mDelegateView; diff --git a/core/java/android/view/textclassifier/TextClassification.java b/core/java/android/view/textclassifier/TextClassification.java index 8c3b8a2e6b20..26d2141e3486 100644 --- a/core/java/android/view/textclassifier/TextClassification.java +++ b/core/java/android/view/textclassifier/TextClassification.java @@ -33,6 +33,52 @@ import java.util.List; /** * Information for generating a widget to handle classified text. + * + * <p>A TextClassification object contains icons, labels, onClickListeners and intents that may + * be used to build a widget that can be used to act on classified text. + * + * <p>e.g. building a view that, when clicked, shares the classified text with the preferred app: + * + * <pre>{@code + * // Called preferably outside the UiThread. + * TextClassification classification = textClassifier.classifyText(allText, 10, 25, null); + * + * // Called on the UiThread. + * Button button = new Button(context); + * button.setCompoundDrawablesWithIntrinsicBounds(classification.getIcon(), null, null, null); + * button.setText(classification.getLabel()); + * button.setOnClickListener(classification.getOnClickListener()); + * }</pre> + * + * <p>e.g. starting an action mode with menu items that can handle the classified text: + * + * <pre>{@code + * // Called preferably outside the UiThread. + * final TextClassification classification = textClassifier.classifyText(allText, 10, 25, null); + * + * // Called on the UiThread. + * view.startActionMode(new ActionMode.Callback() { + * + * public boolean onCreateActionMode(ActionMode mode, Menu menu) { + * for (int i = 0; i < classification.getActionCount(); i++) { + * if (thisAppHasPermissionToInvokeIntent(classification.getIntent(i))) { + * menu.add(Menu.NONE, i, 20, classification.getLabel(i)) + * .setIcon(classification.getIcon(i)) + * .setIntent(classification.getIntent(i)); + * } + * } + * return true; + * } + * + * public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + * context.startActivity(item.getIntent()); + * return true; + * } + * + * ... + * }); + * }</pre> + * */ public final class TextClassification { diff --git a/core/java/android/view/textclassifier/TextClassifier.java b/core/java/android/view/textclassifier/TextClassifier.java index c3601d9d32be..46dbd0e34a1c 100644 --- a/core/java/android/view/textclassifier/TextClassifier.java +++ b/core/java/android/view/textclassifier/TextClassifier.java @@ -29,8 +29,8 @@ import java.lang.annotation.RetentionPolicy; /** * Interface for providing text classification related features. * - * <p>Unless otherwise stated, methods of this interface are blocking operations and you should - * avoid calling them on the UI thread. + * <p>Unless otherwise stated, methods of this interface are blocking operations. + * Avoid calling them on the UI thread. */ public interface TextClassifier { @@ -75,8 +75,8 @@ public interface TextClassifier { }; /** - * Returns suggested text selection indices, recognized types and their associated confidence - * scores. The selections are ordered from highest to lowest scoring. + * Returns suggested text selection start and end indices, recognized entity types, and their + * associated confidence scores. The entity types are ordered from highest to lowest scoring. * * @param text text providing context for the selected text (which is specified * by the sub sequence starting at selectionStartIndex and ending at selectionEndIndex) diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java index 95cb4549cec1..48e427fd53b5 100644 --- a/core/java/android/webkit/WebViewFactory.java +++ b/core/java/android/webkit/WebViewFactory.java @@ -25,7 +25,6 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.Signature; -import android.os.Build; import android.os.RemoteException; import android.os.ServiceManager; import android.os.StrictMode; @@ -445,38 +444,17 @@ public final class WebViewFactory { } } - private static int prepareWebViewInSystemServer(String[] nativeLibraryPaths) { - if (DEBUG) Log.v(LOGTAG, "creating relro files"); - int numRelros = 0; - - // We must always trigger createRelRo regardless of the value of nativeLibraryPaths. Any - // unexpected values will be handled there to ensure that we trigger notifying any process - // waiting on relro creation. - if (Build.SUPPORTED_32_BIT_ABIS.length > 0) { - if (DEBUG) Log.v(LOGTAG, "Create 32 bit relro"); - WebViewLibraryLoader.createRelroFile(false /* is64Bit */, nativeLibraryPaths[0]); - numRelros++; - } - - if (Build.SUPPORTED_64_BIT_ABIS.length > 0) { - if (DEBUG) Log.v(LOGTAG, "Create 64 bit relro"); - WebViewLibraryLoader.createRelroFile(true /* is64Bit */, nativeLibraryPaths[1]); - numRelros++; - } - return numRelros; - } - /** * @hide */ public static int onWebViewProviderChanged(PackageInfo packageInfo) { - String[] nativeLibs = null; + int startedRelroProcesses = 0; ApplicationInfo originalAppInfo = new ApplicationInfo(packageInfo.applicationInfo); try { fixupStubApplicationInfo(packageInfo.applicationInfo, AppGlobals.getInitialApplication().getPackageManager()); - nativeLibs = WebViewLibraryLoader.updateWebViewZygoteVmSize(packageInfo); + startedRelroProcesses = WebViewLibraryLoader.prepareNativeLibraries(packageInfo); } catch (Throwable t) { // Log and discard errors at this stage as we must not crash the system server. Log.e(LOGTAG, "error preparing webview native library", t); @@ -484,7 +462,7 @@ public final class WebViewFactory { WebViewZygote.onWebViewProviderChanged(packageInfo, originalAppInfo); - return prepareWebViewInSystemServer(nativeLibs); + return startedRelroProcesses; } private static String WEBVIEW_UPDATE_SERVICE_NAME = "webviewupdate"; diff --git a/core/java/android/webkit/WebViewLibraryLoader.java b/core/java/android/webkit/WebViewLibraryLoader.java index fa1a3907b1f9..341c69fd2eba 100644 --- a/core/java/android/webkit/WebViewLibraryLoader.java +++ b/core/java/android/webkit/WebViewLibraryLoader.java @@ -16,6 +16,8 @@ package android.webkit; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.ActivityManagerInternal; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; @@ -26,6 +28,7 @@ import android.os.SystemProperties; import android.text.TextUtils; import android.util.Log; +import com.android.internal.annotations.VisibleForTesting; import com.android.server.LocalServices; import dalvik.system.VMRuntime; @@ -36,7 +39,11 @@ import java.util.Arrays; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; -class WebViewLibraryLoader { +/** + * @hide + */ +@VisibleForTesting +public class WebViewLibraryLoader { private static final String LOGTAG = WebViewLibraryLoader.class.getSimpleName(); private static final String CHROMIUM_WEBVIEW_NATIVE_RELRO_32 = @@ -94,7 +101,7 @@ class WebViewLibraryLoader { /** * Create a single relro file by invoking an isolated process that to do the actual work. */ - static void createRelroFile(final boolean is64Bit, String nativeLibraryPath) { + static void createRelroFile(final boolean is64Bit, @NonNull WebViewNativeLibrary nativeLib) { final String abi = is64Bit ? Build.SUPPORTED_64_BIT_ABIS[0] : Build.SUPPORTED_32_BIT_ABIS[0]; @@ -112,12 +119,12 @@ class WebViewLibraryLoader { }; try { - if (nativeLibraryPath == null) { + if (nativeLib == null || nativeLib.path == null) { throw new IllegalArgumentException( "Native library paths to the WebView RelRo process must not be null!"); } int pid = LocalServices.getService(ActivityManagerInternal.class).startIsolatedProcess( - RelroFileCreator.class.getName(), new String[] { nativeLibraryPath }, + RelroFileCreator.class.getName(), new String[] { nativeLib.path }, "WebViewLoader-" + abi, abi, Process.SHARED_RELRO_UID, crashHandler); if (pid <= 0) throw new Exception("Failed to start the relro file creator process"); } catch (Throwable t) { @@ -128,56 +135,77 @@ class WebViewLibraryLoader { } /** + * Perform preparations needed to allow loading WebView from an application. This method should + * be called whenever we change WebView provider. + * @return the number of relro processes started. + */ + static int prepareNativeLibraries(PackageInfo webviewPackageInfo) + throws WebViewFactory.MissingWebViewPackageException { + WebViewNativeLibrary nativeLib32bit = + getWebViewNativeLibrary(webviewPackageInfo, false /* is64bit */); + WebViewNativeLibrary nativeLib64bit = + getWebViewNativeLibrary(webviewPackageInfo, true /* is64bit */); + updateWebViewZygoteVmSize(nativeLib32bit, nativeLib64bit); + + return createRelros(nativeLib32bit, nativeLib64bit); + } + + /** + * @return the number of relro processes started. + */ + private static int createRelros(@Nullable WebViewNativeLibrary nativeLib32bit, + @Nullable WebViewNativeLibrary nativeLib64bit) { + if (DEBUG) Log.v(LOGTAG, "creating relro files"); + int numRelros = 0; + + if (Build.SUPPORTED_32_BIT_ABIS.length > 0) { + if (nativeLib32bit == null) { + Log.e(LOGTAG, "No 32-bit WebView library path, skipping relro creation."); + } else { + if (DEBUG) Log.v(LOGTAG, "Create 32 bit relro"); + createRelroFile(false /* is64Bit */, nativeLib32bit); + numRelros++; + } + } + + if (Build.SUPPORTED_64_BIT_ABIS.length > 0) { + if (nativeLib64bit == null) { + Log.e(LOGTAG, "No 64-bit WebView library path, skipping relro creation."); + } else { + if (DEBUG) Log.v(LOGTAG, "Create 64 bit relro"); + createRelroFile(true /* is64Bit */, nativeLib64bit); + numRelros++; + } + } + return numRelros; + } + + /** * * @return the native WebView libraries in the new WebView APK. */ - static String[] updateWebViewZygoteVmSize(PackageInfo packageInfo) + private static void updateWebViewZygoteVmSize( + @Nullable WebViewNativeLibrary nativeLib32bit, + @Nullable WebViewNativeLibrary nativeLib64bit) throws WebViewFactory.MissingWebViewPackageException { // Find the native libraries of the new WebView package, to change the size of the // memory region in the Zygote reserved for the library. - String[] nativeLibs = getWebViewNativeLibraryPaths(packageInfo); - if (nativeLibs != null) { - long newVmSize = 0L; - - for (String path : nativeLibs) { - if (path == null || TextUtils.isEmpty(path)) continue; - if (DEBUG) Log.d(LOGTAG, "Checking file size of " + path); - File f = new File(path); - if (f.exists()) { - newVmSize = Math.max(newVmSize, f.length()); - continue; - } - if (path.contains("!/")) { - String[] split = TextUtils.split(path, "!/"); - if (split.length == 2) { - try (ZipFile z = new ZipFile(split[0])) { - ZipEntry e = z.getEntry(split[1]); - if (e != null && e.getMethod() == ZipEntry.STORED) { - newVmSize = Math.max(newVmSize, e.getSize()); - continue; - } - } - catch (IOException e) { - Log.e(LOGTAG, "error reading APK file " + split[0] + ", ", e); - } - } - } - Log.e(LOGTAG, "error sizing load for " + path); - } + long newVmSize = 0L; - if (DEBUG) { - Log.v(LOGTAG, "Based on library size, need " + newVmSize - + " bytes of address space."); - } - // The required memory can be larger than the file on disk (due to .bss), and an - // upgraded version of the library will likely be larger, so always attempt to - // reserve twice as much as we think to allow for the library to grow during this - // boot cycle. - newVmSize = Math.max(2 * newVmSize, CHROMIUM_WEBVIEW_DEFAULT_VMSIZE_BYTES); - Log.d(LOGTAG, "Setting new address space to " + newVmSize); - setWebViewZygoteVmSize(newVmSize); + if (nativeLib32bit != null) newVmSize = Math.max(newVmSize, nativeLib32bit.size); + if (nativeLib64bit != null) newVmSize = Math.max(newVmSize, nativeLib64bit.size); + + if (DEBUG) { + Log.v(LOGTAG, "Based on library size, need " + newVmSize + + " bytes of address space."); } - return nativeLibs; + // The required memory can be larger than the file on disk (due to .bss), and an + // upgraded version of the library will likely be larger, so always attempt to + // reserve twice as much as we think to allow for the library to grow during this + // boot cycle. + newVmSize = Math.max(2 * newVmSize, CHROMIUM_WEBVIEW_DEFAULT_VMSIZE_BYTES); + Log.d(LOGTAG, "Setting new address space to " + newVmSize); + setWebViewZygoteVmSize(newVmSize); } /** @@ -227,64 +255,78 @@ class WebViewLibraryLoader { /** * Fetch WebView's native library paths from {@param packageInfo}. + * @hide */ - static String[] getWebViewNativeLibraryPaths(PackageInfo packageInfo) - throws WebViewFactory.MissingWebViewPackageException { + @Nullable + @VisibleForTesting + public static WebViewNativeLibrary getWebViewNativeLibrary(PackageInfo packageInfo, + boolean is64bit) throws WebViewFactory.MissingWebViewPackageException { ApplicationInfo ai = packageInfo.applicationInfo; final String nativeLibFileName = WebViewFactory.getWebViewLibrary(ai); - String path32; - String path64; - boolean primaryArchIs64bit = VMRuntime.is64BitAbi(ai.primaryCpuAbi); - if (!TextUtils.isEmpty(ai.secondaryCpuAbi)) { - // Multi-arch case. - if (primaryArchIs64bit) { - // Primary arch: 64-bit, secondary: 32-bit. - path64 = ai.nativeLibraryDir; - path32 = ai.secondaryNativeLibraryDir; - } else { - // Primary arch: 32-bit, secondary: 64-bit. - path64 = ai.secondaryNativeLibraryDir; - path32 = ai.nativeLibraryDir; - } - } else if (primaryArchIs64bit) { - // Single-arch 64-bit. - path64 = ai.nativeLibraryDir; - path32 = ""; - } else { - // Single-arch 32-bit. - path32 = ai.nativeLibraryDir; - path64 = ""; + String dir = getWebViewNativeLibraryDirectory(ai, is64bit /* 64bit */); + + WebViewNativeLibrary lib = findNativeLibrary(ai, nativeLibFileName, + is64bit ? Build.SUPPORTED_64_BIT_ABIS : Build.SUPPORTED_32_BIT_ABIS, dir); + + if (DEBUG) { + Log.v(LOGTAG, String.format("Native %d-bit lib: %s", is64bit ? 64 : 32, lib.path)); } + return lib; + } - // Form the full paths to the extracted native libraries. - // If libraries were not extracted, try load from APK paths instead. - if (!TextUtils.isEmpty(path32)) { - path32 += "/" + nativeLibFileName; - File f = new File(path32); - if (!f.exists()) { - path32 = getLoadFromApkPath(ai.sourceDir, - Build.SUPPORTED_32_BIT_ABIS, - nativeLibFileName); - } + /** + * @return the directory of the native WebView library with bitness {@param is64bit}. + * @hide + */ + @VisibleForTesting + public static String getWebViewNativeLibraryDirectory(ApplicationInfo ai, boolean is64bit) { + // Primary arch has the same bitness as the library we are looking for. + if (is64bit == VMRuntime.is64BitAbi(ai.primaryCpuAbi)) return ai.nativeLibraryDir; + + // Secondary arch has the same bitness as the library we are looking for. + if (!TextUtils.isEmpty(ai.secondaryCpuAbi)) { + return ai.secondaryNativeLibraryDir; } - if (!TextUtils.isEmpty(path64)) { - path64 += "/" + nativeLibFileName; - File f = new File(path64); - if (!f.exists()) { - path64 = getLoadFromApkPath(ai.sourceDir, - Build.SUPPORTED_64_BIT_ABIS, - nativeLibFileName); - } + + return ""; + } + + /** + * @return an object describing a native WebView library given the directory path of that + * library, or null if the library couldn't be found. + */ + @Nullable + private static WebViewNativeLibrary findNativeLibrary(ApplicationInfo ai, + String nativeLibFileName, String[] abiList, String libDirectory) + throws WebViewFactory.MissingWebViewPackageException { + if (TextUtils.isEmpty(libDirectory)) return null; + String libPath = libDirectory + "/" + nativeLibFileName; + File f = new File(libPath); + if (f.exists()) { + return new WebViewNativeLibrary(libPath, f.length()); + } else { + return getLoadFromApkPath(ai.sourceDir, abiList, nativeLibFileName); } + } + + /** + * @hide + */ + @VisibleForTesting + public static class WebViewNativeLibrary { + public final String path; + public final long size; - if (DEBUG) Log.v(LOGTAG, "Native 32-bit lib: " + path32 + ", 64-bit lib: " + path64); - return new String[] { path32, path64 }; + WebViewNativeLibrary(String path, long size) { + this.path = path; + this.size = size; + } } - private static String getLoadFromApkPath(String apkPath, - String[] abiList, - String nativeLibFileName) + private static WebViewNativeLibrary getLoadFromApkPath(String apkPath, + String[] abiList, + String nativeLibFileName) throws WebViewFactory.MissingWebViewPackageException { // Search the APK for a native library conforming to a listed ABI. try (ZipFile z = new ZipFile(apkPath)) { @@ -293,13 +335,13 @@ class WebViewLibraryLoader { ZipEntry e = z.getEntry(entry); if (e != null && e.getMethod() == ZipEntry.STORED) { // Return a path formatted for dlopen() load from APK. - return apkPath + "!/" + entry; + return new WebViewNativeLibrary(apkPath + "!/" + entry, e.getSize()); } } } catch (IOException e) { throw new WebViewFactory.MissingWebViewPackageException(e); } - return ""; + return null; } /** diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index af0959239b6f..e6da69dc95c7 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -129,7 +129,7 @@ import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; import java.util.List; - +import java.util.Map; /** * Helper class used by TextView to handle editable text views. @@ -163,9 +163,9 @@ public class Editor { private static final int MENU_ITEM_ORDER_REPLACE = 9; private static final int MENU_ITEM_ORDER_AUTOFILL = 10; private static final int MENU_ITEM_ORDER_PASTE_AS_PLAIN_TEXT = 11; + private static final int MENU_ITEM_ORDER_SECONDARY_ASSIST_ACTIONS_START = 50; private static final int MENU_ITEM_ORDER_PROCESS_TEXT_INTENT_ACTIONS_START = 100; - private static final float MAGNIFIER_ZOOM = 1.25f; @IntDef({MagnifierHandleTrigger.SELECTION_START, MagnifierHandleTrigger.SELECTION_END, MagnifierHandleTrigger.INSERTION}) @@ -476,17 +476,6 @@ public class Editor { stopTextActionModeWithPreservingSelection(); } - void invalidateMagnifier() { - final DisplayMetrics dm = mTextView.getResources().getDisplayMetrics(); - invalidateMagnifier(0, 0, dm.widthPixels, dm.heightPixels); - } - - void invalidateMagnifier(final float l, final float t, final float r, final float b) { - if (mMagnifier != null) { - mTextView.post(() -> mMagnifier.invalidate(new RectF(l, t, r, b))); - } - } - private void discardTextDisplayLists() { if (mTextRenderNodes != null) { for (int i = 0; i < mTextRenderNodes.length; i++) { @@ -3804,6 +3793,7 @@ public class Editor { private final RectF mSelectionBounds = new RectF(); private final boolean mHasSelection; private final int mHandleHeight; + private final Map<MenuItem, OnClickListener> mAssistClickHandlers = new HashMap<>(); public TextActionModeCallback(boolean hasSelection) { mHasSelection = hasSelection; @@ -3831,6 +3821,8 @@ public class Editor { @Override public boolean onCreateActionMode(ActionMode mode, Menu menu) { + mAssistClickHandlers.clear(); + mode.setTitle(null); mode.setSubtitle(null); mode.setTitleOptionalHint(true); @@ -3914,14 +3906,14 @@ public class Editor { updateSelectAllItem(menu); updateReplaceItem(menu); - updateAssistMenuItem(menu); + updateAssistMenuItems(menu); } @Override public boolean onPrepareActionMode(ActionMode mode, Menu menu) { updateSelectAllItem(menu); updateReplaceItem(menu); - updateAssistMenuItem(menu); + updateAssistMenuItems(menu); Callback customCallback = getCustomCallback(); if (customCallback != null) { @@ -3954,32 +3946,118 @@ public class Editor { } } - private void updateAssistMenuItem(Menu menu) { - menu.removeItem(TextView.ID_ASSIST); + private void updateAssistMenuItems(Menu menu) { + clearAssistMenuItems(menu); + if (!mTextView.isDeviceProvisioned()) { + return; + } final TextClassification textClassification = getSelectionActionModeHelper().getTextClassification(); - if (canAssist()) { - menu.add(TextView.ID_ASSIST, TextView.ID_ASSIST, MENU_ITEM_ORDER_ASSIST, - textClassification.getLabel()) - .setIcon(textClassification.getIcon()) - .setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); - mMetricsLogger.write( - new LogMaker(MetricsEvent.TEXT_SELECTION_MENU_ITEM_ASSIST) - .setType(MetricsEvent.TYPE_OPEN) - .setSubtype(textClassification.getLogType())); + final int count = textClassification != null ? textClassification.getActionCount() : 0; + for (int i = 0; i < count; i++) { + if (!isValidAssistMenuItem(i)) { + continue; + } + final int groupId = TextView.ID_ASSIST; + final int order = (i == 0) + ? MENU_ITEM_ORDER_ASSIST + : MENU_ITEM_ORDER_SECONDARY_ASSIST_ACTIONS_START + i; + final int id = (i == 0) ? TextView.ID_ASSIST : Menu.NONE; + final int showAsFlag = (i == 0) + ? MenuItem.SHOW_AS_ACTION_ALWAYS + : MenuItem.SHOW_AS_ACTION_NEVER; + final MenuItem item = menu.add( + groupId, id, order, textClassification.getLabel(i)) + .setIcon(textClassification.getIcon(i)) + .setIntent(textClassification.getIntent(i)); + item.setShowAsAction(showAsFlag); + mAssistClickHandlers.put(item, textClassification.getOnClickListener(i)); + if (id == TextView.ID_ASSIST) { + mMetricsLogger.write( + new LogMaker(MetricsEvent.TEXT_SELECTION_MENU_ITEM_ASSIST) + .setType(MetricsEvent.TYPE_OPEN) + .setSubtype(textClassification.getLogType())); + } + } + } + + private void clearAssistMenuItems(Menu menu) { + int i = 0; + while (i < menu.size()) { + final MenuItem menuItem = menu.getItem(i); + if (menuItem.getGroupId() == TextView.ID_ASSIST) { + menu.removeItem(menuItem.getItemId()); + continue; + } + i++; } } - private boolean canAssist() { + private boolean isValidAssistMenuItem(int index) { final TextClassification textClassification = getSelectionActionModeHelper().getTextClassification(); - return mTextView.isDeviceProvisioned() - && textClassification != null - && (textClassification.getIcon() != null - || !TextUtils.isEmpty(textClassification.getLabel())) - && (textClassification.getOnClickListener() != null - || (textClassification.getIntent() != null - && mTextView.getContext().canStartActivityForResult())); + if (!mTextView.isDeviceProvisioned() || textClassification == null + || index < 0 || index >= textClassification.getActionCount()) { + return false; + } + final Drawable icon = textClassification.getIcon(index); + final CharSequence label = textClassification.getLabel(index); + final boolean hasUi = icon != null || !TextUtils.isEmpty(label); + final OnClickListener onClick = textClassification.getOnClickListener(index); + final Intent intent = textClassification.getIntent(index); + final boolean hasAction = onClick != null || isSupportedIntent(intent); + return hasUi && hasAction; + } + + private boolean isSupportedIntent(Intent intent) { + if (intent == null) { + return false; + } + final Context context = mTextView.getContext(); + final ResolveInfo info = context.getPackageManager().resolveActivity(intent, 0); + final boolean samePackage = context.getPackageName().equals( + info.activityInfo.packageName); + if (samePackage) { + return true; + } + + final boolean exported = info.activityInfo.exported; + final boolean requiresPermission = info.activityInfo.permission != null; + final boolean hasPermission = !requiresPermission + || context.checkSelfPermission(info.activityInfo.permission) + == PackageManager.PERMISSION_GRANTED; + return exported && hasPermission; + } + + private boolean onAssistMenuItemClicked(MenuItem assistMenuItem) { + Preconditions.checkArgument(assistMenuItem.getGroupId() == TextView.ID_ASSIST); + + final TextClassification textClassification = + getSelectionActionModeHelper().getTextClassification(); + if (!mTextView.isDeviceProvisioned() || textClassification == null) { + // No textClassification result to handle the click. Eat the click. + return true; + } + + OnClickListener onClickListener = mAssistClickHandlers.get(assistMenuItem); + if (onClickListener == null) { + final Intent intent = assistMenuItem.getIntent(); + if (intent != null) { + onClickListener = TextClassification.createStartActivityOnClickListener( + mTextView.getContext(), intent); + } + } + if (onClickListener != null) { + onClickListener.onClick(mTextView); + stopTextActionMode(); + if (assistMenuItem.getItemId() == TextView.ID_ASSIST) { + mMetricsLogger.action( + MetricsEvent.ACTION_TEXT_SELECTION_MENU_ITEM_ASSIST, + textClassification.getLogType()); + } + } + // We tried our best. + return true; } @Override @@ -3993,25 +4071,7 @@ public class Editor { if (customCallback != null && customCallback.onActionItemClicked(mode, item)) { return true; } - final TextClassification textClassification = - getSelectionActionModeHelper().getTextClassification(); - if (TextView.ID_ASSIST == item.getItemId() && textClassification != null) { - final OnClickListener onClickListener = - textClassification.getOnClickListener(); - if (onClickListener != null) { - onClickListener.onClick(mTextView); - } else { - final Intent intent = textClassification.getIntent(); - if (intent != null) { - TextClassification.createStartActivityOnClickListener( - mTextView.getContext(), intent) - .onClick(mTextView); - } - } - mMetricsLogger.action( - MetricsEvent.ACTION_TEXT_SELECTION_MENU_ITEM_ASSIST, - textClassification.getLogType()); - stopTextActionMode(); + if (item.getGroupId() == TextView.ID_ASSIST && onAssistMenuItemClicked(item)) { return true; } return mTextView.onTextContextMenuItem(item.getItemId()); @@ -4040,6 +4100,8 @@ public class Editor { if (mSelectionModifierCursorController != null) { mSelectionModifierCursorController.hide(); } + + mAssistClickHandlers.clear(); } @Override @@ -4550,17 +4612,15 @@ public class Editor { final Layout layout = mTextView.getLayout(); final int lineNumber = layout.getLineForOffset(offset); // Horizontally snap to character offset. - final float xPosInView = getHorizontal(mTextView.getLayout(), offset); + final float xPosInView = getHorizontal(mTextView.getLayout(), offset) + + mTextView.getTotalPaddingLeft() - mTextView.getScrollX(); // Vertically snap to middle of current line. final float yPosInView = (mTextView.getLayout().getLineTop(lineNumber) - + mTextView.getLayout().getLineBottom(lineNumber)) / 2.0f; - final int[] coordinatesOnScreen = new int[2]; - mTextView.getLocationOnScreen(coordinatesOnScreen); - final float centerXOnScreen = mTextView.convertViewToScreenCoord(xPosInView, true); - final float centerYOnScreen = mTextView.convertViewToScreenCoord(yPosInView, false); + + mTextView.getLayout().getLineBottom(lineNumber)) / 2.0f + + mTextView.getTotalPaddingTop() - mTextView.getScrollY(); suspendBlink(); - mMagnifier.show(centerXOnScreen, centerYOnScreen, MAGNIFIER_ZOOM); + mMagnifier.show(xPosInView, yPosInView); } protected final void dismissMagnifier() { @@ -6573,6 +6633,9 @@ public class Editor { private void loadSupportedActivities() { mSupportedActivities.clear(); + if (!mContext.canStartActivityForResult()) { + return; + } PackageManager packageManager = mTextView.getContext().getPackageManager(); List<ResolveInfo> unfiltered = packageManager.queryIntentActivities(createProcessTextIntent(), 0); diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index ce805526e4ad..d9bc51fffd6a 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -9219,36 +9219,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } - @Override - public void invalidate() { - super.invalidate(); - - if (mEditor != null) { - mEditor.invalidateMagnifier(); - } - } - - @Override - public void invalidate(int l, int t, int r, int b) { - super.invalidate(l, t, r, b); - - if (mEditor != null) { - mEditor.invalidateMagnifier( - convertViewToScreenCoord(l, true /* isHorizontal */), - convertViewToScreenCoord(t, false /* isHorizontal */), - convertViewToScreenCoord(r, true /* isHorizontal */), - convertViewToScreenCoord(b, false /* isHorizontal */)); - } - } - - float convertViewToScreenCoord(float viewCoord, boolean isHorizontal) { - final int[] coordinatesOnScreen = new int[2]; - getLocationOnScreen(coordinatesOnScreen); - return isHorizontal - ? viewCoord + getTotalPaddingLeft() - getScrollX() + coordinatesOnScreen[0] - : viewCoord + getTotalPaddingTop() - getScrollY() + coordinatesOnScreen[1]; - } - /** * @return whether or not the cursor is visible (assuming this TextView is editable) * diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 239d7091ed3d..f0d05da2ec5e 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -63,6 +63,7 @@ import android.util.Slog; import android.util.SparseArray; import android.util.SparseIntArray; import android.util.SparseLongArray; +import android.util.StatsLog; import android.util.TimeUtils; import android.util.Xml; import android.view.Display; @@ -4030,6 +4031,7 @@ public class BatteryStatsImpl extends BatteryStats { } addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_LONG_WAKE_LOCK_START, historyName, uid); + StatsLog.write(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED, uid, name, historyName, 1); } public void noteLongPartialWakelockFinish(String name, String historyName, int uid) { @@ -4045,6 +4047,7 @@ public class BatteryStatsImpl extends BatteryStats { } addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_LONG_WAKE_LOCK_FINISH, historyName, uid); + StatsLog.write(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED, uid, name, historyName, 0); } void aggregateLastWakeupUptimeLocked(long uptimeMs) { @@ -4052,6 +4055,7 @@ public class BatteryStatsImpl extends BatteryStats { long deltaUptime = uptimeMs - mLastWakeupUptimeMs; SamplingTimer timer = getWakeupReasonTimerLocked(mLastWakeupReason); timer.add(deltaUptime * 1000, 1); // time in in microseconds + StatsLog.write(StatsLog.KERNEL_WAKEUP_REPORTED, mLastWakeupReason, deltaUptime * 1000); mLastWakeupReason = null; } } @@ -4403,6 +4407,7 @@ public class BatteryStatsImpl extends BatteryStats { mPowerSaveModeEnabledTimer.stopRunningLocked(elapsedRealtime); } addHistoryRecordLocked(elapsedRealtime, uptime); + StatsLog.write(StatsLog.BATTERY_SAVER_MODE_STATE_CHANGED, enabled ? 1 : 0); } } @@ -4629,6 +4634,7 @@ public class BatteryStatsImpl extends BatteryStats { if (DEBUG_HISTORY) Slog.v(TAG, "Signal strength " + strengthBin + " to: " + Integer.toHexString(mHistoryCur.states)); newHistory = true; + StatsLog.write(StatsLog.PHONE_SIGNAL_STRENGTH_CHANGED, strengthBin); } else { stopAllPhoneSignalStrengthTimersLocked(-1); } @@ -5184,6 +5190,7 @@ public class BatteryStatsImpl extends BatteryStats { if (strengthBin >= 0) { if (!mWifiSignalStrengthsTimer[strengthBin].isRunningLocked()) { mWifiSignalStrengthsTimer[strengthBin].startRunningLocked(elapsedRealtime); + StatsLog.write(StatsLog.WIFI_SIGNAL_STRENGTH_CHANGED, strengthBin); } mHistoryCur.states2 = (mHistoryCur.states2&~HistoryItem.STATE2_WIFI_SIGNAL_STRENGTH_MASK) @@ -6049,6 +6056,8 @@ public class BatteryStatsImpl extends BatteryStats { mBsi.mFullWifiLockTimers, mBsi.mOnBatteryTimeBase); } mFullWifiLockTimer.startRunningLocked(elapsedRealtimeMs); + // TODO(statsd): Possibly use a worksource instead of a uid. + StatsLog.write(StatsLog.WIFI_LOCK_STATE_CHANGED, getUid(), 1); } } @@ -6057,6 +6066,10 @@ public class BatteryStatsImpl extends BatteryStats { if (mFullWifiLockOut) { mFullWifiLockOut = false; mFullWifiLockTimer.stopRunningLocked(elapsedRealtimeMs); + if (!mFullWifiLockTimer.isRunningLocked()) { // only tell statsd if truly stopped + // TODO(statsd): Possibly use a worksource instead of a uid. + StatsLog.write(StatsLog.WIFI_LOCK_STATE_CHANGED, getUid(), 0); + } } } @@ -6070,6 +6083,8 @@ public class BatteryStatsImpl extends BatteryStats { mOnBatteryBackgroundTimeBase); } mWifiScanTimer.startRunningLocked(elapsedRealtimeMs); + // TODO(statsd): Possibly use a worksource instead of a uid. + StatsLog.write(StatsLog.WIFI_SCAN_STATE_CHANGED, getUid(), 1); } } @@ -6078,6 +6093,10 @@ public class BatteryStatsImpl extends BatteryStats { if (mWifiScanStarted) { mWifiScanStarted = false; mWifiScanTimer.stopRunningLocked(elapsedRealtimeMs); + if (!mWifiScanTimer.isRunningLocked()) { // only tell statsd if truly stopped + // TODO(statsd): Possibly use a worksource instead of a uid. + StatsLog.write(StatsLog.WIFI_SCAN_STATE_CHANGED, getUid(), 0); + } } } @@ -6180,17 +6199,25 @@ public class BatteryStatsImpl extends BatteryStats { public void noteAudioTurnedOnLocked(long elapsedRealtimeMs) { createAudioTurnedOnTimerLocked().startRunningLocked(elapsedRealtimeMs); + // TODO(statsd): Possibly use a worksource instead of a uid. + StatsLog.write(StatsLog.AUDIO_STATE_CHANGED, getUid(), 1); } public void noteAudioTurnedOffLocked(long elapsedRealtimeMs) { if (mAudioTurnedOnTimer != null) { mAudioTurnedOnTimer.stopRunningLocked(elapsedRealtimeMs); + if (!mAudioTurnedOnTimer.isRunningLocked()) { // only tell statsd if truly stopped + // TODO(statsd): Possibly use a worksource instead of a uid. + StatsLog.write(StatsLog.AUDIO_STATE_CHANGED, getUid(), 0); + } } } public void noteResetAudioLocked(long elapsedRealtimeMs) { if (mAudioTurnedOnTimer != null) { mAudioTurnedOnTimer.stopAllRunningLocked(elapsedRealtimeMs); + // TODO(statsd): Possibly use a worksource instead of a uid. + StatsLog.write(StatsLog.AUDIO_STATE_CHANGED, getUid(), 0); } } @@ -6204,17 +6231,25 @@ public class BatteryStatsImpl extends BatteryStats { public void noteVideoTurnedOnLocked(long elapsedRealtimeMs) { createVideoTurnedOnTimerLocked().startRunningLocked(elapsedRealtimeMs); + // TODO(statsd): Possibly use a worksource instead of a uid. + StatsLog.write(StatsLog.MEDIA_CODEC_ACTIVITY_CHANGED, getUid(), 1); } public void noteVideoTurnedOffLocked(long elapsedRealtimeMs) { if (mVideoTurnedOnTimer != null) { mVideoTurnedOnTimer.stopRunningLocked(elapsedRealtimeMs); + if (!mVideoTurnedOnTimer.isRunningLocked()) { // only tell statsd if truly stopped + // TODO(statsd): Possibly use a worksource instead of a uid. + StatsLog.write(StatsLog.MEDIA_CODEC_ACTIVITY_CHANGED, getUid(), 0); + } } } public void noteResetVideoLocked(long elapsedRealtimeMs) { if (mVideoTurnedOnTimer != null) { mVideoTurnedOnTimer.stopAllRunningLocked(elapsedRealtimeMs); + // TODO(statsd): Possibly use a worksource instead of a uid. + StatsLog.write(StatsLog.MEDIA_CODEC_ACTIVITY_CHANGED, getUid(), 0); } } @@ -6228,17 +6263,25 @@ public class BatteryStatsImpl extends BatteryStats { public void noteFlashlightTurnedOnLocked(long elapsedRealtimeMs) { createFlashlightTurnedOnTimerLocked().startRunningLocked(elapsedRealtimeMs); + // TODO(statsd): Possibly use a worksource instead of a uid. + StatsLog.write(StatsLog.FLASHLIGHT_STATE_CHANGED, getUid(), 1); } public void noteFlashlightTurnedOffLocked(long elapsedRealtimeMs) { if (mFlashlightTurnedOnTimer != null) { mFlashlightTurnedOnTimer.stopRunningLocked(elapsedRealtimeMs); + if (!mFlashlightTurnedOnTimer.isRunningLocked()) { + // TODO(statsd): Possibly use a worksource instead of a uid. + StatsLog.write(StatsLog.FLASHLIGHT_STATE_CHANGED, getUid(), 0); + } } } public void noteResetFlashlightLocked(long elapsedRealtimeMs) { if (mFlashlightTurnedOnTimer != null) { mFlashlightTurnedOnTimer.stopAllRunningLocked(elapsedRealtimeMs); + // TODO(statsd): Possibly use a worksource instead of a uid. + StatsLog.write(StatsLog.FLASHLIGHT_STATE_CHANGED, getUid(), 0); } } @@ -6252,17 +6295,25 @@ public class BatteryStatsImpl extends BatteryStats { public void noteCameraTurnedOnLocked(long elapsedRealtimeMs) { createCameraTurnedOnTimerLocked().startRunningLocked(elapsedRealtimeMs); + // TODO(statsd): Possibly use a worksource instead of a uid. + StatsLog.write(StatsLog.CAMERA_STATE_CHANGED, getUid(), 1); } public void noteCameraTurnedOffLocked(long elapsedRealtimeMs) { if (mCameraTurnedOnTimer != null) { mCameraTurnedOnTimer.stopRunningLocked(elapsedRealtimeMs); + if (!mCameraTurnedOnTimer.isRunningLocked()) { // only tell statsd if truly stopped + // TODO(statsd): Possibly use a worksource instead of a uid. + StatsLog.write(StatsLog.CAMERA_STATE_CHANGED, getUid(), 0); + } } } public void noteResetCameraLocked(long elapsedRealtimeMs) { if (mCameraTurnedOnTimer != null) { mCameraTurnedOnTimer.stopAllRunningLocked(elapsedRealtimeMs); + // TODO(statsd): Possibly use a worksource instead of a uid. + StatsLog.write(StatsLog.CAMERA_STATE_CHANGED, getUid(), 0); } } @@ -6311,26 +6362,42 @@ public class BatteryStatsImpl extends BatteryStats { public void noteBluetoothScanStartedLocked(long elapsedRealtimeMs, boolean isUnoptimized) { createBluetoothScanTimerLocked().startRunningLocked(elapsedRealtimeMs); + // TODO(statsd): Possibly use a worksource instead of a uid. + StatsLog.write(StatsLog.BLE_SCAN_STATE_CHANGED, getUid(), 1); if (isUnoptimized) { createBluetoothUnoptimizedScanTimerLocked().startRunningLocked(elapsedRealtimeMs); + // TODO(statsd): Possibly use a worksource instead of a uid. + StatsLog.write(StatsLog.BLE_UNOPTIMIZED_SCAN_STATE_CHANGED, getUid(), 1); } } public void noteBluetoothScanStoppedLocked(long elapsedRealtimeMs, boolean isUnoptimized) { if (mBluetoothScanTimer != null) { mBluetoothScanTimer.stopRunningLocked(elapsedRealtimeMs); + if (!mBluetoothScanTimer.isRunningLocked()) { // only tell statsd if truly stopped + // TODO(statsd): Possibly use a worksource instead of a uid. + StatsLog.write(StatsLog.BLE_SCAN_STATE_CHANGED, getUid(), 0); + } } if (isUnoptimized && mBluetoothUnoptimizedScanTimer != null) { mBluetoothUnoptimizedScanTimer.stopRunningLocked(elapsedRealtimeMs); + if (!mBluetoothUnoptimizedScanTimer.isRunningLocked()) { + // TODO(statsd): Possibly use a worksource instead of a uid. + StatsLog.write(StatsLog.BLE_UNOPTIMIZED_SCAN_STATE_CHANGED, getUid(), 0); + } } } public void noteResetBluetoothScanLocked(long elapsedRealtimeMs) { if (mBluetoothScanTimer != null) { mBluetoothScanTimer.stopAllRunningLocked(elapsedRealtimeMs); + // TODO(statsd): Possibly use a worksource instead of a uid. + StatsLog.write(StatsLog.BLE_SCAN_STATE_CHANGED, getUid(), 0); } if (mBluetoothUnoptimizedScanTimer != null) { mBluetoothUnoptimizedScanTimer.stopAllRunningLocked(elapsedRealtimeMs); + // TODO(statsd): Possibly use a worksource instead of a uid. + StatsLog.write(StatsLog.BLE_UNOPTIMIZED_SCAN_STATE_CHANGED, getUid(), 0); } } @@ -6352,6 +6419,9 @@ public class BatteryStatsImpl extends BatteryStats { createBluetoothScanResultCounterLocked().addAtomic(numNewResults); // Uses background timebase, so the count will only be incremented if uid in background. createBluetoothScanResultBgCounterLocked().addAtomic(numNewResults); + // TODO(statsd): Possibly use a worksource instead of a uid. + // TODO(statsd): This could be in AppScanStats instead, if desired. + StatsLog.write(StatsLog.BLE_SCAN_RESULT_RECEIVED, getUid(), numNewResults); } @Override @@ -8725,6 +8795,8 @@ public class BatteryStatsImpl extends BatteryStats { DualTimer t = mSyncStats.startObject(name); if (t != null) { t.startRunningLocked(elapsedRealtimeMs); + // TODO(statsd): Possibly use a worksource instead of a uid. + StatsLog.write(StatsLog.SYNC_STATE_CHANGED, getUid(), name, 1); } } @@ -8732,6 +8804,10 @@ public class BatteryStatsImpl extends BatteryStats { DualTimer t = mSyncStats.stopObject(name); if (t != null) { t.stopRunningLocked(elapsedRealtimeMs); + if (!t.isRunningLocked()) { // only tell statsd if truly stopped + // TODO(statsd): Possibly use a worksource instead of a uid. + StatsLog.write(StatsLog.SYNC_STATE_CHANGED, getUid(), name, 0); + } } } @@ -8739,6 +8815,8 @@ public class BatteryStatsImpl extends BatteryStats { DualTimer t = mJobStats.startObject(name); if (t != null) { t.startRunningLocked(elapsedRealtimeMs); + // TODO(statsd): Possibly use a worksource instead of a uid. + StatsLog.write(StatsLog.SCHEDULED_JOB_STATE_CHANGED, getUid(), name, 1); } } @@ -8746,6 +8824,10 @@ public class BatteryStatsImpl extends BatteryStats { DualTimer t = mJobStats.stopObject(name); if (t != null) { t.stopRunningLocked(elapsedRealtimeMs); + if (!t.isRunningLocked()) { // only tell statsd if truly stopped + // TODO(statsd): Possibly use a worksource instead of a uid. + StatsLog.write(StatsLog.SCHEDULED_JOB_STATE_CHANGED, getUid(), name, 0); + } } if (mBsi.mOnBatteryTimeBase.isRunning()) { SparseIntArray types = mJobCompletions.get(name); @@ -8809,9 +8891,13 @@ public class BatteryStatsImpl extends BatteryStats { Wakelock wl = mWakelockStats.startObject(name); if (wl != null) { getWakelockTimerLocked(wl, type).startRunningLocked(elapsedRealtimeMs); + // TODO(statsd): Hopefully use a worksource instead of a uid (so move elsewhere) + StatsLog.write(StatsLog.WAKELOCK_STATE_CHANGED, getUid(), type, name, 1); } if (type == WAKE_TYPE_PARTIAL) { createAggregatedPartialWakelockTimerLocked().startRunningLocked(elapsedRealtimeMs); + // TODO(statsd): Possibly use a worksource instead of a uid. + StatsLog.write(StatsLog.UID_WAKELOCK_STATE_CHANGED, getUid(), type, 1); if (pid >= 0) { Pid p = getPidStatsLocked(pid); if (p.mWakeNesting++ == 0) { @@ -8824,11 +8910,21 @@ public class BatteryStatsImpl extends BatteryStats { public void noteStopWakeLocked(int pid, String name, int type, long elapsedRealtimeMs) { Wakelock wl = mWakelockStats.stopObject(name); if (wl != null) { - getWakelockTimerLocked(wl, type).stopRunningLocked(elapsedRealtimeMs); + StopwatchTimer wlt = getWakelockTimerLocked(wl, type); + wlt.stopRunningLocked(elapsedRealtimeMs); + if (!wlt.isRunningLocked()) { // only tell statsd if truly stopped + // TODO(statsd): Possibly use a worksource instead of a uid. + StatsLog.write(StatsLog.WAKELOCK_STATE_CHANGED, getUid(), type, name, 0); + } } if (type == WAKE_TYPE_PARTIAL) { if (mAggregatedPartialWakelockTimer != null) { mAggregatedPartialWakelockTimer.stopRunningLocked(elapsedRealtimeMs); + if (!mAggregatedPartialWakelockTimer.isRunningLocked()) { + // TODO(statsd): Possibly use a worksource instead of a uid. + StatsLog.write(StatsLog.UID_WAKELOCK_STATE_CHANGED, getUid(), type, + 0); + } } if (pid >= 0) { Pid p = mPids.get(pid); @@ -8852,6 +8948,12 @@ public class BatteryStatsImpl extends BatteryStats { public void noteStartSensor(int sensor, long elapsedRealtimeMs) { DualTimer t = getSensorTimerLocked(sensor, /* create= */ true); t.startRunningLocked(elapsedRealtimeMs); + // TODO(statsd): Possibly use a worksource instead of a uid. + if (sensor == Sensor.GPS) { + StatsLog.write(StatsLog.GPS_SCAN_STATE_CHANGED, getUid(), 1); + } else { + StatsLog.write(StatsLog.SENSOR_STATE_CHANGED, getUid(), sensor, 1); + } } public void noteStopSensor(int sensor, long elapsedRealtimeMs) { @@ -8859,6 +8961,14 @@ public class BatteryStatsImpl extends BatteryStats { DualTimer t = getSensorTimerLocked(sensor, false); if (t != null) { t.stopRunningLocked(elapsedRealtimeMs); + if (!t.isRunningLocked()) { // only tell statsd if truly stopped + // TODO(statsd): Possibly use a worksource instead of a uid. + if (sensor == Sensor.GPS) { + StatsLog.write(StatsLog.GPS_SCAN_STATE_CHANGED, getUid(), 0); + } else { + StatsLog.write(StatsLog.SENSOR_STATE_CHANGED, getUid(), sensor, 0); + } + } } } @@ -11068,11 +11178,15 @@ public class BatteryStatsImpl extends BatteryStats { // This should probably be exposed in the API, though it's not critical public static final int BATTERY_PLUGGED_NONE = 0; - public void setBatteryStateLocked(int status, int health, int plugType, int level, - int temp, int volt, int chargeUAh, int chargeFullUAh) { + public void setBatteryStateLocked(final int status, final int health, final int plugType, + final int level, /* not final */ int temp, final int volt, final int chargeUAh, + final int chargeFullUAh) { // Temperature is encoded without the signed bit, so clamp any negative temperatures to 0. temp = Math.max(0, temp); + reportChangesToStatsLog(mHaveBatteryLevel ? mHistoryCur : null, + status, plugType, level, temp); + final boolean onBattery = plugType == BATTERY_PLUGGED_NONE; final long uptime = mClocks.uptimeMillis(); final long elapsedRealtime = mClocks.elapsedRealtime(); @@ -11249,6 +11363,24 @@ public class BatteryStatsImpl extends BatteryStats { mMaxLearnedBatteryCapacity = Math.max(mMaxLearnedBatteryCapacity, chargeFullUAh); } + // Inform StatsLog of setBatteryState changes. + // If this is the first reporting, pass in recentPast == null. + private void reportChangesToStatsLog(HistoryItem recentPast, + final int status, final int plugType, final int level, final int temp) { + + if (recentPast == null || recentPast.batteryStatus != status) { + StatsLog.write(StatsLog.CHARGING_STATE_CHANGED, status); + } + if (recentPast == null || recentPast.batteryPlugType != plugType) { + StatsLog.write(StatsLog.PLUGGED_STATE_CHANGED, plugType); + } + if (recentPast == null || recentPast.batteryLevel != level) { + StatsLog.write(StatsLog.BATTERY_LEVEL_CHANGED, level); + } + // Let's just always print the temperature, regardless of whether it changed. + StatsLog.write(StatsLog.DEVICE_TEMPERATURE_REPORTED, temp); + } + public long getAwakeTimeBattery() { return computeBatteryUptime(getBatteryUptimeLocked(), STATS_CURRENT); } diff --git a/core/java/com/android/internal/os/BinderInternal.java b/core/java/com/android/internal/os/BinderInternal.java index ea4575aba9c0..5bddd2f98983 100644 --- a/core/java/com/android/internal/os/BinderInternal.java +++ b/core/java/com/android/internal/os/BinderInternal.java @@ -16,9 +16,15 @@ package com.android.internal.os; +import android.annotation.NonNull; +import android.os.Handler; import android.os.IBinder; import android.os.SystemClock; import android.util.EventLog; +import android.util.Log; +import android.util.SparseIntArray; + +import com.android.internal.util.Preconditions; import dalvik.system.VMRuntime; @@ -31,11 +37,14 @@ import java.util.ArrayList; * @see IBinder */ public class BinderInternal { + private static final String TAG = "BinderInternal"; static WeakReference<GcWatcher> sGcWatcher = new WeakReference<GcWatcher>(new GcWatcher()); static ArrayList<Runnable> sGcWatchers = new ArrayList<>(); static Runnable[] sTmpWatchers = new Runnable[1]; static long sLastGcTime; + static final BinderProxyLimitListenerDelegate sBinderProxyLimitListenerDelegate = + new BinderProxyLimitListenerDelegate(); static final class GcWatcher { @Override @@ -106,4 +115,96 @@ public class BinderInternal { static void forceBinderGc() { forceGc("Binder"); } + + /** + * Enable/disable Binder Proxy Instance Counting by Uid. While enabled, the set callback will + * be called if this process holds too many Binder Proxies on behalf of a Uid. + * @param enabled true to enable counting, false to disable + */ + public static final native void nSetBinderProxyCountEnabled(boolean enabled); + + /** + * Get the current number of Binder Proxies held for each uid. + * @return SparseIntArray mapping uids to the number of Binder Proxies currently held + */ + public static final native SparseIntArray nGetBinderProxyPerUidCounts(); + + /** + * Get the current number of Binder Proxies held for an individual uid. + * @param uid Requested uid for Binder Proxy count + * @return int with the number of Binder proxies held for a uid + */ + public static final native int nGetBinderProxyCount(int uid); + + /** + * Set the Binder Proxy watermarks. Default high watermark = 2500. Default low watermark = 2000 + * @param high The limit at which the BinderProxyListener callback will be called. + * @param low The threshold a binder count must drop below before the callback + * can be called again. (This is to avoid many repeated calls to the + * callback in a brief period of time) + */ + public static final native void nSetBinderProxyCountWatermarks(int high, int low); + + /** + * Interface for callback invocation when the Binder Proxy limit is reached. onLimitReached will + * be called with the uid of the app causing too many Binder Proxies + */ + public interface BinderProxyLimitListener { + public void onLimitReached(int uid); + } + + /** + * Callback used by native code to trigger a callback in java code. The callback will be + * triggered when too many binder proxies from a uid hits the allowed limit. + * @param uid The uid of the bad behaving app sending too many binders + */ + public static void binderProxyLimitCallbackFromNative(int uid) { + sBinderProxyLimitListenerDelegate.notifyClient(uid); + } + + /** + * Set a callback to be triggered when a uid's Binder Proxy limit is reached for this process. + * @param listener OnLimitReached of listener will be called in the thread provided by handler + * @param handler must not be null, callback will be posted through the handler; + * + */ + public static void setBinderProxyCountCallback(BinderProxyLimitListener listener, + @NonNull Handler handler) { + Preconditions.checkNotNull(handler, + "Must provide NonNull Handler to setBinderProxyCountCallback when setting " + + "BinderProxyLimitListener"); + sBinderProxyLimitListenerDelegate.setListener(listener, handler); + } + + /** + * Clear the Binder Proxy callback + */ + public static void clearBinderProxyCountCallback() { + sBinderProxyLimitListenerDelegate.setListener(null, null); + } + + static private class BinderProxyLimitListenerDelegate { + private BinderProxyLimitListener mBinderProxyLimitListener; + private Handler mHandler; + + void setListener(BinderProxyLimitListener listener, Handler handler) { + synchronized (this) { + mBinderProxyLimitListener = listener; + mHandler = handler; + } + } + + void notifyClient(final int uid) { + synchronized (this) { + if (mBinderProxyLimitListener != null) { + mHandler.post(new Runnable() { + @Override + public void run() { + mBinderProxyLimitListener.onLimitReached(uid); + } + }); + } + } + } + } } diff --git a/core/java/com/android/internal/util/LocalLog.java b/core/java/com/android/internal/util/LocalLog.java index f0e6171562f9..8edb739f273c 100644 --- a/core/java/com/android/internal/util/LocalLog.java +++ b/core/java/com/android/internal/util/LocalLog.java @@ -20,6 +20,7 @@ import java.io.PrintWriter; import java.util.ArrayList; import android.util.Slog; +import android.util.proto.ProtoOutputStream; /** * Helper class for logging serious issues, which also keeps a small @@ -63,4 +64,16 @@ public class LocalLog { return true; } } + + public void writeToProto(ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + + synchronized (mLines) { + for (int i = 0; i < mLines.size(); ++i) { + proto.write(LocalLogProto.LINES, mLines.get(i)); + } + } + + proto.end(token); + } } diff --git a/core/java/com/android/internal/view/InputConnectionWrapper.java b/core/java/com/android/internal/view/InputConnectionWrapper.java index 2098ebd8814a..5b65bbe1e90e 100644 --- a/core/java/com/android/internal/view/InputConnectionWrapper.java +++ b/core/java/com/android/internal/view/InputConnectionWrapper.java @@ -36,6 +36,7 @@ import android.view.inputmethod.InputConnectionInspector.MissingMethodFlags; import android.view.inputmethod.InputContentInfo; import java.lang.ref.WeakReference; +import java.util.concurrent.atomic.AtomicBoolean; public class InputConnectionWrapper implements InputConnection { private static final int MAX_WAIT_TIME_MILLIS = 2000; @@ -46,6 +47,14 @@ public class InputConnectionWrapper implements InputConnection { @MissingMethodFlags private final int mMissingMethods; + /** + * {@code true} if the system already decided to take away IME focus from the target app. This + * can be signaled even when the corresponding signal is in the task queue and + * {@link InputMethodService#onUnbindInput()} is not yet called back on the UI thread. + */ + @NonNull + private final AtomicBoolean mIsUnbindIssued; + static class InputContextCallback extends IInputContextCallback.Stub { private static final String TAG = "InputConnectionWrapper.ICC"; public int mSeq; @@ -231,14 +240,20 @@ public class InputConnectionWrapper implements InputConnection { public InputConnectionWrapper( @NonNull WeakReference<AbstractInputMethodService> inputMethodService, - IInputContext inputContext, @MissingMethodFlags final int missingMethods) { + IInputContext inputContext, @MissingMethodFlags final int missingMethods, + @NonNull AtomicBoolean isUnbindIssued) { mInputMethodService = inputMethodService; mIInputContext = inputContext; mMissingMethods = missingMethods; + mIsUnbindIssued = isUnbindIssued; } @AnyThread public CharSequence getTextAfterCursor(int length, int flags) { + if (mIsUnbindIssued.get()) { + return null; + } + CharSequence value = null; try { InputContextCallback callback = InputContextCallback.getInstance(); @@ -258,6 +273,10 @@ public class InputConnectionWrapper implements InputConnection { @AnyThread public CharSequence getTextBeforeCursor(int length, int flags) { + if (mIsUnbindIssued.get()) { + return null; + } + CharSequence value = null; try { InputContextCallback callback = InputContextCallback.getInstance(); @@ -277,6 +296,10 @@ public class InputConnectionWrapper implements InputConnection { @AnyThread public CharSequence getSelectedText(int flags) { + if (mIsUnbindIssued.get()) { + return null; + } + if (isMethodMissing(MissingMethodFlags.GET_SELECTED_TEXT)) { // This method is not implemented. return null; @@ -300,6 +323,10 @@ public class InputConnectionWrapper implements InputConnection { @AnyThread public int getCursorCapsMode(int reqModes) { + if (mIsUnbindIssued.get()) { + return 0; + } + int value = 0; try { InputContextCallback callback = InputContextCallback.getInstance(); @@ -319,6 +346,10 @@ public class InputConnectionWrapper implements InputConnection { @AnyThread public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) { + if (mIsUnbindIssued.get()) { + return null; + } + ExtractedText value = null; try { InputContextCallback callback = InputContextCallback.getInstance(); @@ -516,6 +547,10 @@ public class InputConnectionWrapper implements InputConnection { @AnyThread public boolean requestCursorUpdates(int cursorUpdateMode) { + if (mIsUnbindIssued.get()) { + return false; + } + boolean result = false; if (isMethodMissing(MissingMethodFlags.REQUEST_CURSOR_UPDATES)) { // This method is not implemented. @@ -550,6 +585,10 @@ public class InputConnectionWrapper implements InputConnection { @AnyThread public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) { + if (mIsUnbindIssued.get()) { + return false; + } + boolean result = false; if (isMethodMissing(MissingMethodFlags.COMMIT_CONTENT)) { // This method is not implemented. diff --git a/core/java/com/android/internal/widget/Magnifier.java b/core/java/com/android/internal/widget/Magnifier.java index 9bc0778d9be6..f6741c3b5169 100644 --- a/core/java/com/android/internal/widget/Magnifier.java +++ b/core/java/com/android/internal/widget/Magnifier.java @@ -22,9 +22,7 @@ import android.annotation.UiThread; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Point; -import android.graphics.PointF; import android.graphics.Rect; -import android.graphics.RectF; import android.os.Handler; import android.util.Log; import android.view.Gravity; @@ -38,13 +36,15 @@ import android.widget.PopupWindow; import com.android.internal.R; import com.android.internal.util.Preconditions; +import java.util.Timer; +import java.util.TimerTask; + /** * Android magnifier widget. Can be used by any view which is attached to window. */ public final class Magnifier { private static final String LOG_TAG = "magnifier"; - // Use this to specify that a previous configuration value does not exist. - private static final int INEXISTENT_PREVIOUS_CONFIG_VALUE = -1; + private static final int MAGNIFIER_REFRESH_RATE_MS = 33; // ~30fps // The view for which this magnifier is attached. private final View mView; // The window containing the magnifier. @@ -62,15 +62,10 @@ public final class Magnifier { // The callback of the pixel copy request will be invoked on this Handler when // the copy is finished. private final Handler mPixelCopyHandler = Handler.getMain(); - - private RectF mTmpRectF; - - // Variables holding previous states, used for detecting redundant calls and invalidation. - private Point mPrevStartCoordsOnScreen = new Point( - INEXISTENT_PREVIOUS_CONFIG_VALUE, INEXISTENT_PREVIOUS_CONFIG_VALUE); - private PointF mPrevCenterCoordsOnScreen = new PointF( - INEXISTENT_PREVIOUS_CONFIG_VALUE, INEXISTENT_PREVIOUS_CONFIG_VALUE); - private float mPrevScale = INEXISTENT_PREVIOUS_CONFIG_VALUE; + // Current magnification scale. + private final float mZoomScale; + // Timer used to schedule the copy task. + private Timer mTimer; /** * Initializes a magnifier. @@ -81,10 +76,12 @@ public final class Magnifier { public Magnifier(@NonNull View view) { mView = Preconditions.checkNotNull(view); final Context context = mView.getContext(); + final float elevation = context.getResources().getDimension(R.dimen.magnifier_elevation); final View content = LayoutInflater.from(context).inflate(R.layout.magnifier, null); + content.findViewById(R.id.magnifier_inner).setClipToOutline(true); mWindowWidth = context.getResources().getDimensionPixelSize(R.dimen.magnifier_width); mWindowHeight = context.getResources().getDimensionPixelSize(R.dimen.magnifier_height); - final float elevation = context.getResources().getDimension(R.dimen.magnifier_elevation); + mZoomScale = context.getResources().getFloat(R.dimen.magnifier_zoom_scale); mWindow = new PopupWindow(context); mWindow.setContentView(content); @@ -94,52 +91,40 @@ public final class Magnifier { mWindow.setTouchable(false); mWindow.setBackgroundDrawable(null); - mBitmap = Bitmap.createBitmap(mWindowWidth, mWindowHeight, Bitmap.Config.ARGB_8888); + final int bitmapWidth = (int) (mWindowWidth / mZoomScale); + final int bitmapHeight = (int) (mWindowHeight / mZoomScale); + mBitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888); getImageView().setImageBitmap(mBitmap); } /** * Shows the magnifier on the screen. * - * @param centerXOnScreen horizontal coordinate of the center point of the magnifier source. The - * lower end is clamped to 0 - * @param centerYOnScreen vertical coordinate of the center point of the magnifier source. The - * lower end is clamped to 0 - * @param scale the scale at which the magnifier zooms on the source content. The - * lower end is clamped to 1 and the higher end to 4 + * @param xPosInView horizontal coordinate of the center point of the magnifier source relative + * to the view. The lower end is clamped to 0 + * @param yPosInView vertical coordinate of the center point of the magnifier source + * relative to the view. The lower end is clamped to 0 */ - public void show(@FloatRange(from=0) float centerXOnScreen, - @FloatRange(from=0) float centerYOnScreen, - @FloatRange(from=1, to=4) float scale) { - if (scale > 4) { - scale = 4; - } - - if (scale < 1) { - scale = 1; - } - - if (centerXOnScreen < 0) { - centerXOnScreen = 0; + public void show(@FloatRange(from=0) float xPosInView, @FloatRange(from=0) float yPosInView) { + if (xPosInView < 0) { + xPosInView = 0; } - if (centerYOnScreen < 0) { - centerYOnScreen = 0; + if (yPosInView < 0) { + yPosInView = 0; } - showInternal(centerXOnScreen, centerYOnScreen, scale, false); - } + configureCoordinates(xPosInView, yPosInView); - private void showInternal(@FloatRange(from=0) float centerXOnScreen, - @FloatRange(from=0) float centerYOnScreen, - @FloatRange(from=1, to=4) float scale, - boolean forceShow) { - if (mPrevScale != scale) { - resizeBitmap(scale); - mPrevScale = scale; + if (mTimer == null) { + mTimer = new Timer(); + mTimer.schedule(new TimerTask() { + @Override + public void run() { + performPixelCopy(); + } + }, 0 /* delay */, MAGNIFIER_REFRESH_RATE_MS); } - configureCoordinates(centerXOnScreen, centerYOnScreen); - maybePerformPixelCopy(scale, forceShow); if (mWindow.isShowing()) { mWindow.update(mWindowCoords.x, mWindowCoords.y, mWindow.getWidth(), @@ -148,9 +133,6 @@ public final class Magnifier { mWindow.showAtLocation(mView.getRootView(), Gravity.NO_GRAVITY, mWindowCoords.x, mWindowCoords.y); } - - mPrevCenterCoordsOnScreen.x = centerXOnScreen; - mPrevCenterCoordsOnScreen.y = centerYOnScreen; } /** @@ -159,36 +141,10 @@ public final class Magnifier { public void dismiss() { mWindow.dismiss(); - mPrevStartCoordsOnScreen.x = INEXISTENT_PREVIOUS_CONFIG_VALUE; - mPrevStartCoordsOnScreen.y = INEXISTENT_PREVIOUS_CONFIG_VALUE; - mPrevCenterCoordsOnScreen.x = INEXISTENT_PREVIOUS_CONFIG_VALUE; - mPrevCenterCoordsOnScreen.y = INEXISTENT_PREVIOUS_CONFIG_VALUE; - mPrevScale = INEXISTENT_PREVIOUS_CONFIG_VALUE; - } - - /** - * Forces the magnifier to update content by taking and showing a new snapshot using the - * previous coordinates. It does this only if the magnifier is showing and the dirty rectangle - * intersects the rectangle which holds the content to be magnified. - * - * @param dirtyRectOnScreen the rectangle representing the screen bounds of the dirty region - */ - public void invalidate(RectF dirtyRectOnScreen) { - if (mWindow.isShowing() && mPrevCenterCoordsOnScreen.x != INEXISTENT_PREVIOUS_CONFIG_VALUE - && mPrevCenterCoordsOnScreen.y != INEXISTENT_PREVIOUS_CONFIG_VALUE - && mPrevScale != INEXISTENT_PREVIOUS_CONFIG_VALUE) { - // Update the current showing RectF. - mTmpRectF = new RectF(mPrevStartCoordsOnScreen.x, - mPrevStartCoordsOnScreen.y, - mPrevStartCoordsOnScreen.x + mBitmap.getWidth(), - mPrevStartCoordsOnScreen.y + mBitmap.getHeight()); - - // Update only if we are currently showing content that has been declared as invalid. - if (RectF.intersects(dirtyRectOnScreen, mTmpRectF)) { - // Update the contents shown in the magnifier. - showInternal(mPrevCenterCoordsOnScreen.x, mPrevCenterCoordsOnScreen.y, mPrevScale, - true /* forceShow */); - } + if (mTimer != null) { + mTimer.cancel(); + mTimer.purge(); + mTimer = null; } } @@ -206,14 +162,19 @@ public final class Magnifier { return mWindowWidth; } - private void resizeBitmap(float scale) { - final int bitmapWidth = (int) (mWindowWidth / scale); - final int bitmapHeight = (int) (mWindowHeight / scale); - mBitmap.reconfigure(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888); - getImageView().setImageBitmap(mBitmap); + /** + * @return the zoom scale of the magnifier. + */ + public float getZoomScale() { + return mZoomScale; } - private void configureCoordinates(float posXOnScreen, float posYOnScreen) { + private void configureCoordinates(float xPosInView, float yPosInView) { + final int[] coordinatesOnScreen = new int[2]; + mView.getLocationOnScreen(coordinatesOnScreen); + final float posXOnScreen = xPosInView + coordinatesOnScreen[0]; + final float posYOnScreen = yPosInView + coordinatesOnScreen[1]; + mCenterZoomCoords.x = (int) posXOnScreen; mCenterZoomCoords.y = (int) posYOnScreen; @@ -223,7 +184,7 @@ public final class Magnifier { mWindowCoords.y = mCenterZoomCoords.y - mWindowHeight / 2 - verticalMagnifierOffset; } - private void maybePerformPixelCopy(final float scale, final boolean forceShow) { + private void performPixelCopy() { final int startY = mCenterZoomCoords.y - mBitmap.getHeight() / 2; int rawStartX = mCenterZoomCoords.x - mBitmap.getWidth() / 2; @@ -234,13 +195,6 @@ public final class Magnifier { rawStartX = mView.getWidth() - mBitmap.getWidth(); } - if (!forceShow && rawStartX == mPrevStartCoordsOnScreen.x - && startY == mPrevStartCoordsOnScreen.y - && scale == mPrevScale) { - // Skip, we are already showing the desired content. - return; - } - final int startX = rawStartX; final ViewRootImpl viewRootImpl = mView.getViewRootImpl(); @@ -251,11 +205,7 @@ public final class Magnifier { new Rect(startX, startY, startX + mBitmap.getWidth(), startY + mBitmap.getHeight()), mBitmap, - result -> { - getImageView().invalidate(); - mPrevStartCoordsOnScreen.x = startX; - mPrevStartCoordsOnScreen.y = startY; - }, + result -> getImageView().invalidate(), mPixelCopyHandler); } else { Log.d(LOG_TAG, "Could not perform PixelCopy request"); diff --git a/core/jni/android/graphics/FontFamily.cpp b/core/jni/android/graphics/FontFamily.cpp index c6801bf613fa..9e6985ca300e 100644 --- a/core/jni/android/graphics/FontFamily.cpp +++ b/core/jni/android/graphics/FontFamily.cpp @@ -56,9 +56,9 @@ static jlong FontFamily_initBuilder(JNIEnv* env, jobject clazz, jstring langs, j if (langs != nullptr) { ScopedUtfChars str(env, langs); builder = new NativeFamilyBuilder( - minikin::FontStyle::registerLanguageList(str.c_str()), variant); + minikin::FontStyle::registerLocaleList(str.c_str()), variant); } else { - builder = new NativeFamilyBuilder(minikin::FontStyle::registerLanguageList(""), variant); + builder = new NativeFamilyBuilder(minikin::FontStyle::registerLocaleList(""), variant); } return reinterpret_cast<jlong>(builder); } diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp index 93bd16b2d08a..fd62a19f310f 100644 --- a/core/jni/android/graphics/Paint.cpp +++ b/core/jni/android/graphics/Paint.cpp @@ -544,7 +544,7 @@ namespace PaintGlue { static jint setTextLocales(JNIEnv* env, jobject clazz, jlong objHandle, jstring locales) { Paint* obj = reinterpret_cast<Paint*>(objHandle); ScopedUtfChars localesChars(env, locales); - jint minikinLangListId = minikin::FontStyle::registerLanguageList(localesChars.c_str()); + jint minikinLangListId = minikin::FontStyle::registerLocaleList(localesChars.c_str()); obj->setMinikinLangListId(minikinLangListId); return minikinLangListId; } diff --git a/core/jni/android_database_SQLiteConnection.cpp b/core/jni/android_database_SQLiteConnection.cpp index c3f9bf7f25bf..1efff7f8b0fb 100644 --- a/core/jni/android_database_SQLiteConnection.cpp +++ b/core/jni/android_database_SQLiteConnection.cpp @@ -765,6 +765,14 @@ static jlong nativeExecuteForCursorWindow(JNIEnv* env, jclass clazz, if (startPos > totalRows) { ALOGE("startPos %d > actual rows %d", startPos, totalRows); } + if (totalRows > 0 && addedRows == 0) { + String8 msg; + msg.appendFormat("Row too big to fit into CursorWindow requiredPos=%d, totalRows=%d", + requiredPos, totalRows); + throw_sqlite3_exception(env, SQLITE_TOOBIG, NULL, msg.string()); + return 0; + } + jlong result = jlong(startPos) << 32 | jlong(totalRows); return result; } diff --git a/core/jni/android_os_HwBinder.cpp b/core/jni/android_os_HwBinder.cpp index fe14d483743f..c4f22eeb8b95 100644 --- a/core/jni/android_os_HwBinder.cpp +++ b/core/jni/android_os_HwBinder.cpp @@ -35,6 +35,7 @@ #include <hidl/HidlTransportSupport.h> #include <hwbinder/ProcessState.h> #include <nativehelper/ScopedLocalRef.h> +#include <nativehelper/ScopedUtfChars.h> #include <vintf/parse_string.h> #include <utils/misc.h> @@ -261,14 +262,9 @@ static void JHwBinder_native_registerService( JNIEnv *env, jobject thiz, jstring serviceNameObj) { - if (serviceNameObj == NULL) { - jniThrowException(env, "java/lang/NullPointerException", NULL); - return; - } - - const char *serviceName = env->GetStringUTFChars(serviceNameObj, NULL); - if (serviceName == NULL) { - return; // XXX exception already pending? + ScopedUtfChars str(env, serviceNameObj); + if (str.c_str() == nullptr) { + return; // NPE will be pending. } sp<hardware::IBinder> binder = JHwBinder::GetNativeBinder(env, thiz); @@ -284,15 +280,12 @@ static void JHwBinder_native_registerService( return; } - Return<bool> ret = manager->add(serviceName, base); - - env->ReleaseStringUTFChars(serviceNameObj, serviceName); - serviceName = NULL; + Return<bool> ret = manager->add(str.c_str(), base); bool ok = ret.isOk() && ret; if (ok) { - LOG(INFO) << "Starting thread pool."; + LOG(INFO) << "HwBinder: Starting thread pool for " << str.c_str(); ::android::hardware::ProcessState::self()->startThreadPool(); } @@ -308,31 +301,26 @@ static jobject JHwBinder_native_getService( using ::android::hidl::base::V1_0::IBase; using ::android::hardware::details::getRawServiceInternal; - if (ifaceNameObj == NULL) { - jniThrowException(env, "java/lang/NullPointerException", NULL); - return NULL; - } - if (serviceNameObj == NULL) { - jniThrowException(env, "java/lang/NullPointerException", NULL); - return NULL; - } - - const char *ifaceNameCStr = env->GetStringUTFChars(ifaceNameObj, NULL); - if (ifaceNameCStr == NULL) { - return NULL; // XXX exception already pending? + std::string ifaceName; + { + ScopedUtfChars str(env, ifaceNameObj); + if (str.c_str() == nullptr) { + return nullptr; // NPE will be pending. + } + ifaceName = str.c_str(); } - std::string ifaceName(ifaceNameCStr); - env->ReleaseStringUTFChars(ifaceNameObj, ifaceNameCStr); - const char *serviceNameCStr = env->GetStringUTFChars(serviceNameObj, NULL); - if (serviceNameCStr == NULL) { - return NULL; // XXX exception already pending? + std::string serviceName; + { + ScopedUtfChars str(env, serviceNameObj); + if (str.c_str() == nullptr) { + return nullptr; // NPE will be pending. + } + serviceName = str.c_str(); } - std::string serviceName(serviceNameCStr); - env->ReleaseStringUTFChars(serviceNameObj, serviceNameCStr); // TODO(b/67981006): true /* retry */ - sp<IBase> ret = getRawServiceInternal(ifaceName, serviceName, false /* retry */, false /* getStub */); + sp<IBase> ret = getRawServiceInternal(ifaceName, serviceName, false /* retry */, false /* getStub */); sp<hardware::IBinder> service = hardware::toBinder<hidl::base::V1_0::IBase>(ret); if (service == NULL) { @@ -340,13 +328,14 @@ static jobject JHwBinder_native_getService( return NULL; } - LOG(INFO) << "Starting thread pool."; + LOG(INFO) << "HwBinder: Starting thread pool for " << serviceName << "::" << ifaceName; ::android::hardware::ProcessState::self()->startThreadPool(); return JHwRemoteBinder::NewObject(env, service); } -void JHwBinder_native_configureRpcThreadpool(jlong maxThreads, jboolean callerWillJoin) { +void JHwBinder_native_configureRpcThreadpool(JNIEnv *, jclass, + jlong maxThreads, jboolean callerWillJoin) { CHECK(maxThreads > 0); ProcessState::self()->setThreadPoolConfiguration(maxThreads, callerWillJoin /*callerJoinsPool*/); } @@ -355,7 +344,7 @@ void JHwBinder_native_joinRpcThreadpool() { IPCThreadState::self()->joinThreadPool(); } -static void JHwBinder_report_sysprop_change(JNIEnv /**env*/, jobject /*clazz*/) +static void JHwBinder_report_sysprop_change(JNIEnv * /*env*/, jclass /*clazz*/) { report_sysprop_change(); } diff --git a/core/jni/android_text_Hyphenator.cpp b/core/jni/android_text_Hyphenator.cpp index 05bec28a5d39..6f9cc22fb3ab 100644 --- a/core/jni/android_text_Hyphenator.cpp +++ b/core/jni/android_text_Hyphenator.cpp @@ -82,45 +82,45 @@ static void init() { constexpr int INDIC_MIN_PREFIX = 2; constexpr int INDIC_MIN_SUFFIX = 2; - addHyphenator("as", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Assamese - addHyphenator("be", 2, 2); // Belarusian - addHyphenator("bg", 2, 2); // Bulgarian - addHyphenator("bn", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Bengali - addHyphenator("cu", 1, 2); // Church Slavonic - addHyphenator("cy", 2, 3); // Welsh - addHyphenator("da", 2, 2); // Danish - addHyphenator("de-1901", 2, 2); // German 1901 orthography - addHyphenator("de-1996", 2, 2); // German 1996 orthography - addHyphenator("de-CH-1901", 2, 2); // Swiss High German 1901 orthography - addHyphenator("en-GB", 2, 3); // British English - addHyphenator("en-US", 2, 3); // American English - addHyphenator("es", 2, 2); // Spanish - addHyphenator("et", 2, 3); // Estonian - addHyphenator("eu", 2, 2); // Basque - addHyphenator("fr", 2, 3); // French - addHyphenator("ga", 2, 3); // Irish - addHyphenator("gu", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Gujarati - addHyphenator("hi", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Hindi - addHyphenator("hr", 2, 2); // Croatian - addHyphenator("hu", 2, 2); // Hungarian + addHyphenator("as", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Assamese + addHyphenator("be", 2, 2); // Belarusian + addHyphenator("bg", 2, 2); // Bulgarian + addHyphenator("bn", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Bengali + addHyphenator("cu", 1, 2); // Church Slavonic + addHyphenator("cy", 2, 3); // Welsh + addHyphenator("da", 2, 2); // Danish + addHyphenator("de-1901", 2, 2); // German 1901 orthography + addHyphenator("de-1996", 2, 2); // German 1996 orthography + addHyphenator("de-CH-1901", 2, 2); // Swiss High German 1901 orthography + addHyphenator("en-GB", 2, 3); // British English + addHyphenator("en-US", 2, 3); // American English + addHyphenator("es", 2, 2); // Spanish + addHyphenator("et", 2, 3); // Estonian + addHyphenator("eu", 2, 2); // Basque + addHyphenator("fr", 2, 3); // French + addHyphenator("ga", 2, 3); // Irish + addHyphenator("gu", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Gujarati + addHyphenator("hi", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Hindi + addHyphenator("hr", 2, 2); // Croatian + addHyphenator("hu", 2, 2); // Hungarian // texhyphen sources say Armenian may be (1, 2); but that it needs confirmation. // Going with a more conservative value of (2, 2) for now. - addHyphenator("hy", 2, 2); // Armenian - addHyphenator("kn", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Kannada - addHyphenator("la", 2, 2); // Latin - addHyphenator("ml", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Malayalam - addHyphenator("mn-Cyrl", 2, 2); // Mongolian in Cyrillic script - addHyphenator("mr", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Marathi - addHyphenator("nb", 2, 2); // Norwegian Bokmål - addHyphenator("nn", 2, 2); // Norwegian Nynorsk - addHyphenator("or", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Oriya - addHyphenator("pa", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Punjabi - addHyphenator("pt", 2, 3); // Portuguese - addHyphenator("sl", 2, 2); // Slovenian - addHyphenator("ta", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Tamil - addHyphenator("te", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Telugu - addHyphenator("tk", 2, 2); // Turkmen - addHyphenator("und-Ethi", 1, 1); // Any language in Ethiopic script + addHyphenator("hy", 2, 2); // Armenian + addHyphenator("kn", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Kannada + addHyphenator("la", 2, 2); // Latin + addHyphenator("ml", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Malayalam + addHyphenator("mn-Cyrl", 2, 2); // Mongolian in Cyrillic script + addHyphenator("mr", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Marathi + addHyphenator("nb", 2, 2); // Norwegian Bokmål + addHyphenator("nn", 2, 2); // Norwegian Nynorsk + addHyphenator("or", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Oriya + addHyphenator("pa", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Punjabi + addHyphenator("pt", 2, 3); // Portuguese + addHyphenator("sl", 2, 2); // Slovenian + addHyphenator("ta", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Tamil + addHyphenator("te", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Telugu + addHyphenator("tk", 2, 2); // Turkmen + addHyphenator("und-Ethi", 1, 1); // Any language in Ethiopic script // Following two hyphenators do not have pattern files but there is some special logic based on // language. @@ -130,13 +130,13 @@ static void init() { // English locales that fall back to en-US. The data is from CLDR. It's all English locales, // minus the locales whose parent is en-001 (from supplementalData.xml, under <parentLocales>). // TODO: Figure out how to get this from ICU. - addHyphenatorAlias("en-AS", "en-US"); // English (American Samoa) - addHyphenatorAlias("en-GU", "en-US"); // English (Guam) - addHyphenatorAlias("en-MH", "en-US"); // English (Marshall Islands) - addHyphenatorAlias("en-MP", "en-US"); // English (Northern Mariana Islands) - addHyphenatorAlias("en-PR", "en-US"); // English (Puerto Rico) - addHyphenatorAlias("en-UM", "en-US"); // English (United States Minor Outlying Islands) - addHyphenatorAlias("en-VI", "en-US"); // English (Virgin Islands) + addHyphenatorAlias("en-AS", "en-US"); // English (American Samoa) + addHyphenatorAlias("en-GU", "en-US"); // English (Guam) + addHyphenatorAlias("en-MH", "en-US"); // English (Marshall Islands) + addHyphenatorAlias("en-MP", "en-US"); // English (Northern Mariana Islands) + addHyphenatorAlias("en-PR", "en-US"); // English (Puerto Rico) + addHyphenatorAlias("en-UM", "en-US"); // English (United States Minor Outlying Islands) + addHyphenatorAlias("en-VI", "en-US"); // English (Virgin Islands) // All English locales other than those falling back to en-US are mapped to en-GB. addHyphenatorAlias("en", "en-GB"); @@ -150,17 +150,28 @@ static void init() { addHyphenatorAlias("no", "nb"); // Use mn-Cyrl. According to CLDR's likelySubtags.xml, mn is most likely to be mn-Cyrl. - addHyphenatorAlias("mn", "mn-Cyrl"); // Mongolian + addHyphenatorAlias("mn", "mn-Cyrl"); // Mongolian // Fall back to Ethiopic script for languages likely to be written in Ethiopic. // Data is from CLDR's likelySubtags.xml. // TODO: Convert this to a mechanism using ICU4J's ULocale#addLikelySubtags(). - addHyphenatorAlias("am", "und-Ethi"); // Amharic - addHyphenatorAlias("byn", "und-Ethi"); // Blin - addHyphenatorAlias("gez", "und-Ethi"); // Geʻez - addHyphenatorAlias("ti", "und-Ethi"); // Tigrinya - addHyphenatorAlias("wal", "und-Ethi"); // Wolaytta - + addHyphenatorAlias("am", "und-Ethi"); // Amharic + addHyphenatorAlias("byn", "und-Ethi"); // Blin + addHyphenatorAlias("gez", "und-Ethi"); // Geʻez + addHyphenatorAlias("ti", "und-Ethi"); // Tigrinya + addHyphenatorAlias("wal", "und-Ethi"); // Wolaytta + + // Use Hindi as a fallback hyphenator for all languages written in Devanagari, etc. This makes + // sense because our Indic patterns are not really linguistic, but script-based. + addHyphenatorAlias("und-Beng", "bn"); // Bengali + addHyphenatorAlias("und-Deva", "hi"); // Devanagari -> Hindi + addHyphenatorAlias("und-Gujr", "gu"); // Gujarati + addHyphenatorAlias("und-Guru", "pa"); // Gurmukhi -> Punjabi + addHyphenatorAlias("und-Knda", "kn"); // Kannada + addHyphenatorAlias("und-Mlym", "ml"); // Malayalam + addHyphenatorAlias("und-Orya", "or"); // Oriya + addHyphenatorAlias("und-Taml", "ta"); // Tamil + addHyphenatorAlias("und-Telu", "te"); // Telugu } static const JNINativeMethod gMethods[] = { diff --git a/core/jni/android_text_StaticLayout.cpp b/core/jni/android_text_StaticLayout.cpp index 04e9dfd2706f..c1419ba6c7c6 100644 --- a/core/jni/android_text_StaticLayout.cpp +++ b/core/jni/android_text_StaticLayout.cpp @@ -55,11 +55,11 @@ static JLineBreaksID gLineBreaks_fieldID; class JNILineBreakerLineWidth : public minikin::LineBreaker::LineWidthDelegate { public: JNILineBreakerLineWidth(float firstWidth, int32_t firstLineCount, float restWidth, - std::vector<float>&& indents, std::vector<float>&& leftPaddings, - std::vector<float>&& rightPaddings, int32_t indentsAndPaddingsOffset) + const std::vector<float>& indents, const std::vector<float>& leftPaddings, + const std::vector<float>& rightPaddings, int32_t indentsAndPaddingsOffset) : mFirstWidth(firstWidth), mFirstLineCount(firstLineCount), mRestWidth(restWidth), - mIndents(std::move(indents)), mLeftPaddings(std::move(leftPaddings)), - mRightPaddings(std::move(rightPaddings)), mOffset(indentsAndPaddingsOffset) {} + mIndents(indents), mLeftPaddings(leftPaddings), + mRightPaddings(rightPaddings), mOffset(indentsAndPaddingsOffset) {} float getLineWidth(size_t lineNo) override { const float width = ((ssize_t)lineNo < (ssize_t)mFirstLineCount) @@ -91,9 +91,9 @@ class JNILineBreakerLineWidth : public minikin::LineBreaker::LineWidthDelegate { const float mFirstWidth; const int32_t mFirstLineCount; const float mRestWidth; - const std::vector<float> mIndents; - const std::vector<float> mLeftPaddings; - const std::vector<float> mRightPaddings; + const std::vector<float>& mIndents; + const std::vector<float>& mLeftPaddings; + const std::vector<float>& mRightPaddings; const int32_t mOffset; }; @@ -106,32 +106,132 @@ static inline std::vector<float> jintArrayToFloatVector(JNIEnv* env, jintArray j } } +class Run { + public: + Run(int32_t start, int32_t end) : mStart(start), mEnd(end) {} + virtual ~Run() {} + + virtual void addTo(minikin::LineBreaker* lineBreaker) = 0; + + protected: + const int32_t mStart; + const int32_t mEnd; + + private: + // Forbid copy and assign. + Run(const Run&) = delete; + void operator=(const Run&) = delete; +}; + +class StyleRun : public Run { + public: + StyleRun(int32_t start, int32_t end, minikin::MinikinPaint&& paint, + std::shared_ptr<minikin::FontCollection>&& collection, + minikin::FontStyle&& style, bool isRtl) + : Run(start, end), mPaint(std::move(paint)), mCollection(std::move(collection)), + mStyle(std::move(style)), mIsRtl(isRtl) {} + + void addTo(minikin::LineBreaker* lineBreaker) override { + lineBreaker->addStyleRun(&mPaint, mCollection, mStyle, mStart, mEnd, mIsRtl); + } + + private: + minikin::MinikinPaint mPaint; + std::shared_ptr<minikin::FontCollection> mCollection; + minikin::FontStyle mStyle; + const bool mIsRtl; +}; + +class Replacement : public Run { + public: + Replacement(int32_t start, int32_t end, float width, uint32_t localeListId) + : Run(start, end), mWidth(width), mLocaleListId(localeListId) {} + + void addTo(minikin::LineBreaker* lineBreaker) override { + lineBreaker->addReplacement(mStart, mEnd, mWidth, mLocaleListId); + } + + private: + const float mWidth; + const uint32_t mLocaleListId; +}; + +class StaticLayoutNative { + public: + StaticLayoutNative( + minikin::BreakStrategy strategy, minikin::HyphenationFrequency frequency, + bool isJustified, std::vector<float>&& indents, std::vector<float>&& leftPaddings, + std::vector<float>&& rightPaddings) + : mStrategy(strategy), mFrequency(frequency), mIsJustified(isJustified), + mIndents(std::move(indents)), mLeftPaddings(std::move(leftPaddings)), + mRightPaddings(std::move(rightPaddings)) {} + + void addStyleRun(int32_t start, int32_t end, minikin::MinikinPaint&& paint, + std::shared_ptr<minikin::FontCollection> collection, + minikin::FontStyle&& style, bool isRtl) { + mRuns.emplace_back(std::make_unique<StyleRun>( + start, end, std::move(paint), std::move(collection), std::move(style), isRtl)); + } + + void addReplacementRun(int32_t start, int32_t end, float width, uint32_t localeListId) { + mRuns.emplace_back(std::make_unique<Replacement>(start, end, width, localeListId)); + } + + // Only valid while this instance is alive. + inline std::unique_ptr<minikin::LineBreaker::LineWidthDelegate> buildLineWidthDelegate( + float firstWidth, int32_t firstLineCount, float restWidth, + int32_t indentsAndPaddingsOffset) { + return std::make_unique<JNILineBreakerLineWidth>( + firstWidth, firstLineCount, restWidth, mIndents, mLeftPaddings, mRightPaddings, + indentsAndPaddingsOffset); + } + + void addRuns(minikin::LineBreaker* lineBreaker) { + for (const auto& run : mRuns) { + run->addTo(lineBreaker); + } + } + + void clearRuns() { + mRuns.clear(); + } + + inline minikin::BreakStrategy getStrategy() const { return mStrategy; } + inline minikin::HyphenationFrequency getFrequency() const { return mFrequency; } + inline bool isJustified() const { return mIsJustified; } + + private: + const minikin::BreakStrategy mStrategy; + const minikin::HyphenationFrequency mFrequency; + const bool mIsJustified; + const std::vector<float> mIndents; + const std::vector<float> mLeftPaddings; + const std::vector<float> mRightPaddings; + + std::vector<std::unique_ptr<Run>> mRuns; +}; + +static inline StaticLayoutNative* toNative(jlong ptr) { + return reinterpret_cast<StaticLayoutNative*>(ptr); +} + // set text and set a number of parameters for creating a layout (width, tabstops, strategy, // hyphenFrequency) -static void nSetupParagraph(JNIEnv* env, jclass, jlong nativePtr, jcharArray text, jint length, - jfloat firstWidth, jint firstWidthLineLimit, jfloat restWidth, - jintArray variableTabStops, jint defaultTabStop, jint strategy, jint hyphenFrequency, - jboolean isJustified, jintArray indents, jintArray leftPaddings, jintArray rightPaddings, - jint indentsAndPaddingsOffset) { - minikin::LineBreaker* b = reinterpret_cast<minikin::LineBreaker*>(nativePtr); - b->resize(length); - env->GetCharArrayRegion(text, 0, length, b->buffer()); - b->setText(); - if (variableTabStops == nullptr) { - b->setTabStops(nullptr, 0, defaultTabStop); - } else { - ScopedIntArrayRO stops(env, variableTabStops); - b->setTabStops(stops.get(), stops.size(), defaultTabStop); - } - b->setStrategy(static_cast<minikin::BreakStrategy>(strategy)); - b->setHyphenationFrequency(static_cast<minikin::HyphenationFrequency>(hyphenFrequency)); - b->setJustified(isJustified); - - // TODO: copy indents and paddings only once when LineBreaker is started to be used. - b->setLineWidthDelegate(std::make_unique<JNILineBreakerLineWidth>( - firstWidth, firstWidthLineLimit, restWidth, jintArrayToFloatVector(env, indents), - jintArrayToFloatVector(env, leftPaddings), jintArrayToFloatVector(env, rightPaddings), - indentsAndPaddingsOffset)); +static jlong nInit(JNIEnv* env, jclass /* unused */, + jint breakStrategy, jint hyphenationFrequency, jboolean isJustified, + jintArray indents, jintArray leftPaddings, jintArray rightPaddings) { + return reinterpret_cast<jlong>(new StaticLayoutNative( + static_cast<minikin::BreakStrategy>(breakStrategy), + static_cast<minikin::HyphenationFrequency>(hyphenationFrequency), + isJustified, + jintArrayToFloatVector(env, indents), + jintArrayToFloatVector(env, leftPaddings), + jintArrayToFloatVector(env, rightPaddings))); +} + +// CriticalNative +static void nFinish(jlong nativePtr) { + delete toNative(nativePtr); } static void recycleCopy(JNIEnv* env, jobject recycle, jintArray recycleBreaks, @@ -163,42 +263,65 @@ static void recycleCopy(JNIEnv* env, jobject recycle, jintArray recycleBreaks, } static jint nComputeLineBreaks(JNIEnv* env, jclass, jlong nativePtr, - jobject recycle, jintArray recycleBreaks, - jfloatArray recycleWidths, jfloatArray recycleAscents, - jfloatArray recycleDescents, jintArray recycleFlags, - jint recycleLength, jfloatArray charWidths) { - minikin::LineBreaker* b = reinterpret_cast<minikin::LineBreaker*>(nativePtr); + // Inputs + jcharArray text, + jint length, + jfloat firstWidth, + jint firstWidthLineCount, + jfloat restWidth, + jintArray variableTabStops, + jint defaultTabStop, + jint indentsOffset, + + // Outputs + jobject recycle, + jint recycleLength, + jintArray recycleBreaks, + jfloatArray recycleWidths, + jfloatArray recycleAscents, + jfloatArray recycleDescents, + jintArray recycleFlags, + jfloatArray charWidths) { + + StaticLayoutNative* builder = toNative(nativePtr); + + // TODO: Reorganize minikin APIs. + minikin::LineBreaker b; + b.resize(length); + env->GetCharArrayRegion(text, 0, length, b.buffer()); + b.setText(); + if (variableTabStops == nullptr) { + b.setTabStops(nullptr, 0, defaultTabStop); + } else { + ScopedIntArrayRO stops(env, variableTabStops); + b.setTabStops(stops.get(), stops.size(), defaultTabStop); + } + b.setStrategy(builder->getStrategy()); + b.setHyphenationFrequency(builder->getFrequency()); + b.setJustified(builder->isJustified()); + b.setLineWidthDelegate(builder->buildLineWidthDelegate( + firstWidth, firstWidthLineCount, restWidth, indentsOffset)); - size_t nBreaks = b->computeBreaks(); + builder->addRuns(&b); + + size_t nBreaks = b.computeBreaks(); recycleCopy(env, recycle, recycleBreaks, recycleWidths, recycleAscents, recycleDescents, - recycleFlags, recycleLength, nBreaks, b->getBreaks(), b->getWidths(), b->getAscents(), - b->getDescents(), b->getFlags()); + recycleFlags, recycleLength, nBreaks, b.getBreaks(), b.getWidths(), b.getAscents(), + b.getDescents(), b.getFlags()); - env->SetFloatArrayRegion(charWidths, 0, b->size(), b->charWidths()); + env->SetFloatArrayRegion(charWidths, 0, b.size(), b.charWidths()); - b->finish(); + b.finish(); + builder->clearRuns(); return static_cast<jint>(nBreaks); } -static jlong nNewBuilder(JNIEnv*, jclass) { - return reinterpret_cast<jlong>(new minikin::LineBreaker); -} - -static void nFreeBuilder(JNIEnv*, jclass, jlong nativePtr) { - delete reinterpret_cast<minikin::LineBreaker*>(nativePtr); -} - -static void nFinishBuilder(JNIEnv*, jclass, jlong nativePtr) { - minikin::LineBreaker* b = reinterpret_cast<minikin::LineBreaker*>(nativePtr); - b->finish(); -} - // Basically similar to Paint.getTextRunAdvances but with C++ interface -static void nAddStyleRun(JNIEnv* env, jclass, jlong nativePtr, jlong nativePaint, jint start, - jint end, jboolean isRtl) { - minikin::LineBreaker* b = reinterpret_cast<minikin::LineBreaker*>(nativePtr); +// CriticalNative +static void nAddStyleRun(jlong nativePtr, jlong nativePaint, jint start, jint end, jboolean isRtl) { + StaticLayoutNative* builder = toNative(nativePtr); Paint* paint = reinterpret_cast<Paint*>(nativePaint); const Typeface* typeface = paint->getAndroidTypeface(); minikin::MinikinPaint minikinPaint; @@ -206,26 +329,59 @@ static void nAddStyleRun(JNIEnv* env, jclass, jlong nativePtr, jlong nativePaint minikin::FontStyle style = MinikinUtils::prepareMinikinPaint(&minikinPaint, paint, typeface); - b->addStyleRun(&minikinPaint, resolvedTypeface->fFontCollection, style, start, end, isRtl); + builder->addStyleRun( + start, end, std::move(minikinPaint), resolvedTypeface->fFontCollection, std::move(style), + isRtl); } -static void nAddReplacementRun(JNIEnv* env, jclass, jlong nativePtr, jlong nativePaint, - jint start, jint end, jfloat width) { - minikin::LineBreaker* b = reinterpret_cast<minikin::LineBreaker*>(nativePtr); +// CriticalNative +static void nAddReplacementRun(jlong nativePtr, jlong nativePaint, jint start, jint end, + jfloat width) { + StaticLayoutNative* builder = toNative(nativePtr); Paint* paint = reinterpret_cast<Paint*>(nativePaint); - b->addReplacement(start, end, width, paint->getMinikinLangListId()); + builder->addReplacementRun(start, end, width, paint->getMinikinLangListId()); } static const JNINativeMethod gMethods[] = { - // TODO performance: many of these are candidates for fast jni, awaiting guidance - {"nNewBuilder", "()J", (void*) nNewBuilder}, - {"nFreeBuilder", "(J)V", (void*) nFreeBuilder}, - {"nFinishBuilder", "(J)V", (void*) nFinishBuilder}, - {"nSetupParagraph", "(J[CIFIF[IIIIZ[I[I[II)V", (void*) nSetupParagraph}, + // Fast Natives + {"nInit", "(" + "I" // breakStrategy + "I" // hyphenationFrequency + "Z" // isJustified + "[I" // indents + "[I" // left paddings + "[I" // right paddings + ")J", (void*) nInit}, + + // Critical Natives + {"nFinish", "(J)V", (void*) nFinish}, {"nAddStyleRun", "(JJIIZ)V", (void*) nAddStyleRun}, {"nAddReplacementRun", "(JJIIF)V", (void*) nAddReplacementRun}, - {"nComputeLineBreaks", "(JLandroid/text/StaticLayout$LineBreaks;[I[F[F[F[II[F)I", - (void*) nComputeLineBreaks} + + // Regular JNI + {"nComputeLineBreaks", "(" + "J" // nativePtr + + // Inputs + "[C" // text + "I" // length + "F" // firstWidth + "I" // firstWidthLineCount + "F" // restWidth + "[I" // variableTabStops + "I" // defaultTabStop + "I" // indentsOffset + + // Outputs + "Landroid/text/StaticLayout$LineBreaks;" // recycle + "I" // recycleLength + "[I" // recycleBreaks + "[F" // recycleWidths + "[F" // recycleAscents + "[F" // recycleDescents + "[I" // recycleFlags + "[F" // charWidths + ")I", (void*) nComputeLineBreaks} }; int register_android_text_StaticLayout(JNIEnv* env) diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp index 7908c9d2b609..6362bc7187fc 100644 --- a/core/jni/android_util_Binder.cpp +++ b/core/jni/android_util_Binder.cpp @@ -32,6 +32,7 @@ #include <binder/IServiceManager.h> #include <binder/IPCThreadState.h> #include <binder/Parcel.h> +#include <binder/BpBinder.h> #include <binder/ProcessState.h> #include <log/log.h> #include <utils/Atomic.h> @@ -80,9 +81,17 @@ static struct binderinternal_offsets_t // Class state. jclass mClass; jmethodID mForceGc; + jmethodID mProxyLimitCallback; } gBinderInternalOffsets; +static struct sparseintarray_offsets_t +{ + jclass classObject; + jmethodID constructor; + jmethodID put; +} gSparseIntArrayOffsets; + // ---------------------------------------------------------------------------- static struct error_offsets_t @@ -973,6 +982,43 @@ static void android_os_BinderInternal_handleGc(JNIEnv* env, jobject clazz) android_atomic_and(0, &gNumRefsCreated); } +static void android_os_BinderInternal_proxyLimitcallback(int uid) +{ + JNIEnv *env = AndroidRuntime::getJNIEnv(); + env->CallStaticVoidMethod(gBinderInternalOffsets.mClass, + gBinderInternalOffsets.mProxyLimitCallback, + uid); +} + +static void android_os_BinderInternal_setBinderProxyCountEnabled(JNIEnv* env, jobject clazz, + jboolean enable) +{ + BpBinder::setCountByUidEnabled((bool) enable); +} + +static jobject android_os_BinderInternal_getBinderProxyPerUidCounts(JNIEnv* env, jclass clazz) +{ + Vector<uint32_t> uids, counts; + BpBinder::getCountByUid(uids, counts); + jobject sparseIntArray = env->NewObject(gSparseIntArrayOffsets.classObject, + gSparseIntArrayOffsets.constructor); + for (size_t i = 0; i < uids.size(); i++) { + env->CallVoidMethod(sparseIntArray, gSparseIntArrayOffsets.put, + static_cast<jint>(uids[i]), static_cast<jint>(counts[i])); + } + return sparseIntArray; +} + +static jint android_os_BinderInternal_getBinderProxyCount(JNIEnv* env, jobject clazz, jint uid) { + return static_cast<jint>(BpBinder::getBinderProxyCount(static_cast<uint32_t>(uid))); +} + +static void android_os_BinderInternal_setBinderProxyCountWatermarks(JNIEnv* env, jobject clazz, + jint high, jint low) +{ + BpBinder::setBinderProxyCountWatermarks(high, low); +} + // ---------------------------------------------------------------------------- static const JNINativeMethod gBinderInternalMethods[] = { @@ -981,7 +1027,11 @@ static const JNINativeMethod gBinderInternalMethods[] = { { "joinThreadPool", "()V", (void*)android_os_BinderInternal_joinThreadPool }, { "disableBackgroundScheduling", "(Z)V", (void*)android_os_BinderInternal_disableBackgroundScheduling }, { "setMaxThreads", "(I)V", (void*)android_os_BinderInternal_setMaxThreads }, - { "handleGc", "()V", (void*)android_os_BinderInternal_handleGc } + { "handleGc", "()V", (void*)android_os_BinderInternal_handleGc }, + { "nSetBinderProxyCountEnabled", "(Z)V", (void*)android_os_BinderInternal_setBinderProxyCountEnabled }, + { "nGetBinderProxyPerUidCounts", "()Landroid/util/SparseIntArray;", (void*)android_os_BinderInternal_getBinderProxyPerUidCounts }, + { "nGetBinderProxyCount", "(I)I", (void*)android_os_BinderInternal_getBinderProxyCount }, + { "nSetBinderProxyCountWatermarks", "(II)V", (void*)android_os_BinderInternal_setBinderProxyCountWatermarks} }; const char* const kBinderInternalPathName = "com/android/internal/os/BinderInternal"; @@ -992,6 +1042,16 @@ static int int_register_android_os_BinderInternal(JNIEnv* env) gBinderInternalOffsets.mClass = MakeGlobalRefOrDie(env, clazz); gBinderInternalOffsets.mForceGc = GetStaticMethodIDOrDie(env, clazz, "forceBinderGc", "()V"); + gBinderInternalOffsets.mProxyLimitCallback = GetStaticMethodIDOrDie(env, clazz, "binderProxyLimitCallbackFromNative", "(I)V"); + + jclass SparseIntArrayClass = FindClassOrDie(env, "android/util/SparseIntArray"); + gSparseIntArrayOffsets.classObject = MakeGlobalRefOrDie(env, SparseIntArrayClass); + gSparseIntArrayOffsets.constructor = GetMethodIDOrDie(env, gSparseIntArrayOffsets.classObject, + "<init>", "()V"); + gSparseIntArrayOffsets.put = GetMethodIDOrDie(env, gSparseIntArrayOffsets.classObject, "put", + "(II)V"); + + BpBinder::setLimitCallback(android_os_BinderInternal_proxyLimitcallback); return RegisterMethodsOrDie( env, kBinderInternalPathName, diff --git a/core/proto/android/app/activitymanager.proto b/core/proto/android/app/activitymanager.proto new file mode 100644 index 000000000000..e87499ed2b83 --- /dev/null +++ b/core/proto/android/app/activitymanager.proto @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2017 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. + */ + +syntax = "proto2"; + +package android.app; + +option java_multiple_files = true; + +message ActivityManagerProto { + + // ActivityManager.java PROCESS_STATEs + enum ProcessState { + // Order matters for process states, so values have been spaced to provide + // room for future additions. + + // Not a real process state. + PROCESS_STATE_UNKNOWN = -100; + // Process is a persistent system process. + PROCESS_STATE_PERSISTENT = 0; + // Process is a persistent system process and is doing UI. + PROCESS_STATE_PERSISTENT_UI = 100; + // Process is hosting the current top activities. Note that this covers + // all activities that are visible to the user. + PROCESS_STATE_TOP = 200; + // Process is hosting a foreground service due to a system binding. + PROCESS_STATE_BOUND_FOREGROUND_SERVICE = 300; + // Process is hosting a foreground service. + PROCESS_STATE_FOREGROUND_SERVICE = 400; + // Same as PROCESS_STATE_TOP but while device is sleeping. + PROCESS_STATE_TOP_SLEEPING = 500; + // Process is important to the user, and something they are aware of. + PROCESS_STATE_IMPORTANT_FOREGROUND = 600; + // Process is important to the user, but not something they are aware of. + PROCESS_STATE_IMPORTANT_BACKGROUND = 700; + // Process is in the background transient so we will try to keep running. + PROCESS_STATE_TRANSIENT_BACKGROUND = 800; + // Process is in the background running a backup/restore operation. + PROCESS_STATE_BACKUP = 900; + // Process is in the background, but it can't restore its state so we want + // to try to avoid killing it. + PROCESS_STATE_HEAVY_WEIGHT = 1000; + // Process is in the background running a service. Unlike oom_adj, this + // level is used for both the normal running in background state and the + // executing operations state. + PROCESS_STATE_SERVICE = 1100; + // Process is in the background running a receiver. Note that from the + // perspective of oom_adj, receivers run at a higher foreground level, but + // for our prioritization here that is not necessary and putting them + // below services means many fewer changes in some process states as they + // receive broadcasts. + PROCESS_STATE_RECEIVER = 1200; + // Process is in the background but hosts the home activity. + PROCESS_STATE_HOME = 1300; + // Process is in the background but hosts the last shown activity. + PROCESS_STATE_LAST_ACTIVITY = 1400; + // Process is being cached for later use and contains activities. + PROCESS_STATE_CACHED_ACTIVITY = 1500; + // Process is being cached for later use and is a client of another cached + // process that contains activities. + PROCESS_STATE_CACHED_ACTIVITY_CLIENT = 1600; + // Process is being cached for later use and is empty. + PROCESS_STATE_CACHED_EMPTY = 1700; + // Process does not exist. + PROCESS_STATE_NONEXISTENT = 1800; + } +} diff --git a/core/proto/android/app/alarmmanager.proto b/core/proto/android/app/alarmmanager.proto new file mode 100644 index 000000000000..789e3d6293c3 --- /dev/null +++ b/core/proto/android/app/alarmmanager.proto @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2017 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. + */ + +syntax = "proto2"; + +import "frameworks/base/core/proto/android/app/pendingintent.proto"; + +option java_multiple_files = true; + +package android.app; + +/** + * An android.app.AlarmManager object. + */ +message AlarmManagerProto { + enum AlarmType { + // Alarm time in System.currentTimeMillis() (wall clock time in UTC), which + // will wake up the device when it goes off. + RTC_WAKEUP = 0; + // Alarm time in System.currentTimeMillis() (wall clock time in UTC). This + // alarm does not wake the device up; if it goes off while the device is + // asleep, it will not be delivered until the next time the device wakes up. + RTC = 1; + // Alarm time in SystemClock.elapsedRealtime() (time since boot, including + // sleep), which will wake up the device when it goes off. + ELAPSED_REALTIME_WAKEUP = 2; + // Alarm time in SystemClock.elapsedRealtime() (time since boot, including + // sleep). This alarm does not wake the device up; if it goes off while the + // device is asleep, it will not be delivered until the next time the device + // wakes up. + ELAPSED_REALTIME = 3; + } +} + +// An android.app.AlarmManager.AlarmClockInfo object. +message AlarmClockInfoProto { + // This value is UTC wall clock time in milliseconds, as returned by + // System#currentTimeMillis() for example. + optional int64 trigger_time_ms = 1; + optional android.app.PendingIntentProto show_intent = 2; +} diff --git a/core/proto/android/app/pendingintent.proto b/core/proto/android/app/pendingintent.proto new file mode 100644 index 000000000000..b562c0bb38e5 --- /dev/null +++ b/core/proto/android/app/pendingintent.proto @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2017 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. + */ + +syntax = "proto2"; + +option java_multiple_files = true; + +package android.app; + +/** + * An android.app.PendingIntent object. + */ +message PendingIntentProto { + optional string target = 1; +} diff --git a/core/proto/android/content/intent.proto b/core/proto/android/content/intent.proto index 4f49744d9c5a..3e5265ad9c13 100644 --- a/core/proto/android/content/intent.proto +++ b/core/proto/android/content/intent.proto @@ -15,15 +15,37 @@ */ syntax = "proto2"; +package android.content; + option java_package = "android.content"; option java_multiple_files = true; import "frameworks/base/core/proto/android/os/patternmatcher.proto"; -package android.content; - // Next Tag: 13 message IntentProto { + enum DockState { + // Used as an int value for Intent#EXTRA_DOCK_STATE to represent that + // the phone is not in any dock. + DOCK_STATE_UNDOCKED = 0; + + // Used as an int value for Intent#EXTRA_DOCK_STATE to represent that + // the phone is in a desk dock. + DOCK_STATE_DESK = 1; + + // Used as an int value for Intent#EXTRA_DOCK_STATE to represent that + // the phone is in a car dock. + DOCK_STATE_CAR = 2; + + // Used as an int value for Intent#EXTRA_DOCK_STATE to represent that + // the phone is in a analog (low end) dock. + DOCK_STATE_LE_DESK = 3; + + // Used as an int value for Intent#EXTRA_DOCK_STATE to represent that + // the phone is in a digital (high end) dock. + DOCK_STATE_HE_DESK = 4; + } + optional string action = 1; repeated string categories = 2; optional string data = 3; diff --git a/core/proto/android/internal/locallog.proto b/core/proto/android/internal/locallog.proto new file mode 100644 index 000000000000..51f6c1ce2fd7 --- /dev/null +++ b/core/proto/android/internal/locallog.proto @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2017 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. + */ + +syntax = "proto2"; +package com.android.internal.util; + +option java_multiple_files = true; + +message LocalLogProto { + repeated string lines = 1; +} diff --git a/core/proto/android/os/batterymanager.proto b/core/proto/android/os/batterymanager.proto new file mode 100644 index 000000000000..669bf2d0a6e7 --- /dev/null +++ b/core/proto/android/os/batterymanager.proto @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2017 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. + */ + +syntax = "proto2"; +package android.os; + +option java_multiple_files = true; + +message BatteryManagerProto { + enum PlugType { + PLUG_TYPE_NONE = 0; + PLUG_TYPE_AC = 1; + PLUG_TYPE_USB = 2; + PLUG_TYPE_WIRELESS = 4; + } +} diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto index 5a5454e2f6ae..f716ffee53af 100644 --- a/core/proto/android/os/incident.proto +++ b/core/proto/android/os/incident.proto @@ -20,6 +20,7 @@ option java_outer_classname = "IncidentProtoMetadata"; import "frameworks/base/libs/incident/proto/android/privacy.proto"; import "frameworks/base/libs/incident/proto/android/section.proto"; +import "frameworks/base/core/proto/android/server/powermanagerservice.proto"; import "frameworks/base/core/proto/android/service/appwidget.proto"; import "frameworks/base/core/proto/android/service/battery.proto"; import "frameworks/base/core/proto/android/service/batterystats.proto"; @@ -28,10 +29,10 @@ import "frameworks/base/core/proto/android/service/diskstats.proto"; import "frameworks/base/core/proto/android/service/netstats.proto"; import "frameworks/base/core/proto/android/service/notification.proto"; import "frameworks/base/core/proto/android/service/package.proto"; -import "frameworks/base/core/proto/android/service/power.proto"; import "frameworks/base/core/proto/android/service/print.proto"; import "frameworks/base/core/proto/android/service/procstats.proto"; import "frameworks/base/core/proto/android/server/activitymanagerservice.proto"; +import "frameworks/base/core/proto/android/server/alarmmanagerservice.proto"; import "frameworks/base/core/proto/android/providers/settings.proto"; import "frameworks/base/core/proto/android/os/incidentheader.proto"; import "frameworks/base/core/proto/android/os/kernelwake.proto"; @@ -105,7 +106,11 @@ message IncidentProto { (section).args = "package --proto" ]; - optional android.service.power.PowerServiceDumpProto power = 3009; + optional com.android.server.power.PowerManagerServiceDumpProto power = 3009 [ + (section).type = SECTION_DUMPSYS, + (section).args = "power --proto" + ]; + optional android.service.print.PrintServiceDumpProto print = 3010; optional android.service.procstats.ProcessStatsServiceDumpProto procstats = 3011 [ @@ -125,4 +130,9 @@ message IncidentProto { optional com.android.server.am.proto.ServiceProto amservices = 3014; optional com.android.server.am.proto.ProcessProto amprocesses = 3015; + + optional com.android.server.AlarmManagerServiceProto alarm = 3016 [ + (section).type = SECTION_DUMPSYS, + (section).args = "alarm --proto" + ]; } diff --git a/core/proto/android/os/powermanager.proto b/core/proto/android/os/powermanager.proto index 3bfe5d68eaae..e9f409d0d75d 100644 --- a/core/proto/android/os/powermanager.proto +++ b/core/proto/android/os/powermanager.proto @@ -15,10 +15,10 @@ */ syntax = "proto2"; -option java_multiple_files = true; - package android.os; +option java_multiple_files = true; + message PowerManagerProto { /* User activity events in PowerManager.java. */ enum UserActivityEvent { @@ -31,4 +31,84 @@ message PowerManagerProto { // Accessibility taking action on behalf of user. USER_ACTIVITY_EVENT_ACCESSIBILITY = 3; } + + enum WakeLockLevel { + // NOTE: Wake lock levels were previously defined as a bit field, except + // that only a few combinations were actually supported so the bit field + // was removed. This explains why the numbering scheme is so odd. If + // adding a new wake lock level, any unused value can be used. + + // Ensures that the CPU is running; the screen and keyboard backlight + // will be allowed to go off. + PARTIAL_WAKE_LOCK = 1; + + // Ensures that the screen is on (but may be dimmed); the keyboard + // backlight will be allowed to go off. If the user presses the power + // button, then the SCREEN_DIM_WAKE_LOCK will be implicitly released by + // the system, causing both the screen and the CPU to be turned off. + SCREEN_DIM_WAKE_LOCK = 6 [deprecated = true]; + + // Ensures that the screen is on at full brightness; the keyboard + // backlight will be allowed to go off. If the user presses the power + // button, then the SCREEN_BRIGHT_WAKE_LOCK will be implicitly released + // by the system, causing both the screen and the CPU to be turned off. + SCREEN_BRIGHT_WAKE_LOCK = 10 [deprecated = true]; + + // Ensures that the screen and keyboard backlight are on at full + // brightness. If the user presses the power button, then the + // FULL_WAKE_LOCK will be implicitly released by the system, causing + // both the screen and the CPU to be turned off. + FULL_WAKE_LOCK = 26 [deprecated = true]; + + // Turns the screen off when the proximity sensor activates. If the + // proximity sensor detects that an object is nearby, the screen turns + // off immediately. Shortly after the object moves away, the screen + // turns on again. + // A proximity wake lock does not prevent the device from falling asleep + // unlike FULL_WAKE_LOCK, SCREEN_BRIGHT_WAKE_LOCK and + // SCREEN_DIM_WAKE_LOCK. If there is no user activity and no other wake + // locks are held, then the device will fall asleep (and lock) as usual. + // However, the device will not fall asleep while the screen has been + // turned off by the proximity sensor because it effectively counts as + // ongoing user activity. + PROXIMITY_SCREEN_OFF_WAKE_LOCK = 32; + + // Put the screen in a low power state and allow the CPU to suspend if + // no other wake locks are held. This is used by the dream manager to + // implement doze mode. It currently has no effect unless the power + // manager is in the dozing state. + DOZE_WAKE_LOCK = 64; + + // Keep the device awake enough to allow drawing to occur. This is used + // by the window manager to allow applications to draw while the system + // is dozing. It currently has no effect unless the power manager is in + // the dozing state. + DRAW_WAKE_LOCK = 128; + } +} + +message PowerManagerInternalProto { + // Enum values gotten from PowerManagerInternal.java + enum Wakefulness { + // The device is asleep. It can only be awoken by a call to wakeUp(). + // The screen should be off or in the process of being turned off by the + // display controller. The device typically passes through the dozing + // state first. + WAKEFULNESS_ASLEEP = 0; + // The device is fully awake. It can be put to sleep by a call to + // goToSleep(). When the user activity timeout expires, the device may + // start dreaming or go to sleep. + WAKEFULNESS_AWAKE = 1; + // The device is dreaming. It can be awoken by a call to wakeUp(), which + // ends the dream. The device goes to sleep when goToSleep() is called, + // when the dream ends, or when unplugged. User activity may brighten + // the screen but does not end the dream. + WAKEFULNESS_DREAMING = 2; + // The device is dozing. It is almost asleep but is allowing a special + // low-power "doze" dream to run which keeps the display on but lets the + // application processor suspend. It can be awoken by a call to wakeUp() + // which ends the dream. The device fully goes to sleep if the dream + // cannot be started or ends on its own. + WAKEFULNESS_DOZING = 3; + } } diff --git a/core/proto/android/providers/settings.proto b/core/proto/android/providers/settings.proto index f0927134c37c..3411c6a6f1d4 100644 --- a/core/proto/android/providers/settings.proto +++ b/core/proto/android/providers/settings.proto @@ -599,6 +599,14 @@ message SettingProto { optional bool default_from_system = 6; } +message SettingsProto { + // Enum values gotten from Settings.java + enum ScreenBrightnessMode { + SCREEN_BRIGHTNESS_MODE_MANUAL = 0; + SCREEN_BRIGHTNESS_MODE_AUTOMATIC = 1; + } +} + message SettingsOperationProto { // When the operation happened optional int64 timestamp = 1; diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto index 788ac8f06840..c57cb72c8b68 100644 --- a/core/proto/android/server/activitymanagerservice.proto +++ b/core/proto/android/server/activitymanagerservice.proto @@ -69,12 +69,11 @@ message TaskRecordProto { optional string real_activity = 6; optional string orig_activity = 7; optional int32 activity_type = 8; - optional int32 return_to_type = 9; - optional int32 resize_mode = 10; - optional bool fullscreen = 11; - optional .android.graphics.RectProto bounds = 12; - optional int32 min_width = 13; - optional int32 min_height = 14; + optional int32 resize_mode = 9; + optional bool fullscreen = 10; + optional .android.graphics.RectProto bounds = 11; + optional int32 min_width = 12; + optional int32 min_height = 13; } message ActivityRecordProto { diff --git a/core/proto/android/server/alarmmanagerservice.proto b/core/proto/android/server/alarmmanagerservice.proto new file mode 100644 index 000000000000..d2cd19072128 --- /dev/null +++ b/core/proto/android/server/alarmmanagerservice.proto @@ -0,0 +1,247 @@ +/* + * Copyright (C) 2017 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. + */ + +syntax = "proto2"; + +import "frameworks/base/core/proto/android/app/alarmmanager.proto"; +import "frameworks/base/core/proto/android/app/pendingintent.proto"; +import "frameworks/base/core/proto/android/internal/locallog.proto"; +import "frameworks/base/core/proto/android/os/worksource.proto"; + +package com.android.server; + +option java_multiple_files = true; + +message AlarmManagerServiceProto { + optional int64 current_time = 1; + optional int64 elapsed_realtime = 2; + optional int64 last_time_change_clock_time = 3; + optional int64 last_time_change_realtime = 4; + // Current settings + optional ConstantsProto settings = 5; + // UIDs currently in the foreground. + repeated int32 foreground_uids = 6; + // Packages forced into app standby. + repeated string forced_app_standby_packages = 7; + + optional bool is_interactive = 8; + // Only valid if is_interactive is false. + optional int64 time_since_non_interactive_ms = 9; + // Only valid if is_interactive is false. + optional int64 max_wakeup_delay_ms = 10; + // Only valid if is_interactive is false. + optional int64 time_since_last_dispatch_ms = 11; + // Only valid if is_interactive is false. + optional int64 time_until_next_non_wakeup_delivery_ms = 12; + + optional int64 time_until_next_non_wakeup_alarm_ms = 13; + optional int64 time_until_next_wakeup_ms = 14; + optional int64 time_since_last_wakeup_ms = 15; + // Time since the last wakeup was set. + optional int64 time_since_last_wakeup_set_ms = 16; + optional int64 time_change_event_count = 17; + // The current set of user whitelisted apps for device idle mode, meaning + // these are allowed to freely schedule alarms. These are app IDs, not UIDs. + repeated int32 device_idle_user_whitelist_app_ids = 18; + + repeated AlarmClockMetadataProto next_alarm_clock_metadata = 19; + + repeated BatchProto pending_alarm_batches = 20; + + // List of alarms per uid deferred due to user applied background restrictions + // on the source app. + repeated AlarmProto pending_user_blocked_background_alarms = 21; + + // When idling mode will end. Will be empty if the device is not currently + // idling. + optional AlarmProto pending_idle_until = 22; + + // Any alarms that we don't want to run during idle mode. Will be empty if the + // device is not currently idling. + repeated AlarmProto pending_while_idle_alarms = 23; + + // This is a special alarm that will put the system into idle until it goes + // off. The caller has given the time they want this to happen at. + optional AlarmProto next_wake_from_idle = 24; + + repeated AlarmProto past_due_non_wakeup_alarms = 25; + + // Number of delayed alarms. + optional int32 delayed_alarm_count = 26; + // The total amount of time alarms had been delayed. Overlapping alarms are + // only counted once (ie. If two alarms were meant to trigger at the same time + // but were delayed by 5 seconds, the total time would be 5 seconds). + optional int64 total_delay_time_ms = 27; + optional int64 max_delay_duration_ms = 28; + optional int64 max_non_interactive_duration_ms = 29; + + optional int32 broadcast_ref_count = 30; + // Canonical count of (operation.send() - onSendFinished()) and listener + // send/complete/timeout invocations. + optional int32 pending_intent_send_count = 31; + optional int32 pending_intent_finish_count = 32; + optional int32 listener_send_count = 33; + optional int32 listener_finish_count = 34; + + repeated InFlightProto outstanding_deliveries = 35; + + // Minimum time between ALLOW_WHILE_IDLE alarms when system is idling. It + // should be either CosntantsProto.allow_while_idle_short_duration_ms or + // ConstantsProto.allow_while_idle_long_duration_ms. + optional int64 allow_while_idle_min_duration_ms = 36; + + message LastAllowWhileIdleDispatch { + optional int32 uid = 1; + // In the 'elapsed' timebase. + optional int64 time_ms = 2; + } + // For each uid, this is the last time we dispatched an "allow while idle" + // alarm, used to determine the earliest we can dispatch the next such alarm. + repeated LastAllowWhileIdleDispatch last_allow_while_idle_dispatch_times = 37; + + optional com.android.internal.util.LocalLogProto recent_problems = 38; + + message TopAlarm { + optional int32 uid = 1; + optional string package_name = 2; + optional FilterStatsProto filter = 3; + } + repeated TopAlarm top_alarms = 39; + + message AlarmStat { + optional BroadcastStatsProto broadcast = 1; + repeated FilterStatsProto filters = 2; + } + repeated AlarmStat alarm_stats = 40; + + repeated IdleDispatchEntryProto allow_while_idle_dispatches = 41; + repeated WakeupEventProto recent_wakeup_history = 42; +} + +// This is a soft wrapper for alarm clock information. It is not representative +// of an android.app.AlarmManager.AlarmClockInfo object. +message AlarmClockMetadataProto { + optional int32 user = 1; + optional bool is_pending_send = 2; + // This value is UTC wall clock time in milliseconds, as returned by + // System#currentTimeMillis() for example. + optional int64 trigger_time_ms = 3; +} + +// A com.android.server.AlarmManagerService.Alarm object. +message AlarmProto { + optional string tag = 1; + optional .android.app.AlarmManagerProto.AlarmType type = 2; + // How long until the alarm goes off, in the 'elapsed' timebase. + optional int64 when_elapsed_ms = 3; + optional int64 window_length_ms = 4; + optional int64 repeat_interval_ms = 5; + optional int32 count = 6; + optional int32 flags = 7; + optional .android.app.AlarmClockInfoProto alarm_clock = 8; + optional .android.app.PendingIntentProto operation = 9; + optional string listener = 10; +} + +// A com.android.server.AlarmManagerService.Batch object. +message BatchProto { + // Start time in terms of elapsed realtime. + optional int64 start_realtime = 1; + // End time in terms of elapsed realtime. + optional int64 end_realtime = 2; + optional int32 flags = 3; + repeated AlarmProto alarms = 4; +} + +// A com.android.server.AlarmManagerService.BroadcastStats object. +message BroadcastStatsProto { + optional int32 uid = 1; + optional string package_name = 2; + // The total amount of time this broadcast was in flight. + optional int64 total_flight_duration_ms = 3; + optional int32 count = 4; + optional int32 wakeup_count = 5; + // The last time this first became active (when nesting changed from 0 to 1) + // in terms of elapsed realtime. + optional int64 start_time_realtime = 6; + // The broadcast is active if nesting > 0. + optional int32 nesting = 7; +} + +// A com.android.server.AlarmManagerService.Constants object. +message ConstantsProto { + // Minimum futurity of a new alarm. + optional int64 min_futurity_duration_ms = 1; + // Minimum alarm recurrence interval. + optional int64 min_interval_duration_ms = 2; + // Direct alarm listener callback timeout. + optional int64 listener_timeout_duration_ms = 3; + // Minimum time between ALLOW_WHILE_IDLE alarms when system is not idle. + optional int64 allow_while_idle_short_duration_ms = 4; + // Minimum time between ALLOW_WHILE_IDLE alarms when system is idling. + optional int64 allow_while_idle_long_duration_ms = 5; + // BroadcastOptions.setTemporaryAppWhitelistDuration() to use for FLAG_ALLOW_WHILE_IDLE. + optional int64 allow_while_idle_whitelist_duration_ms = 6; +} + +// A com.android.server.AlarmManagerService.FilterStats object. +message FilterStatsProto { + optional string tag = 1; + // The last time this filter when in flight, in terms of elapsed realtime. + optional int64 last_flight_time_realtime = 2; + // The total amount of time this filter was in flight. + optional int64 total_flight_duration_ms = 3; + optional int32 count = 4; + optional int32 wakeup_count = 5; + // The last time this first became active (when nesting changed from 0 to 1) + // in terms of elapsed realtime. + optional int64 start_time_realtime = 6; + // The filter is active if nesting > 0. + optional int32 nesting = 7; +} + +// A com.android.server.AlarmManagerService.IdleDispatchEntry object. +message IdleDispatchEntryProto { + optional int32 uid = 1; + optional string pkg = 2; + optional string tag = 3; + optional string op = 4; + // Time when this entry was created, in terms of elapsed realtime. + optional int64 entry_creation_realtime = 5; + // For a RESCHEDULED op, this is the last time we dispatched an "allow while + // idle" alarm for the UID. For a SET op, this is when the alarm was + // triggered. Times are in the 'elapsed' timebase. + optional int64 arg_realtime = 6; +} + +// A com.android.server.AlarmManagerService.InFlight object. +message InFlightProto { + optional int32 uid = 1; + optional string tag = 2; + optional int64 when_elapsed_ms = 3; + optional .android.app.AlarmManagerProto.AlarmType alarm_type = 4; + optional .android.app.PendingIntentProto pending_intent = 5; + optional BroadcastStatsProto broadcast_stats = 6; + optional FilterStatsProto filter_stats = 7; + optional .android.os.WorkSourceProto work_source = 8; +} + +// A com.android.server.AlarmManagerService.WakeupEvent object. +message WakeupEventProto { + optional int32 uid = 1; + optional string action = 2; + optional int64 when = 3; +} diff --git a/core/proto/android/service/power.proto b/core/proto/android/server/powermanagerservice.proto index 5d53847a65aa..d442acfd91be 100644 --- a/core/proto/android/service/power.proto +++ b/core/proto/android/server/powermanagerservice.proto @@ -15,16 +15,22 @@ */ syntax = "proto2"; -package android.service.power; +package com.android.server.power; option java_multiple_files = true; -option java_outer_classname = "PowerServiceProto"; +import "frameworks/base/core/proto/android/app/activitymanager.proto"; +import "frameworks/base/core/proto/android/content/intent.proto"; +import "frameworks/base/core/proto/android/os/batterymanager.proto"; import "frameworks/base/core/proto/android/os/looper.proto"; +import "frameworks/base/core/proto/android/os/powermanager.proto"; import "frameworks/base/core/proto/android/os/worksource.proto"; -import "frameworks/base/core/proto/android/service/wirelesschargerdetector.proto"; +import "frameworks/base/core/proto/android/providers/settings.proto"; +import "frameworks/base/core/proto/android/server/wirelesschargerdetector.proto"; +import "frameworks/base/core/proto/android/view/display.proto"; -message PowerServiceDumpProto { +message PowerManagerServiceDumpProto { + // A com.android.server.power.PowerManagerService.Constants object. message ConstantsProto { optional bool is_no_cached_wake_locks = 1; } @@ -44,79 +50,14 @@ message PowerServiceDumpProto { optional bool is_screen_dim = 2; optional bool is_screen_dream = 3; } - message UidProto { - // Enum values gotten from ActivityManager.java - enum ProcessState { - // Process is a persistent system process. - PROCESS_STATE_PERSISTENT = 0; - // Process is a persistent system process and is doing UI. - PROCESS_STATE_PERSISTENT_UI = 1; - // Process is hosting the current top activities. Note that this - // covers all activities that are visible to the user. - PROCESS_STATE_TOP = 2; - // Process is hosting a foreground service due to a system binding. - PROCESS_STATE_BOUND_FOREGROUND_SERVICE = 3; - // Process is hosting a foreground service. - PROCESS_STATE_FOREGROUND_SERVICE = 4; - // Same as {@link #PROCESS_STATE_TOP} but while device is sleeping. - PROCESS_STATE_TOP_SLEEPING = 5; - // Process is important to the user, and something they are aware of. - PROCESS_STATE_IMPORTANT_FOREGROUND = 6; - // Process is important to the user, but not something they are aware of. - PROCESS_STATE_IMPORTANT_BACKGROUND = 7; - // Process is in the background running a backup/restore operation. - PROCESS_STATE_BACKUP = 8; - // Process is in the background, but it can't restore its state so - // we want to try to avoid killing it. - PROCESS_STATE_HEAVY_WEIGHT = 9; - // Process is in the background running a service. - PROCESS_STATE_SERVICE = 10; - // Process is in the background running a receiver. - PROCESS_STATE_RECEIVER = 11; - // Process is in the background but hosts the home activity. - PROCESS_STATE_HOME = 12; - // Process is in the background but hosts the last shown activity. - PROCESS_STATE_LAST_ACTIVITY = 13; - // Process is being cached for later use and contains activities. - PROCESS_STATE_CACHED_ACTIVITY = 14; - // Process is being cached for later use and is a client of another - // cached process that contains activities. - PROCESS_STATE_CACHED_ACTIVITY_CLIENT = 15; - // Process is being cached for later use and is empty. - PROCESS_STATE_CACHED_EMPTY = 16; - // Process does not exist. - PROCESS_STATE_NONEXISTENT = 17; - } + // A com.android.server.power.PowerManagerService.UidState object. + message UidStateProto { optional int32 uid = 1; optional string uid_string = 2; optional bool is_active = 3; optional int32 num_wake_locks = 4; optional bool is_process_state_unknown = 5; - optional ProcessState process_state = 6; - } - - // Enum values gotten from PowerManagerInternal.java - enum Wakefulness { - WAKEFULNESS_ASLEEP = 0; - WAKEFULNESS_AWAKE = 1; - WAKEFULNESS_DREAMING = 2; - WAKEFULNESS_DOZING = 3; - WAKEFULNESS_UNKNOWN = 4; - } - // Enum values gotten from BatteryManager.java - enum PlugType { - PLUG_TYPE_NONE = 0; - PLUG_TYPE_PLUGGED_AC = 1; - PLUG_TYPE_PLUGGED_USB = 2; - PLUG_TYPE_PLUGGED_WIRELESS = 4; - } - // Enum values gotten from Intent.java - enum DockState { - DOCK_STATE_UNDOCKED = 0; - DOCK_STATE_DESK = 1; - DOCK_STATE_CAR = 2; - DOCK_STATE_LE_DESK = 3; - DOCK_STATE_HE_DESK = 4; + optional .android.app.ActivityManagerProto.ProcessState process_state = 6; } optional ConstantsProto constants = 1; @@ -124,18 +65,18 @@ message PowerServiceDumpProto { // changed and need to be recalculated. optional int32 dirty = 2; // Indicates whether the device is awake or asleep or somewhere in between. - optional Wakefulness wakefulness = 3; + optional .android.os.PowerManagerInternalProto.Wakefulness wakefulness = 3; optional bool is_wakefulness_changing = 4; // True if the device is plugged into a power source. optional bool is_powered = 5; // The current plug type - optional PlugType plug_type = 6; + optional .android.os.BatteryManagerProto.PlugType plug_type = 6; // The current battery level percentage. optional int32 battery_level = 7; // The battery level percentage at the time the dream started. optional int32 battery_level_when_dream_started = 8; // The current dock state. - optional DockState dock_state = 9; + optional .android.content.IntentProto.DockState dock_state = 9; // True if the device should stay on. optional bool is_stay_on = 10; // True if the proximity sensor reads a positive result. @@ -215,8 +156,8 @@ message PowerServiceDumpProto { // Some uids have actually changed while mUidsChanging was true. optional bool are_uids_changed = 45; // List of UIDs and their states - repeated UidProto uids = 46; - optional android.os.LooperProto looper = 47; + repeated UidStateProto uid_states = 46; + optional .android.os.LooperProto looper = 47; // List of all wake locks acquired by applications. repeated WakeLockProto wake_locks = 48; // List of all suspend blockers. @@ -224,11 +165,13 @@ message PowerServiceDumpProto { optional WirelessChargerDetectorProto wireless_charger_detector = 50; } +// A com.android.server.power.PowerManagerService.SuspendBlockerImpl object. message SuspendBlockerProto { optional string name = 1; optional int32 reference_count = 2; } +// A com.android.server.power.PowerManagerService.WakeLock object. message WakeLockProto { message WakeLockFlagsProto { // Turn the screen on when the wake lock is acquired. @@ -238,27 +181,7 @@ message WakeLockProto { optional bool is_on_after_release = 2; } - // Enum values gotten from PowerManager.java - enum LockLevel { - WAKE_LOCK_INVALID = 0; - // Ensures that the CPU is running. - PARTIAL_WAKE_LOCK = 1; - // Ensures that the screen is on (but may be dimmed). - SCREEN_DIM_WAKE_LOCK = 6; - // Ensures that the screen is on at full brightness. - SCREEN_BRIGHT_WAKE_LOCK = 10; - // Ensures that the screen and keyboard backlight are on at full brightness. - FULL_WAKE_LOCK = 26; - // Turns the screen off when the proximity sensor activates. - PROXIMITY_SCREEN_OFF_WAKE_LOCK = 32; - // Put the screen in a low power state and allow the CPU to suspend - // if no other wake locks are held. - DOZE_WAKE_LOCK = 64; - // Keep the device awake enough to allow drawing to occur. - DRAW_WAKE_LOCK = 128; - } - - optional LockLevel lock_level = 1; + optional .android.os.PowerManagerProto.WakeLockLevel lock_level = 1; optional string tag = 2; optional WakeLockFlagsProto flags = 3; optional bool is_disabled = 4; @@ -269,7 +192,7 @@ message WakeLockProto { optional int32 uid = 7; // Owner PID optional int32 pid = 8; - optional android.os.WorkSourceProto work_source = 9; + optional .android.os.WorkSourceProto work_source = 9; } message PowerServiceSettingsAndConfigurationDumpProto { @@ -285,22 +208,6 @@ message PowerServiceSettingsAndConfigurationDumpProto { optional int32 setting_for_vr_default = 4; } - // Enum values gotten from Settings.java - enum ScreenBrightnessMode { - SCREEN_BRIGHTNESS_MODE_MANUAL = 0; - SCREEN_BRIGHTNESS_MODE_AUTOMATIC = 1; - } - // Enum values gotten from Display.java - enum DisplayState { - DISPLAY_STATE_UNKNOWN = 0; - DISPLAY_STATE_OFF = 1; - DISPLAY_STATE_ON = 2; - DISPLAY_STATE_DOZE = 3; - DISPLAY_STATE_DOZE_SUSPEND = 4; - DISPLAY_STATE_VR = 5; - } - - // True to decouple auto-suspend mode from the display state. optional bool is_decouple_hal_auto_suspend_mode_from_display_config = 1; // True to decouple interactive mode from the display state. @@ -371,7 +278,7 @@ message PowerServiceSettingsAndConfigurationDumpProto { // Use 0 if there is no adjustment. optional float screen_auto_brightness_adjustment_setting = 31; // The screen brightness mode. - optional ScreenBrightnessMode screen_brightness_mode_setting = 32; + optional .android.providers.settings.SettingsProto.ScreenBrightnessMode screen_brightness_mode_setting = 32; // The screen brightness setting override from the window manager // to allow the current foreground activity to override the brightness. // Use -1 to disable. @@ -393,7 +300,7 @@ message PowerServiceSettingsAndConfigurationDumpProto { // Use NaN to disable. optional float temporary_screen_auto_brightness_adjustment_setting_override = 37; // The screen state to use while dozing. - optional DisplayState doze_screen_state_override_from_dream_manager = 38; + optional .android.view.DisplayProto.DisplayState doze_screen_state_override_from_dream_manager = 38; // The screen brightness to use while dozing. optional float dozed_screen_brightness_override_from_dream_manager = 39; // Screen brightness settings limits. diff --git a/core/proto/android/service/wirelesschargerdetector.proto b/core/proto/android/server/wirelesschargerdetector.proto index bd697c85b8ae..89cf2f8900fe 100644 --- a/core/proto/android/service/wirelesschargerdetector.proto +++ b/core/proto/android/server/wirelesschargerdetector.proto @@ -15,7 +15,7 @@ */ syntax = "proto2"; -package android.service.power; +package com.android.server.power; option java_multiple_files = true; @@ -46,4 +46,4 @@ message WirelessChargerDetectorProto { optional VectorProto first_sample = 9; // The value of the last sample that was collected. optional VectorProto last_sample = 10; -}
\ No newline at end of file +} diff --git a/core/proto/android/service/battery.proto b/core/proto/android/service/battery.proto index 998a808b6e52..8382b8262b95 100644 --- a/core/proto/android/service/battery.proto +++ b/core/proto/android/service/battery.proto @@ -20,13 +20,9 @@ package android.service.battery; option java_multiple_files = true; option java_outer_classname = "BatteryServiceProto"; +import "frameworks/base/core/proto/android/os/batterymanager.proto"; + message BatteryServiceDumpProto { - enum BatteryPlugged { - BATTERY_PLUGGED_NONE = 0; - BATTERY_PLUGGED_AC = 1; - BATTERY_PLUGGED_USB = 2; - BATTERY_PLUGGED_WIRELESS = 4; - } enum BatteryStatus { BATTERY_STATUS_INVALID = 0; BATTERY_STATUS_UNKNOWN = 1; @@ -49,7 +45,7 @@ message BatteryServiceDumpProto { // If true: UPDATES STOPPED -- use 'reset' to restart optional bool are_updates_stopped = 1; // Plugged status of power sources - optional BatteryPlugged plugged = 2; + optional android.os.BatteryManagerProto.PlugType plugged = 2; // Max current in microamperes optional int32 max_charging_current = 3; // Max voltage diff --git a/core/proto/android/view/display.proto b/core/proto/android/view/display.proto new file mode 100644 index 000000000000..210c6d103faa --- /dev/null +++ b/core/proto/android/view/display.proto @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2017 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. + */ + +syntax = "proto2"; +package android.view; + +option java_multiple_files = true; + +message DisplayProto { + enum DisplayState { + // The display state is unknown. + DISPLAY_STATE_UNKNOWN = 0; + // The display state is off. + DISPLAY_STATE_OFF = 1; + // The display state is on. + DISPLAY_STATE_ON = 2; + // The display is dozing in a low power state; it is still on but is + // optimized for showing system-provided content while the device is + // non-interactive. + DISPLAY_STATE_DOZE = 3; + // The display is dozing in a suspended low power state; it is still on + // but is optimized for showing static system-provided content while the + // device is non-interactive. + DISPLAY_STATE_DOZE_SUSPEND = 4; + // The display is on and optimized for VR mode. + DISPLAY_STATE_VR = 5; + } +} diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 105bb7ef9f3f..507a431e8a28 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -183,15 +183,23 @@ <protected-broadcast android:name="android.bluetooth.a2dp-sink.profile.action.AUDIO_CONFIG_CHANGED" /> <protected-broadcast + android:name="android.bluetooth.avrcp-controller.profile.action.BROWSE_CONNECTION_STATE_CHANGED" /> + <protected-broadcast android:name="android.bluetooth.avrcp-controller.profile.action.CONNECTION_STATE_CHANGED" /> <protected-broadcast + android:name="android.bluetooth.avrcp-controller.profile.action.FOLDER_LIST" /> + <protected-broadcast + android:name="android.bluetooth.avrcp-controller.profile.action.TRACK_EVENT" /> + <protected-broadcast android:name="android.bluetooth.input.profile.action.CONNECTION_STATE_CHANGED" /> <protected-broadcast + android:name="android.bluetooth.input.profile.action.IDLE_TIME_CHANGED" /> + <protected-broadcast android:name="android.bluetooth.input.profile.action.PROTOCOL_MODE_CHANGED" /> <protected-broadcast android:name="android.bluetooth.input.profile.action.VIRTUAL_UNPLUG_STATUS" /> <protected-broadcast - android:name="android.bluetooth.inputhost.profile.action.CONNECTION_STATE_CHANGED" /> + android:name="android.bluetooth.hiddevice.profile.action.CONNECTION_STATE_CHANGED" /> <protected-broadcast android:name="android.bluetooth.map.profile.action.CONNECTION_STATE_CHANGED" /> <protected-broadcast android:name="android.bluetooth.mapmce.profile.action.CONNECTION_STATE_CHANGED" /> diff --git a/core/res/res/layout/magnifier.xml b/core/res/res/layout/magnifier.xml index 181e5e54bb00..f3344c7470a1 100644 --- a/core/res/res/layout/magnifier.xml +++ b/core/res/res/layout/magnifier.xml @@ -18,10 +18,18 @@ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:background="?android:attr/floatingToolbarPopupBackgroundDrawable"> - <ImageView - android:id="@+id/magnifier_image" - android:layout_width="match_parent" - android:layout_height="match_parent" /> + android:layout_height="wrap_content"> + <LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/magnifier_inner" + android:layout_width="@android:dimen/magnifier_width" + android:layout_height="@android:dimen/magnifier_height" + android:elevation="@android:dimen/magnifier_elevation" + android:background="?android:attr/floatingToolbarPopupBackgroundDrawable" + android:scaleType="fitXY"> + <ImageView + android:id="@+id/magnifier_image" + android:layout_width="match_parent" + android:layout_height="match_parent" /> + </LinearLayout> </LinearLayout> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 5a497acd38eb..f5b391ec6fee 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -3654,7 +3654,7 @@ </declare-styleable> <!-- Specify one or more <code>t3tPmm-filter</code> elements inside a - <code>host-nfcf-service</code> element to specify a LF_T3T_PMM --> + <code>host-nfcf-service</code> element to specify a LF_T3T_PMM. --> <declare-styleable name="T3tPmmFilter"> <attr name="name" /> diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index 0e9028708d66..be0f6d9ac647 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -1334,7 +1334,7 @@ split that contains the defined component. --> <attr name="splitName" format="string" /> - <!-- Specifies the target sandbox this app wants to use. Higher sanbox versions + <!-- Specifies the target sandbox this app wants to use. Higher sandbox versions will have increasing levels of security. <p>The default value of this attribute is <code>1</code>. --> diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index 14069e779939..dc75ba6077e7 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -521,10 +521,11 @@ <dimen name="content_rect_bottom_clip_allowance">20dp</dimen> <!-- Magnifier dimensions --> - <dimen name="magnifier_width">200dp</dimen> + <dimen name="magnifier_width">164dp</dimen> <dimen name="magnifier_height">48dp</dimen> <dimen name="magnifier_elevation">2dp</dimen> <dimen name="magnifier_offset">42dp</dimen> + <item type="dimen" format="float" name="magnifier_zoom_scale">1.25</item> <dimen name="chooser_grid_padding">0dp</dimen> <!-- Spacing around the background change frome service to non-service --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 46a6e6efca45..896de53cbc4e 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2479,10 +2479,12 @@ <!-- Magnifier --> <java-symbol type="id" name="magnifier_image" /> + <java-symbol type="id" name="magnifier_inner" /> <java-symbol type="layout" name="magnifier" /> <java-symbol type="dimen" name="magnifier_width" /> <java-symbol type="dimen" name="magnifier_height" /> <java-symbol type="dimen" name="magnifier_elevation" /> + <java-symbol type="dimen" name="magnifier_zoom_scale" /> <java-symbol type="dimen" name="magnifier_offset" /> <java-symbol type="string" name="date_picker_prev_month_button" /> diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerStressTestRunner.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerStressTestRunner.java index fbaf0f324ab1..0806fa0b9879 100644 --- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerStressTestRunner.java +++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerStressTestRunner.java @@ -20,7 +20,6 @@ import android.os.Bundle; import android.test.InstrumentationTestRunner; import android.test.InstrumentationTestSuite; -import com.android.connectivitymanagertest.stress.WifiApStress; import com.android.connectivitymanagertest.stress.WifiStressTest; import junit.framework.TestSuite; @@ -35,7 +34,7 @@ import junit.framework.TestSuite; */ public class ConnectivityManagerStressTestRunner extends InstrumentationTestRunner { - private int mSoftApIterations = 100; + private int mSoftApIterations = 0; private int mScanIterations = 100; private int mReconnectIterations = 100; // sleep time before restart wifi, default is set to 2 minutes @@ -47,7 +46,6 @@ public class ConnectivityManagerStressTestRunner extends InstrumentationTestRunn @Override public TestSuite getAllTests() { TestSuite suite = new InstrumentationTestSuite(this); - suite.addTestSuite(WifiApStress.class); suite.addTestSuite(WifiStressTest.class); return suite; } @@ -60,13 +58,6 @@ public class ConnectivityManagerStressTestRunner extends InstrumentationTestRunn @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); - String valueStr = icicle.getString("softap_iterations"); - if (valueStr != null) { - int iteration = Integer.parseInt(valueStr); - if (iteration > 0) { - mSoftApIterations = iteration; - } - } String scanIterationStr = icicle.getString("scan_iterations"); if (scanIterationStr != null) { diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestBase.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestBase.java index 64fed7f77176..3706e4b3d8e8 100644 --- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestBase.java +++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestBase.java @@ -129,12 +129,6 @@ public class ConnectivityManagerTestBase extends InstrumentationTestCase { // Get an instance of WifiManager mWifiManager =(WifiManager)mContext.getSystemService(Context.WIFI_SERVICE); - if (mWifiManager.isWifiApEnabled()) { - // if soft AP is enabled, disable it - mWifiManager.setWifiApEnabled(null, false); - logv("Disable soft ap"); - } - // register a connectivity receiver for CONNECTIVITY_ACTION; mConnectivityReceiver = new ConnectivityReceiver(); mContext.registerReceiver(mConnectivityReceiver, diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerUnitTestRunner.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerUnitTestRunner.java index 0e57a006eed0..746cb841da66 100644 --- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerUnitTestRunner.java +++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerUnitTestRunner.java @@ -19,7 +19,6 @@ package com.android.connectivitymanagertest; import android.test.InstrumentationTestRunner; import android.test.InstrumentationTestSuite; import com.android.connectivitymanagertest.unit.WifiClientTest; -import com.android.connectivitymanagertest.unit.WifiSoftAPTest; import junit.framework.TestSuite; @@ -35,7 +34,6 @@ public class ConnectivityManagerUnitTestRunner extends InstrumentationTestRunner public TestSuite getAllTests() { TestSuite suite = new InstrumentationTestSuite(this); suite.addTestSuite(WifiClientTest.class); - suite.addTestSuite(WifiSoftAPTest.class); return suite; } diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/stress/WifiApStress.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/stress/WifiApStress.java deleted file mode 100644 index de934b9c9d82..000000000000 --- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/stress/WifiApStress.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright (C) 2010, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.connectivitymanagertest.stress; - - -import android.net.wifi.WifiConfiguration; -import android.net.wifi.WifiConfiguration.AuthAlgorithm; -import android.net.wifi.WifiConfiguration.KeyMgmt; -import android.net.wifi.WifiManager; -import android.os.Environment; -import android.test.suitebuilder.annotation.LargeTest; - -import com.android.connectivitymanagertest.ConnectivityManagerStressTestRunner; -import com.android.connectivitymanagertest.ConnectivityManagerTestBase; - -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileWriter; - -/** - * Stress test setting up device as wifi hotspot - */ -public class WifiApStress extends ConnectivityManagerTestBase { - private static String NETWORK_ID = "AndroidAPTest"; - private static String PASSWD = "androidwifi"; - private final static String OUTPUT_FILE = "WifiStressTestOutput.txt"; - private int mTotalIterations; - private BufferedWriter mOutputWriter = null; - private int mLastIteration = 0; - private boolean mWifiOnlyFlag; - - public WifiApStress() { - super(WifiApStress.class.getSimpleName()); - } - - @Override - protected void setUp() throws Exception { - super.setUp(); - ConnectivityManagerStressTestRunner mRunner = - (ConnectivityManagerStressTestRunner)getInstrumentation(); - mTotalIterations = mRunner.getSoftApInterations(); - mWifiOnlyFlag = mRunner.isWifiOnly(); - turnScreenOn(); - } - - @Override - protected void tearDown() throws Exception { - // write the total number of iterations into output file - mOutputWriter = new BufferedWriter(new FileWriter(new File( - Environment.getExternalStorageDirectory(), OUTPUT_FILE))); - mOutputWriter.write(String.format("iteration %d out of %d\n", - mLastIteration + 1, mTotalIterations)); - mOutputWriter.flush(); - mOutputWriter.close(); - super.tearDown(); - } - - @LargeTest - public void testWifiHotSpot() { - if (mWifiOnlyFlag) { - logv(getName() + " is excluded for wi-fi only test"); - return; - } - WifiConfiguration config = new WifiConfiguration(); - config.SSID = NETWORK_ID; - config.allowedKeyManagement.set(KeyMgmt.WPA_PSK); - config.allowedAuthAlgorithms.set(AuthAlgorithm.OPEN); - config.preSharedKey = PASSWD; - - // if wifiap enabled, disable it - assertTrue("failed to disable wifi hotspot", - mWifiManager.setWifiApEnabled(config, false)); - assertTrue("wifi hotspot not enabled", waitForWifiApState( - WifiManager.WIFI_AP_STATE_DISABLED, 2 * LONG_TIMEOUT)); - - // if Wifi is enabled, disable it - if (mWifiManager.isWifiEnabled()) { - assertTrue("failed to disable wifi", disableWifi()); - // wait for the wifi state to be DISABLED - assertTrue("wifi state not disabled", waitForWifiState( - WifiManager.WIFI_STATE_DISABLED, LONG_TIMEOUT)); - } - int i; - for (i = 0; i < mTotalIterations; i++) { - logv("iteration: " + i); - mLastIteration = i; - // enable Wifi tethering - assertTrue("failed to enable wifi hotspot", - mWifiManager.setWifiApEnabled(config, true)); - // wait for wifi ap state to be ENABLED - assertTrue("wifi hotspot not enabled", waitForWifiApState( - WifiManager.WIFI_AP_STATE_ENABLED, 2 * LONG_TIMEOUT)); - // wait for wifi tethering result - assertTrue("tether state not changed", waitForTetherStateChange(LONG_TIMEOUT)); - // allow the wifi tethering to be enabled for 10 seconds - try { - Thread.sleep(2 * SHORT_TIMEOUT); - } catch (Exception e) { - // ignore - } - assertTrue("no uplink data connection after Wi-Fi tethering", pingTest()); - // disable wifi hotspot - assertTrue("failed to disable wifi hotspot", - mWifiManager.setWifiApEnabled(config, false)); - assertTrue("wifi hotspot not enabled", waitForWifiApState( - WifiManager.WIFI_AP_STATE_DISABLED, 2 * LONG_TIMEOUT)); - assertFalse("wifi hotspot still enabled", mWifiManager.isWifiApEnabled()); - } - } - -} diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/unit/WifiSoftAPTest.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/unit/WifiSoftAPTest.java deleted file mode 100644 index f202862f6dbf..000000000000 --- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/unit/WifiSoftAPTest.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.connectivitymanagertest.unit; - -import android.content.Context; -import android.net.wifi.WifiManager; -import android.net.wifi.WifiConfiguration; -import android.net.wifi.WifiConfiguration.KeyMgmt; - -import android.test.suitebuilder.annotation.LargeTest; -import android.test.AndroidTestCase; - -import android.util.Log; - -/** - * Test Wifi soft AP configuration - */ -public class WifiSoftAPTest extends AndroidTestCase { - - private WifiManager mWifiManager; - private WifiConfiguration mWifiConfig = null; - private final String TAG = "WifiSoftAPTest"; - private final int DURATION = 10000; - - @Override - protected void setUp() throws Exception { - super.setUp(); - mWifiManager = (WifiManager) getContext().getSystemService(Context.WIFI_SERVICE); - assertNotNull(mWifiManager); - assertTrue(mWifiManager.setWifiApEnabled(null, true)); - mWifiConfig = mWifiManager.getWifiApConfiguration(); - if (mWifiConfig != null) { - Log.v(TAG, "mWifiConfig is " + mWifiConfig.toString()); - } else { - Log.v(TAG, "mWifiConfig is null."); - } - } - - @Override - protected void tearDown() throws Exception { - Log.v(TAG, "turn off wifi tethering"); - mWifiManager.setWifiApEnabled(null, false); - super.tearDown(); - } - - // Test case 1: Test the soft AP SSID with letters - @LargeTest - public void testApSsidWithAlphabet() { - WifiConfiguration config = new WifiConfiguration(); - config.SSID = "abcdefghijklmnopqrstuvwxyz"; - config.allowedKeyManagement.set(KeyMgmt.NONE); - mWifiConfig = config; - assertTrue(mWifiManager.setWifiApEnabled(mWifiConfig, true)); - try { - Thread.sleep(DURATION); - } catch (InterruptedException e) { - Log.v(TAG, "exception " + e.getStackTrace()); - assertFalse(true); - } - assertNotNull(mWifiManager.getWifiApConfiguration()); - assertEquals("wifi AP state is not enabled", WifiManager.WIFI_AP_STATE_ENABLED, - mWifiManager.getWifiApState()); - } -} diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothStressTest.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothStressTest.java index 31ce95eea1d2..4b32ceae0617 100644 --- a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothStressTest.java +++ b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothStressTest.java @@ -256,13 +256,13 @@ public class BluetoothStressTest extends InstrumentationTestCase { mTestUtils.unpair(mAdapter, device); mTestUtils.pair(mAdapter, device, BluetoothTestRunner.sDevicePairPasskey, BluetoothTestRunner.sDevicePairPin); - mTestUtils.disconnectProfile(mAdapter, device, BluetoothProfile.INPUT_DEVICE, null); + mTestUtils.disconnectProfile(mAdapter, device, BluetoothProfile.HID_HOST, null); for (int i = 0; i < iterations; i++) { mTestUtils.writeOutput("connectInput iteration " + (i + 1) + " of " + iterations); - mTestUtils.connectProfile(mAdapter, device, BluetoothProfile.INPUT_DEVICE, + mTestUtils.connectProfile(mAdapter, device, BluetoothProfile.HID_HOST, String.format("connectInput(device=%s)", device)); - mTestUtils.disconnectProfile(mAdapter, device, BluetoothProfile.INPUT_DEVICE, + mTestUtils.disconnectProfile(mAdapter, device, BluetoothProfile.HID_HOST, String.format("disconnectInput(device=%s)", device)); } diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java index ee159788ad21..ada03666b7ba 100644 --- a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java +++ b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java @@ -227,8 +227,8 @@ public class BluetoothTestUtils extends Assert { case BluetoothProfile.HEADSET: mConnectionAction = BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED; break; - case BluetoothProfile.INPUT_DEVICE: - mConnectionAction = BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED; + case BluetoothProfile.HID_HOST: + mConnectionAction = BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED; break; case BluetoothProfile.PAN: mConnectionAction = BluetoothPan.ACTION_CONNECTION_STATE_CHANGED; @@ -322,8 +322,8 @@ public class BluetoothTestUtils extends Assert { case BluetoothProfile.HEADSET: mHeadset = (BluetoothHeadset) proxy; break; - case BluetoothProfile.INPUT_DEVICE: - mInput = (BluetoothInputDevice) proxy; + case BluetoothProfile.HID_HOST: + mInput = (BluetoothHidHost) proxy; break; case BluetoothProfile.PAN: mPan = (BluetoothPan) proxy; @@ -342,7 +342,7 @@ public class BluetoothTestUtils extends Assert { case BluetoothProfile.HEADSET: mHeadset = null; break; - case BluetoothProfile.INPUT_DEVICE: + case BluetoothProfile.HID_HOST: mInput = null; break; case BluetoothProfile.PAN: @@ -362,7 +362,7 @@ public class BluetoothTestUtils extends Assert { private Context mContext; private BluetoothA2dp mA2dp = null; private BluetoothHeadset mHeadset = null; - private BluetoothInputDevice mInput = null; + private BluetoothHidHost mInput = null; private BluetoothPan mPan = null; /** @@ -894,7 +894,7 @@ public class BluetoothTestUtils extends Assert { * @param adapter The BT adapter. * @param device The remote device. * @param profile The profile to connect. One of {@link BluetoothProfile#A2DP}, - * {@link BluetoothProfile#HEADSET}, or {@link BluetoothProfile#INPUT_DEVICE}. + * {@link BluetoothProfile#HEADSET}, or {@link BluetoothProfile#HID_HOST}. * @param methodName The method name to printed in the logs. If null, will be * "connectProfile(profile=<profile>, device=<device>)" */ @@ -935,8 +935,8 @@ public class BluetoothTestUtils extends Assert { assertTrue(((BluetoothA2dp)proxy).connect(device)); } else if (profile == BluetoothProfile.HEADSET) { assertTrue(((BluetoothHeadset)proxy).connect(device)); - } else if (profile == BluetoothProfile.INPUT_DEVICE) { - assertTrue(((BluetoothInputDevice)proxy).connect(device)); + } else if (profile == BluetoothProfile.HID_HOST) { + assertTrue(((BluetoothHidHost)proxy).connect(device)); } break; default: @@ -975,7 +975,7 @@ public class BluetoothTestUtils extends Assert { * @param adapter The BT adapter. * @param device The remote device. * @param profile The profile to disconnect. One of {@link BluetoothProfile#A2DP}, - * {@link BluetoothProfile#HEADSET}, or {@link BluetoothProfile#INPUT_DEVICE}. + * {@link BluetoothProfile#HEADSET}, or {@link BluetoothProfile#HID_HOST}. * @param methodName The method name to printed in the logs. If null, will be * "connectProfile(profile=<profile>, device=<device>)" */ @@ -1010,8 +1010,8 @@ public class BluetoothTestUtils extends Assert { assertTrue(((BluetoothA2dp)proxy).disconnect(device)); } else if (profile == BluetoothProfile.HEADSET) { assertTrue(((BluetoothHeadset)proxy).disconnect(device)); - } else if (profile == BluetoothProfile.INPUT_DEVICE) { - assertTrue(((BluetoothInputDevice)proxy).disconnect(device)); + } else if (profile == BluetoothProfile.HID_HOST) { + assertTrue(((BluetoothHidHost)proxy).disconnect(device)); } break; case BluetoothProfile.STATE_DISCONNECTED: @@ -1237,7 +1237,7 @@ public class BluetoothTestUtils extends Assert { long s = System.currentTimeMillis(); while (System.currentTimeMillis() - s < CONNECT_DISCONNECT_PROFILE_TIMEOUT) { state = mPan.getConnectionState(device); - if (state == BluetoothInputDevice.STATE_DISCONNECTED + if (state == BluetoothHidHost.STATE_DISCONNECTED && (receiver.getFiredFlags() & mask) == mask) { long finish = receiver.getCompletedTime(); if (start != -1 && finish != -1) { @@ -1255,7 +1255,7 @@ public class BluetoothTestUtils extends Assert { int firedFlags = receiver.getFiredFlags(); removeReceiver(receiver); fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%s)", - methodName, state, BluetoothInputDevice.STATE_DISCONNECTED, firedFlags, mask)); + methodName, state, BluetoothHidHost.STATE_DISCONNECTED, firedFlags, mask)); } /** @@ -1404,7 +1404,7 @@ public class BluetoothTestUtils extends Assert { String[] actions = { BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED, BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED, - BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED}; + BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED}; ConnectProfileReceiver receiver = new ConnectProfileReceiver(device, profile, expectedFlags); addReceiver(receiver, actions); @@ -1443,7 +1443,7 @@ public class BluetoothTestUtils extends Assert { return mHeadset; } break; - case BluetoothProfile.INPUT_DEVICE: + case BluetoothProfile.HID_HOST: if (mInput != null) { return mInput; } @@ -1469,7 +1469,7 @@ public class BluetoothTestUtils extends Assert { sleep(POLL_TIME); } return mHeadset; - case BluetoothProfile.INPUT_DEVICE: + case BluetoothProfile.HID_HOST: while (mInput == null && System.currentTimeMillis() - s < CONNECT_PROXY_TIMEOUT) { sleep(POLL_TIME); } diff --git a/core/tests/coretests/Android.mk b/core/tests/coretests/Android.mk index 15eab1f72aab..f2eb872893b4 100644 --- a/core/tests/coretests/Android.mk +++ b/core/tests/coretests/Android.mk @@ -19,7 +19,12 @@ LOCAL_SRC_FILES := \ $(call all-java-files-under, src) \ $(call all-Iaidl-files-under, src) \ $(call all-java-files-under, DisabledTestApp/src) \ - $(call all-java-files-under, EnabledTestApp/src) + $(call all-java-files-under, EnabledTestApp/src) \ + $(call all-java-files-under, BinderProxyCountingTestApp/src) \ + $(call all-java-files-under, BinderProxyCountingTestService/src) \ + $(call all-Iaidl-files-under, aidl) + +LOCAL_AIDL_INCLUDES := $(LOCAL_PATH)/aidl LOCAL_DX_FLAGS := --core-library LOCAL_JACK_FLAGS := --multi-dex native diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml index 9c0543b18f8b..51bfc209bf55 100644 --- a/core/tests/coretests/AndroidManifest.xml +++ b/core/tests/coretests/AndroidManifest.xml @@ -86,6 +86,7 @@ <uses-permission android:name="android.permission.BROADCAST_STICKY" /> <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" /> + <uses-permission android:name="android.permission.KILL_UID" /> <!-- location test permissions --> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> diff --git a/core/tests/coretests/BinderProxyCountingTestApp/Android.mk b/core/tests/coretests/BinderProxyCountingTestApp/Android.mk new file mode 100644 index 000000000000..e31d50f15d5f --- /dev/null +++ b/core/tests/coretests/BinderProxyCountingTestApp/Android.mk @@ -0,0 +1,27 @@ +# Copyright (C) 2017 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. + +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := tests + +LOCAL_STATIC_JAVA_LIBRARIES := coretests-aidl +LOCAL_SRC_FILES := $(call all-subdir-java-files) + +LOCAL_PACKAGE_NAME := BinderProxyCountingTestApp +LOCAL_CERTIFICATE := platform + +include $(BUILD_PACKAGE) + diff --git a/core/tests/coretests/BinderProxyCountingTestApp/AndroidManifest.xml b/core/tests/coretests/BinderProxyCountingTestApp/AndroidManifest.xml new file mode 100644 index 000000000000..a971730f389d --- /dev/null +++ b/core/tests/coretests/BinderProxyCountingTestApp/AndroidManifest.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2017 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. +--> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.frameworks.coretests.binderproxycountingtestapp"> + + <application> + <service android:name=".BpcTestAppCmdService" + android:exported="true"/> + </application> +</manifest> diff --git a/core/tests/coretests/BinderProxyCountingTestApp/src/com/android/frameworks/coretests/binderproxycountingtestapp/BpcTestAppCmdService.java b/core/tests/coretests/BinderProxyCountingTestApp/src/com/android/frameworks/coretests/binderproxycountingtestapp/BpcTestAppCmdService.java new file mode 100644 index 000000000000..5aae1203e559 --- /dev/null +++ b/core/tests/coretests/BinderProxyCountingTestApp/src/com/android/frameworks/coretests/binderproxycountingtestapp/BpcTestAppCmdService.java @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2017 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.frameworks.coretests.binderproxycountingtestapp; + +import android.app.Service; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.ServiceConnection; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; + +import com.android.frameworks.coretests.aidl.IBinderProxyCountingService; +import com.android.frameworks.coretests.aidl.IBpcTestAppCmdService; +import com.android.frameworks.coretests.aidl.ITestRemoteCallback; + +import java.util.ArrayList; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +public class BpcTestAppCmdService extends Service { + private static final String TAG = BpcTestAppCmdService.class.getSimpleName(); + + private static final String TEST_SERVICE_PKG = + "com.android.frameworks.coretests.binderproxycountingtestservice"; + private static final String TEST_SERVICE_CLASS = + TEST_SERVICE_PKG + ".BinderProxyCountingService"; + private static final int BIND_SERVICE_TIMEOUT_SEC = 5; + + private static ServiceConnection mServiceConnection; + private static IBinderProxyCountingService mBpcService; + + private IBpcTestAppCmdService.Stub mBinder = new IBpcTestAppCmdService.Stub() { + + private ArrayList<BroadcastReceiver> mBrList = new ArrayList(); + private ArrayList<ITestRemoteCallback> mTrcList = new ArrayList(); + + @Override + public void createSystemBinders(int count) { + int i = 0; + while (i++ < count) { + BroadcastReceiver br = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + + } + }; + IntentFilter filt = new IntentFilter(Intent.ACTION_POWER_DISCONNECTED); + synchronized (mBrList) { + mBrList.add(br); + } + registerReceiver(br, filt); + } + } + + @Override + public void releaseSystemBinders(int count) { + int i = 0; + while (i++ < count) { + BroadcastReceiver br; + synchronized (mBrList) { + br = mBrList.remove(0); + } + unregisterReceiver(br); + } + } + + @Override + public void createTestBinders(int count) { + int i = 0; + while (i++ < count) { + ITestRemoteCallback cb = new ITestRemoteCallback.Stub() {}; + synchronized (mTrcList) { + mTrcList.add(cb); + } + try { + mBpcService.registerCallback(cb); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException caught! " + e); + } + } + } + + @Override + public void releaseTestBinders(int count) { + int i = 0; + while (i++ < count) { + + ITestRemoteCallback cb; + synchronized (mTrcList) { + cb = mTrcList.remove(0); + } + try { + mBpcService.unregisterCallback(cb); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException caught! " + e); + } + } + } + + @Override + public void releaseAllBinders() { + synchronized (mBrList) { + while (mBrList.size() > 0) { + unregisterReceiver(mBrList.remove(0)); + } + } + synchronized (mTrcList) { + while (mTrcList.size() > 0) { + try { + mBpcService.unregisterCallback(mTrcList.remove(0)); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException caught! " + e); + } + } + } + } + + @Override + public String bindToTestService() { + try { + final CountDownLatch bindLatch = new CountDownLatch(1); + mServiceConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + Log.i(TAG, "Service connected"); + mBpcService = IBinderProxyCountingService.Stub.asInterface(service); + bindLatch.countDown(); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + Log.i(TAG, "Service disconnected"); + } + }; + final Intent intent = new Intent() + .setComponent(new ComponentName(TEST_SERVICE_PKG, TEST_SERVICE_CLASS)); + bindService(intent, mServiceConnection, + Context.BIND_AUTO_CREATE + | Context.BIND_ALLOW_OOM_MANAGEMENT + | Context.BIND_NOT_FOREGROUND); + if (!bindLatch.await(BIND_SERVICE_TIMEOUT_SEC, TimeUnit.SECONDS)) { + throw new RuntimeException("Failed to bind to " + TEST_SERVICE_CLASS); + } + } catch (Exception e) { + unbindFromTestService(); + Log.e(TAG, e.toString()); + return e.toString(); + } + return null; + } + + @Override + public void unbindFromTestService() { + if (mBpcService != null) { + unbindService(mServiceConnection); + } + } + }; + + @Override + public IBinder onBind(Intent intent) { + return mBinder; + } +}
\ No newline at end of file diff --git a/core/tests/coretests/BinderProxyCountingTestService/Android.mk b/core/tests/coretests/BinderProxyCountingTestService/Android.mk new file mode 100644 index 000000000000..a63cf0e8e8b1 --- /dev/null +++ b/core/tests/coretests/BinderProxyCountingTestService/Android.mk @@ -0,0 +1,27 @@ +# Copyright (C) 2017 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. + +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := tests + +LOCAL_STATIC_JAVA_LIBRARIES := coretests-aidl +LOCAL_SRC_FILES := $(call all-subdir-java-files) + +LOCAL_PACKAGE_NAME := BinderProxyCountingTestService +LOCAL_CERTIFICATE := platform + +include $(BUILD_PACKAGE) + diff --git a/core/tests/coretests/BinderProxyCountingTestService/AndroidManifest.xml b/core/tests/coretests/BinderProxyCountingTestService/AndroidManifest.xml new file mode 100644 index 000000000000..777bd20db212 --- /dev/null +++ b/core/tests/coretests/BinderProxyCountingTestService/AndroidManifest.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2017 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. +--> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.frameworks.coretests.binderproxycountingtestservice"> + + <application> + <service android:name=".BpcTestServiceCmdService" + android:exported="true" /> + <service android:name=".BinderProxyCountingService" + android:exported="true" /> + </application> +</manifest> diff --git a/core/tests/coretests/BinderProxyCountingTestService/src/com/android/frameworks/coretests/binderproxycountingtestservice/BinderProxyCountingService.java b/core/tests/coretests/BinderProxyCountingTestService/src/com/android/frameworks/coretests/binderproxycountingtestservice/BinderProxyCountingService.java new file mode 100644 index 000000000000..41b4c69232f4 --- /dev/null +++ b/core/tests/coretests/BinderProxyCountingTestService/src/com/android/frameworks/coretests/binderproxycountingtestservice/BinderProxyCountingService.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2017 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.frameworks.coretests.binderproxycountingtestservice; + +import android.app.Service; +import android.content.Intent; +import android.os.IBinder; +import android.os.RemoteCallbackList; + +import com.android.frameworks.coretests.aidl.IBinderProxyCountingService; +import com.android.frameworks.coretests.aidl.ITestRemoteCallback; + +public class BinderProxyCountingService extends Service { + private static final String TAG = BinderProxyCountingService.class.getSimpleName(); + + private IBinderProxyCountingService.Stub mBinder = new IBinderProxyCountingService.Stub() { + + final RemoteCallbackList<ITestRemoteCallback> mTestCallbacks = new RemoteCallbackList<>(); + + @Override + public void registerCallback(ITestRemoteCallback callback) { + synchronized (this) { + mTestCallbacks.register(callback); + } + } + + @Override + public void unregisterCallback(ITestRemoteCallback callback) { + synchronized (this) { + mTestCallbacks.unregister(callback); + } + } + }; + + @Override + public IBinder onBind(Intent intent) { + return mBinder; + } +}
\ No newline at end of file diff --git a/core/tests/coretests/BinderProxyCountingTestService/src/com/android/frameworks/coretests/binderproxycountingtestservice/BpcTestServiceCmdService.java b/core/tests/coretests/BinderProxyCountingTestService/src/com/android/frameworks/coretests/binderproxycountingtestservice/BpcTestServiceCmdService.java new file mode 100644 index 000000000000..6bed2a2ec53f --- /dev/null +++ b/core/tests/coretests/BinderProxyCountingTestService/src/com/android/frameworks/coretests/binderproxycountingtestservice/BpcTestServiceCmdService.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2017 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.frameworks.coretests.binderproxycountingtestservice; + +import android.app.Service; +import android.content.Intent; +import android.os.Debug; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.IBinder; +import android.util.Log; + +import com.android.frameworks.coretests.aidl.IBpcCallbackObserver; +import com.android.frameworks.coretests.aidl.IBpcTestServiceCmdService; +import com.android.internal.os.BinderInternal; + +public class BpcTestServiceCmdService extends Service { + private static final String TAG = BpcTestServiceCmdService.class.getSimpleName(); + + //ServiceThread mHandlerThread; + Handler mHandler; + HandlerThread mHandlerThread; + + private IBpcTestServiceCmdService.Stub mBinder = new IBpcTestServiceCmdService.Stub() { + IBpcCallbackObserver mCallbackObserver; + + @Override + public void forceGc() { + int gcCount = Integer.parseInt(Debug.getRuntimeStat("art.gc.gc-count")); + int i = 20; + while (gcCount == Integer.parseInt(Debug.getRuntimeStat("art.gc.gc-count")) && i > 0) { + System.gc(); + System.runFinalization(); + i--; + } + } + + @Override + public int getBinderProxyCount(int uid) { + return BinderInternal.nGetBinderProxyCount(uid); + } + + @Override + public void setBinderProxyWatermarks(int high, int low) { + BinderInternal.nSetBinderProxyCountWatermarks(high, low); + } + + @Override + public void enableBinderProxyLimit(boolean enable) { + BinderInternal.nSetBinderProxyCountEnabled(enable); + } + + @Override + public void setBinderProxyCountCallback(IBpcCallbackObserver observer) { + if (observer != null) { + BinderInternal.setBinderProxyCountCallback( + new BinderInternal.BinderProxyLimitListener() { + @Override + public void onLimitReached(int uid) { + try { + synchronized (observer) { + observer.onCallback(uid); + } + } catch (Exception e) { + Log.e(TAG, e.toString()); + } + } + }, mHandler); + } else { + BinderInternal.clearBinderProxyCountCallback(); + } + } + }; + + @Override + public IBinder onBind(Intent intent) { + return mBinder; + } + + @Override + public void onCreate() + { + mHandlerThread = new HandlerThread("BinderProxyCountingServiceThread"); + mHandlerThread.start(); + mHandler = new Handler(mHandlerThread.getLooper()); + } +}
\ No newline at end of file diff --git a/core/tests/coretests/aidl/Android.mk b/core/tests/coretests/aidl/Android.mk new file mode 100644 index 000000000000..86e36b61a5ae --- /dev/null +++ b/core/tests/coretests/aidl/Android.mk @@ -0,0 +1,22 @@ +# Copyright (C) 2017 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. + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_MODULE_TAGS := tests +LOCAL_SDK_VERSION := current +LOCAL_SRC_FILES := $(call all-subdir-Iaidl-files) +LOCAL_MODULE := coretests-aidl +include $(BUILD_STATIC_JAVA_LIBRARY)
\ No newline at end of file diff --git a/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBinderProxyCountingService.aidl b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBinderProxyCountingService.aidl new file mode 100644 index 000000000000..a69b0c58233a --- /dev/null +++ b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBinderProxyCountingService.aidl @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2017 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.frameworks.coretests.aidl; +import com.android.frameworks.coretests.aidl.ITestRemoteCallback; + +interface IBinderProxyCountingService { + void registerCallback(in ITestRemoteCallback callback); + void unregisterCallback(in ITestRemoteCallback callback); +}
\ No newline at end of file diff --git a/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBpcCallbackObserver.aidl b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBpcCallbackObserver.aidl new file mode 100644 index 000000000000..c4ebd56bed6c --- /dev/null +++ b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBpcCallbackObserver.aidl @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2017 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.frameworks.coretests.aidl; + +interface IBpcCallbackObserver { + void onCallback(int uid); +}
\ No newline at end of file diff --git a/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBpcTestAppCmdService.aidl b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBpcTestAppCmdService.aidl new file mode 100644 index 000000000000..86a0aa0ffc12 --- /dev/null +++ b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBpcTestAppCmdService.aidl @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2017 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.frameworks.coretests.aidl; + +interface IBpcTestAppCmdService { + void createSystemBinders(int count); + void releaseSystemBinders(int count); + + void createTestBinders(int count); + void releaseTestBinders(int count); + + void releaseAllBinders(); + + String bindToTestService(); + void unbindFromTestService(); +}
\ No newline at end of file diff --git a/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBpcTestServiceCmdService.aidl b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBpcTestServiceCmdService.aidl new file mode 100644 index 000000000000..abdab41ce537 --- /dev/null +++ b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBpcTestServiceCmdService.aidl @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2017 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.frameworks.coretests.aidl; +import com.android.frameworks.coretests.aidl.IBpcCallbackObserver; + +interface IBpcTestServiceCmdService { + void forceGc(); + int getBinderProxyCount(int uid); + void setBinderProxyWatermarks(int high, int low); + void enableBinderProxyLimit(boolean enable); + void setBinderProxyCountCallback(IBpcCallbackObserver observer); +}
\ No newline at end of file diff --git a/core/java/android/service/autofill/Dataset.aidl b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ITestRemoteCallback.aidl index 2342c5f5a45a..36bdb6cc9c41 100644 --- a/core/java/android/service/autofill/Dataset.aidl +++ b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ITestRemoteCallback.aidl @@ -1,11 +1,11 @@ -/** - * Copyright (c) 2016, The Android Open Source Project +/* + * Copyright (C) 2017 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 + * 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, @@ -14,6 +14,7 @@ * limitations under the License. */ -package android.service.autofill; +package com.android.frameworks.coretests.aidl; -parcelable Dataset; +interface ITestRemoteCallback { +}
\ No newline at end of file diff --git a/core/tests/coretests/src/android/os/BinderProxyCountingTest.java b/core/tests/coretests/src/android/os/BinderProxyCountingTest.java new file mode 100644 index 000000000000..6cdb35abce5a --- /dev/null +++ b/core/tests/coretests/src/android/os/BinderProxyCountingTest.java @@ -0,0 +1,378 @@ +/* + * Copyright (C) 2017 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.os; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import android.app.ActivityManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.LargeTest; +import android.support.test.runner.AndroidJUnit4; +import android.support.test.uiautomator.UiDevice; +import android.util.Log; + +import com.android.frameworks.coretests.aidl.IBpcCallbackObserver; +import com.android.frameworks.coretests.aidl.IBpcTestAppCmdService; +import com.android.frameworks.coretests.aidl.IBpcTestServiceCmdService; + +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +/** + * Tests for verifying the Binder Proxy Counting and Limiting. + * + * To manually build and install relevant test apps + * + * Build: + * mmma frameworks/base/core/tests/coretests/BinderProxyCountingTestApp + * mmma frameworks/base/core/tests/coretests/BinderProxyCountingTestService + * Install: + * adb install -r \ + * ${ANDROID_PRODUCT_OUT}/data/app/BinderProxyCountingTestApp/BinderProxyCountingTestApp.apk + * adb install -r \ + * ${ANDROID_PRODUCT_OUT}/data/app/BinderProxyCountingTestService/BinderProxyCountingTestService.apk + * + * To run the tests, use + * + * Build: m FrameworksCoreTests + * Install: adb install -r \ + * ${ANDROID_PRODUCT_OUT}/data/app/FrameworksCoreTests/FrameworksCoreTests.apk + * Run: adb shell am instrument -e class android.os.BinderProxyCountingTest -w \ + * com.android.frameworks.coretests/android.support.test.runner.AndroidJUnitRunner + * + * or + * + * bit FrameworksCoreTests:android.os.BinderProxyCountingTest + */ +@LargeTest +@RunWith(AndroidJUnit4.class) +public class BinderProxyCountingTest { + private static final String TAG = BinderProxyCountingTest.class.getSimpleName(); + + private static final String TEST_APP_PKG = + "com.android.frameworks.coretests.binderproxycountingtestapp"; + private static final String TEST_APP_CMD_SERVICE = TEST_APP_PKG + ".BpcTestAppCmdService"; + private static final String TEST_SERVICE_PKG = + "com.android.frameworks.coretests.binderproxycountingtestservice"; + private static final String TEST_SERVICE_CMD_SERVICE = + TEST_SERVICE_PKG + ".BpcTestServiceCmdService"; + + private static final int BIND_SERVICE_TIMEOUT_SEC = 5; + private static final int TOO_MANY_BINDERS_TIMEOUT_SEC = 2; + + // Keep in sync with sBinderProxyCountLimit in BpBinder.cpp + private static final int BINDER_PROXY_LIMIT = 2500; + + private static Context sContext; + private static UiDevice sUiDevice; + + private static ServiceConnection sTestAppConnection; + private static ServiceConnection sTestServiceConnection; + private static IBpcTestAppCmdService sBpcTestAppCmdService; + private static IBpcTestServiceCmdService sBpcTestServiceCmdService; + private static final Intent sTestAppIntent = new Intent() + .setComponent(new ComponentName(TEST_APP_PKG, TEST_APP_CMD_SERVICE)); + private static final Intent sTestServiceIntent = new Intent() + .setComponent(new ComponentName(TEST_SERVICE_PKG, TEST_SERVICE_CMD_SERVICE)); + private static final Consumer<IBinder> sTestAppConsumer = (service) -> { + sBpcTestAppCmdService = IBpcTestAppCmdService.Stub.asInterface(service); + }; + private static final Consumer<IBinder> sTestServiceConsumer = (service) -> { + sBpcTestServiceCmdService = IBpcTestServiceCmdService.Stub.asInterface(service); + }; + private static int sTestPkgUid; + + /** + * Setup any common data for the upcoming tests. + */ + @BeforeClass + public static void setUpOnce() throws Exception { + sContext = InstrumentationRegistry.getContext(); + sTestPkgUid = sContext.getPackageManager().getPackageUid(TEST_APP_PKG, 0); + ((ActivityManager) sContext.getSystemService(Context.ACTIVITY_SERVICE)).killUid(sTestPkgUid, + "Wiping Test Package"); + + sUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); + } + + private ServiceConnection bindService(final Consumer<IBinder> consumer, Intent intent) + throws Exception { + final CountDownLatch bindLatch = new CountDownLatch(1); + ServiceConnection connection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + Log.i(TAG, "Service connected"); + consumer.accept(service); + bindLatch.countDown(); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + Log.i(TAG, "Service disconnected"); + } + }; + sContext.bindService(intent, connection, + Context.BIND_AUTO_CREATE + | Context.BIND_ALLOW_OOM_MANAGEMENT + | Context.BIND_NOT_FOREGROUND); + if (!bindLatch.await(BIND_SERVICE_TIMEOUT_SEC, TimeUnit.SECONDS)) { + fail("Timed out waiting for the service to bind in " + sTestPkgUid); + } + return connection; + } + + + private void unbindService(ServiceConnection service) { + if (service != null) { + sContext.unbindService(service); + } + } + + private void bindTestAppToTestService() throws Exception { + if (sBpcTestAppCmdService != null) { + String errorMessage = sBpcTestAppCmdService.bindToTestService(); + if (errorMessage != null) { + fail(errorMessage); + } + } + } + + private void unbindTestAppFromTestService() throws Exception { + if (sBpcTestAppCmdService != null) { + sBpcTestAppCmdService.unbindFromTestService(); + } + } + + private CountDownLatch createBinderLimitLatch() throws RemoteException { + final CountDownLatch latch = new CountDownLatch(1); + sBpcTestServiceCmdService.setBinderProxyCountCallback( + new IBpcCallbackObserver.Stub() { + @Override + public void onCallback(int uid) { + if (uid == sTestPkgUid) { + latch.countDown(); + } + } + }); + return latch; + } + + /** + * Get the Binder Proxy count held by SYSTEM for a given uid + */ + private int getSystemBinderCount(int uid) throws Exception { + return Integer.parseInt(sUiDevice.executeShellCommand( + "dumpsys activity binder-proxies " + uid).trim()); + } + + @Test + public void testBinderProxyCount() throws Exception { + // Arbitrary list of Binder create and release + // Should cumulatively equal 0 and must never add up past the binder limit at any point + int[] testValues = {223, -103, -13, 25, 90, -222}; + try { + sTestAppConnection = bindService(sTestAppConsumer, sTestAppIntent); + // Get the baseline of binders naturally held by the test Package + int expectedBinderCount = getSystemBinderCount(sTestPkgUid); + + for (int testValue : testValues) { + if (testValue > 0) { + sBpcTestAppCmdService.createSystemBinders(testValue); + } else { + sBpcTestAppCmdService.releaseSystemBinders(-testValue); + } + expectedBinderCount += testValue; + int currentBinderCount = getSystemBinderCount(sTestPkgUid); + assertEquals("Current Binder Count (" + currentBinderCount + + ") does not equal expected Binder Count (" + expectedBinderCount + + ")", expectedBinderCount, currentBinderCount); + } + } finally { + unbindService(sTestAppConnection); + } + } + + @Test + public void testBinderProxyLimitBoundary() throws Exception { + final int binderProxyLimit = 2000; + final int rearmThreshold = 1800; + try { + sTestAppConnection = bindService(sTestAppConsumer, sTestAppIntent); + sTestServiceConnection = bindService(sTestServiceConsumer, sTestServiceIntent); + bindTestAppToTestService(); + sBpcTestServiceCmdService.enableBinderProxyLimit(true); + + sBpcTestServiceCmdService.forceGc(); + // Get the baseline of binders naturally held by the test Package + int baseBinderCount = sBpcTestServiceCmdService.getBinderProxyCount(sTestPkgUid); + + final CountDownLatch binderLimitLatch = createBinderLimitLatch(); + sBpcTestServiceCmdService.setBinderProxyWatermarks(binderProxyLimit, rearmThreshold); + + // Create Binder Proxies up to the limit + sBpcTestAppCmdService.createTestBinders(binderProxyLimit - baseBinderCount); + if (binderLimitLatch.await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) { + fail("Received BinderProxyLimitCallback for uid " + sTestPkgUid + + " when proxy limit should not have been reached"); + } + + // Create one more Binder to cross the limit + sBpcTestAppCmdService.createTestBinders(1); + if (!binderLimitLatch.await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) { + fail("Timed out waiting for uid " + sTestPkgUid + " to hit limit"); + } + + sBpcTestAppCmdService.releaseAllBinders(); + } finally { + unbindTestAppFromTestService(); + unbindService(sTestAppConnection); + unbindService(sTestServiceConnection); + } + } + + @Test + public void testSetBinderProxyLimit() throws Exception { + int[] testLimits = {1000, 222, 800}; + try { + sTestAppConnection = bindService(sTestAppConsumer, sTestAppIntent); + sTestServiceConnection = bindService(sTestServiceConsumer, sTestServiceIntent); + bindTestAppToTestService(); + sBpcTestServiceCmdService.enableBinderProxyLimit(true); + + sBpcTestServiceCmdService.forceGc(); + int baseBinderCount = sBpcTestServiceCmdService.getBinderProxyCount(sTestPkgUid); + for (int testLimit : testLimits) { + final CountDownLatch binderLimitLatch = createBinderLimitLatch(); + // Change the BinderProxyLimit + sBpcTestServiceCmdService.setBinderProxyWatermarks(testLimit, baseBinderCount + 10); + // Exceed the new Binder Proxy Limit + sBpcTestAppCmdService.createTestBinders(testLimit + 1); + if (!binderLimitLatch.await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) { + fail("Timed out waiting for uid " + sTestPkgUid + " to hit limit"); + } + + sBpcTestAppCmdService.releaseTestBinders(testLimit + 1); + sBpcTestServiceCmdService.forceGc(); + } + } finally { + unbindTestAppFromTestService(); + unbindService(sTestAppConnection); + unbindService(sTestServiceConnection); + } + } + + @Test + public void testRearmCallbackThreshold() throws Exception { + final int binderProxyLimit = 2000; + final int exceedBinderProxyLimit = binderProxyLimit + 10; + final int rearmThreshold = 1800; + try { + sTestAppConnection = bindService(sTestAppConsumer, sTestAppIntent); + sTestServiceConnection = bindService(sTestServiceConsumer, sTestServiceIntent); + bindTestAppToTestService(); + sBpcTestServiceCmdService.enableBinderProxyLimit(true); + + sBpcTestServiceCmdService.forceGc(); + final CountDownLatch firstBinderLimitLatch = createBinderLimitLatch(); + sBpcTestServiceCmdService.setBinderProxyWatermarks(binderProxyLimit, rearmThreshold); + // Exceed the Binder Proxy Limit + sBpcTestAppCmdService.createTestBinders(exceedBinderProxyLimit); + if (!firstBinderLimitLatch.await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) { + fail("Timed out waiting for uid " + sTestPkgUid + " to hit limit"); + } + + sBpcTestServiceCmdService.forceGc(); + int currentBinderCount = sBpcTestServiceCmdService.getBinderProxyCount(sTestPkgUid); + // Drop to the threshold, this should not rearm the callback + sBpcTestAppCmdService.releaseTestBinders(currentBinderCount - rearmThreshold); + + sBpcTestServiceCmdService.forceGc(); + currentBinderCount = sBpcTestServiceCmdService.getBinderProxyCount(sTestPkgUid); + + final CountDownLatch secondBinderLimitLatch = createBinderLimitLatch(); + // Exceed the Binder Proxy limit which should not cause a callback since there has + // been no rearm + sBpcTestAppCmdService.createTestBinders(exceedBinderProxyLimit - currentBinderCount); + if (secondBinderLimitLatch.await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) { + fail("Received BinderProxyLimitCallback for uid " + sTestPkgUid + + " when the callback has not been rearmed yet"); + } + + sBpcTestServiceCmdService.forceGc(); + currentBinderCount = sBpcTestServiceCmdService.getBinderProxyCount(sTestPkgUid); + // Drop below the rearmThreshold to rearm the BinderProxyLimitCallback + sBpcTestAppCmdService.releaseTestBinders(currentBinderCount - rearmThreshold + 1); + + sBpcTestServiceCmdService.forceGc(); + currentBinderCount = sBpcTestServiceCmdService.getBinderProxyCount(sTestPkgUid); + // Exceed the Binder Proxy limit for the last time + sBpcTestAppCmdService.createTestBinders(exceedBinderProxyLimit - currentBinderCount); + + if (!secondBinderLimitLatch.await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) { + fail("Timed out waiting for uid " + sTestPkgUid + " to hit limit"); + } + sBpcTestAppCmdService.releaseTestBinders(currentBinderCount); + } finally { + unbindTestAppFromTestService(); + unbindService(sTestAppConnection); + unbindService(sTestServiceConnection); + } + } + + @Test + public void testKillBadBehavingApp() throws Exception { + final CountDownLatch binderDeathLatch = new CountDownLatch(1); + final int exceedBinderProxyLimit = BINDER_PROXY_LIMIT + 1; + + try { + sTestAppConnection = bindService(sTestAppConsumer, sTestAppIntent); + sBpcTestAppCmdService.asBinder().linkToDeath(new IBinder.DeathRecipient() { + @Override + public void binderDied() { + Log.v(TAG, "BpcTestAppCmdService died!"); + binderDeathLatch.countDown(); + } + }, 0); + try { + // Exceed the Binder Proxy Limit emulating a bad behaving app + sBpcTestAppCmdService.createSystemBinders(exceedBinderProxyLimit); + } catch (DeadObjectException doe) { + // We are expecting the service to get killed mid call, so a DeadObjectException + // is not unexpected + } + + if (!binderDeathLatch.await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) { + sBpcTestAppCmdService.releaseSystemBinders(exceedBinderProxyLimit); + fail("Timed out waiting for uid " + sTestPkgUid + " to die."); + } + + } finally { + unbindService(sTestAppConnection); + } + } +} diff --git a/core/tests/coretests/src/android/text/TextLineTest.java b/core/tests/coretests/src/android/text/TextLineTest.java new file mode 100644 index 000000000000..f8d688498b5f --- /dev/null +++ b/core/tests/coretests/src/android/text/TextLineTest.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2017 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.text; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.support.test.filters.SmallTest; +import android.support.test.filters.Suppress; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class TextLineTest { + private boolean stretchesToFullWidth(CharSequence line) { + final TextPaint paint = new TextPaint(); + final TextLine tl = TextLine.obtain(); + tl.set(paint, line, 0, line.length(), Layout.DIR_LEFT_TO_RIGHT, + Layout.DIRS_ALL_LEFT_TO_RIGHT, false /* hasTabs */, null /* tabStops */); + final float originalWidth = tl.metrics(null); + final float expandedWidth = 2 * originalWidth; + + tl.justify(expandedWidth); + final float newWidth = tl.metrics(null); + TextLine.recycle(tl); + return Math.abs(newWidth - expandedWidth) < 0.5; + } + + @Test + public void testJustify_spaces() { + // There are no spaces to stretch. + assertFalse(stretchesToFullWidth("text")); + + assertTrue(stretchesToFullWidth("one space")); + assertTrue(stretchesToFullWidth("exactly two spaces")); + assertTrue(stretchesToFullWidth("up to three spaces")); + } + + // NBSP should also stretch when it's not used as a base for a combining mark. This doesn't work + // yet (b/68204709). + @Suppress + public void disabledTestJustify_NBSP() { + final char nbsp = '\u00A0'; + assertTrue(stretchesToFullWidth("non-breaking" + nbsp + "space")); + assertTrue(stretchesToFullWidth("mix" + nbsp + "and match")); + + final char combining_acute = '\u0301'; + assertFalse(stretchesToFullWidth("combining" + nbsp + combining_acute + "acute")); + } +} diff --git a/core/tests/webkit/Android.mk b/core/tests/webkit/Android.mk new file mode 100644 index 000000000000..e6120ec497e7 --- /dev/null +++ b/core/tests/webkit/Android.mk @@ -0,0 +1,44 @@ +# Copyright (C) 2017 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. + +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +# We only want this apk build for tests. +LOCAL_MODULE_TAGS := tests + +LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS) + + +# Include all test java files. +LOCAL_SRC_FILES := \ + $(call all-java-files-under, unit_tests_src) + +LOCAL_JAVA_LIBRARIES := android.test.runner + +LOCAL_STATIC_JAVA_LIBRARIES := \ + android-support-test + +LOCAL_PACKAGE_NAME := WebViewLoadingTests +LOCAL_CERTIFICATE := platform + +LOCAL_COMPATIBILITY_SUITE := device-tests + +LOCAL_ADDITIONAL_DEPENDENCIES := \ + WebViewLoadingOnDiskTestApk \ + WebViewLoadingFromApkTestApk + +include $(BUILD_PACKAGE) + +include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/core/tests/webkit/AndroidManifest.xml b/core/tests/webkit/AndroidManifest.xml new file mode 100644 index 000000000000..42accdf66891 --- /dev/null +++ b/core/tests/webkit/AndroidManifest.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2017 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.webkit.tests" + android:sharedUserId="android.uid.system"> + + <application> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation + android:name="android.support.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.webkit.tests" + android:label="Frameworks WebView Loader Tests" /> + +</manifest> diff --git a/core/tests/webkit/AndroidTest.xml b/core/tests/webkit/AndroidTest.xml new file mode 100644 index 000000000000..78cfa462beeb --- /dev/null +++ b/core/tests/webkit/AndroidTest.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2017 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. +--> +<configuration description="Runs Frameworks WebView Loading Tests."> + <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup"> + <option name="test-file-name" value="WebViewLoadingTests.apk" /> + <option name="test-file-name" value="WebViewLoadingOnDiskTestApk.apk" /> + <option name="test-file-name" value="WebViewLoadingFromApkTestApk.apk" /> + <option name="cleanup-apks" value="true" /> + <option name="alt-dir" value="out" /> + </target_preparer> + + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="com.android.webkit.tests" /> + <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" /> + </test> +</configuration> diff --git a/core/tests/webkit/apk_with_native_libs/Android.mk b/core/tests/webkit/apk_with_native_libs/Android.mk new file mode 100644 index 000000000000..7c6c36e0a6a2 --- /dev/null +++ b/core/tests/webkit/apk_with_native_libs/Android.mk @@ -0,0 +1,66 @@ +# Copyright (C) 2017 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. + +LOCAL_PATH := $(call my-dir) +MY_PATH := $(LOCAL_PATH) + +# Set shared variables +MY_MODULE_TAGS := optional +MY_JNI_SHARED_LIBRARIES := libwebviewtest_jni +MY_MODULE_PATH := $(TARGET_OUT_DATA_APPS) +MY_SRC_FILES := $(call all-java-files-under, src) +MY_SDK_VERSION := system_current +MY_PROGUARD_ENABLED := disabled +MY_MULTILIB := both + +# Recurse down the file tree. +include $(call all-subdir-makefiles) + + + +# Builds an apk containing native libraries that will be unzipped on the device. +include $(CLEAR_VARS) + +LOCAL_PATH := $(MY_PATH) +LOCAL_PACKAGE_NAME := WebViewLoadingOnDiskTestApk +LOCAL_MANIFEST_FILE := ondisk/AndroidManifest.xml + +LOCAL_MODULE_TAGS := $(MY_MODULE_TAGS) +LOCAL_JNI_SHARED_LIBRARIES := $(MY_JNI_SHARED_LIBRARIES) +LOCAL_MODULE_PATH := $(MY_MODULE_PATH) +LOCAL_SRC_FILES := $(MY_SRC_FILES) +LOCAL_SDK_VERSION := $(MY_SDK_VERSION) +LOCAL_PROGUARD_ENABLED := $(MY_PROGUARD_ENABLED) +LOCAL_MULTILIB := $(MY_MULTILIB) + +include $(BUILD_PACKAGE) + + +# Builds an apk containing uncompressed native libraries that have to be +# accessed through the APK itself on the device. +include $(CLEAR_VARS) + +LOCAL_PATH := $(MY_PATH) +LOCAL_PACKAGE_NAME := WebViewLoadingFromApkTestApk +LOCAL_MANIFEST_FILE := inapk/AndroidManifest.xml + +LOCAL_MODULE_TAGS := $(MY_MODULE_TAGS) +LOCAL_JNI_SHARED_LIBRARIES := $(MY_JNI_SHARED_LIBRARIES) +LOCAL_MODULE_PATH := $(MY_MODULE_PATH) +LOCAL_SRC_FILES := $(MY_SRC_FILES) +LOCAL_SDK_VERSION := $(MY_SDK_VERSION) +LOCAL_PROGUARD_ENABLED := $(MY_PROGUARD_ENABLED) +LOCAL_MULTILIB := $(MY_MULTILIB) + +include $(BUILD_PACKAGE) diff --git a/core/tests/webkit/apk_with_native_libs/inapk/AndroidManifest.xml b/core/tests/webkit/apk_with_native_libs/inapk/AndroidManifest.xml new file mode 100644 index 000000000000..868b2388d135 --- /dev/null +++ b/core/tests/webkit/apk_with_native_libs/inapk/AndroidManifest.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + * Copyright (C) 2017 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. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.webviewloading_test_from_apk" + android:versionCode="1" + android:versionName="0.0.0.1"> + + <application android:label="WebView Loading Test APK" + android:multiArch="true" + android:extractNativeLibs="false"> + <meta-data android:name="com.android.webview.WebViewLibrary" + android:value="libwebviewtest_jni.so" /> + </application> +</manifest> diff --git a/core/tests/webkit/apk_with_native_libs/jni/Android.mk b/core/tests/webkit/apk_with_native_libs/jni/Android.mk new file mode 100644 index 000000000000..fd5b5eb50c5f --- /dev/null +++ b/core/tests/webkit/apk_with_native_libs/jni/Android.mk @@ -0,0 +1,32 @@ +# +# Copyright (C) 2017 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. +# + +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE := libwebviewtest_jni + +LOCAL_MODULE_TAGS := optional + +LOCAL_SRC_FILES := WebViewTestJniOnLoad.cpp + +LOCAL_C_INCLUDES := $(JNI_H_INCLUDE) + +LOCAL_SDK_VERSION := current + +LOCAL_MULTILIB := both + +include $(BUILD_SHARED_LIBRARY) diff --git a/core/java/android/service/autofill/SaveInfo.aidl b/core/tests/webkit/apk_with_native_libs/jni/WebViewTestJniOnLoad.cpp index 8cda608e1814..9b0502fc286d 100644 --- a/core/java/android/service/autofill/SaveInfo.aidl +++ b/core/tests/webkit/apk_with_native_libs/jni/WebViewTestJniOnLoad.cpp @@ -1,11 +1,11 @@ -/** - * Copyright (c) 2017, The Android Open Source Project +/* + * Copyright (C) 2017 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 + * 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, @@ -14,6 +14,8 @@ * limitations under the License. */ -package android.service.autofill; +#include <jni.h> -parcelable SaveInfo; +jint JNI_OnLoad(JavaVM *vm, void *reserved) { + return JNI_VERSION_1_4; +} diff --git a/core/tests/webkit/apk_with_native_libs/ondisk/AndroidManifest.xml b/core/tests/webkit/apk_with_native_libs/ondisk/AndroidManifest.xml new file mode 100644 index 000000000000..ffffeb8e1630 --- /dev/null +++ b/core/tests/webkit/apk_with_native_libs/ondisk/AndroidManifest.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + * Copyright (C) 2017 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. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.webviewloading_test_on_disk" + android:versionCode="1" + android:versionName="0.0.0.1"> + + <application android:label="WebView Loading Test APK" + android:multiArch="true"> + <meta-data android:name="com.android.webview.WebViewLibrary" + android:value="libwebviewtest_jni.so" /> + </application> +</manifest> diff --git a/core/java/android/view/autofill/AutoFillValue.aidl b/core/tests/webkit/apk_with_native_libs/src/com/google/android/xts/webview_list/WebViewLoadingTestClass.java index 05b75622c273..0efa4b4ac694 100644 --- a/core/java/android/view/autofill/AutoFillValue.aidl +++ b/core/tests/webkit/apk_with_native_libs/src/com/google/android/xts/webview_list/WebViewLoadingTestClass.java @@ -1,11 +1,11 @@ -/** - * Copyright (c) 2016, The Android Open Source Project +/* + * Copyright (C) 2017 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 + * 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, @@ -14,7 +14,11 @@ * limitations under the License. */ -package android.view.autofill; -// @deprecated TODO(b/35956626): remove once clients use AutofillValue -parcelable AutoFillValue;
\ No newline at end of file +package com.android.webview.chromium; + +/** + * An empty class for testing purposes. + */ +public class WebViewLoadingTestClass { +} diff --git a/core/tests/webkit/unit_tests_src/com/android/webkit/WebViewLibraryLoaderTest.java b/core/tests/webkit/unit_tests_src/com/android/webkit/WebViewLibraryLoaderTest.java new file mode 100644 index 000000000000..e2f2d37a4d68 --- /dev/null +++ b/core/tests/webkit/unit_tests_src/com/android/webkit/WebViewLibraryLoaderTest.java @@ -0,0 +1,329 @@ +/* + * Copyright (C) 2017 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.webkit; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.os.Build; +import android.os.Bundle; +import android.util.Log; + +import android.support.test.filters.MediumTest; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.support.test.InstrumentationRegistry; + +import java.io.File; +import java.io.IOException; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Unit tests for {@link WebViewLibraryLoader}. + * Use the following command to run these tests: + * make WebViewLoadingTests \ + * && adb install -r -d \ + * ${ANDROID_PRODUCT_OUT}/data/app/WebViewLoadingTests/WebViewLoadingTests.apk \ + * && adb shell am instrument -e class 'android.webkit.WebViewLibraryLoaderTest' -w \ + * 'com.android.webkit.tests/android.support.test.runner.AndroidJUnitRunner' + */ +@RunWith(AndroidJUnit4.class) +public final class WebViewLibraryLoaderTest { + private static final String WEBVIEW_LIBS_ON_DISK_TEST_APK = + "com.android.webviewloading_test_on_disk"; + private static final String WEBVIEW_LIBS_IN_APK_TEST_APK = + "com.android.webviewloading_test_from_apk"; + private static final String WEBVIEW_LOADING_TEST_NATIVE_LIB = "libwebviewtest_jni.so"; + + private PackageInfo webviewOnDiskPackageInfo; + private PackageInfo webviewFromApkPackageInfo; + + @Before public void setUp() throws PackageManager.NameNotFoundException { + PackageManager pm = InstrumentationRegistry.getContext().getPackageManager(); + webviewOnDiskPackageInfo = + pm.getPackageInfo(WEBVIEW_LIBS_ON_DISK_TEST_APK, PackageManager.GET_META_DATA); + webviewFromApkPackageInfo = + pm.getPackageInfo(WEBVIEW_LIBS_IN_APK_TEST_APK, PackageManager.GET_META_DATA); + } + + private static boolean is64BitDevice() { + return Build.SUPPORTED_64_BIT_ABIS.length > 0; + } + + // We test the getWebViewNativeLibraryDirectory method here because it handled several different + // cases/combinations and it seems unnecessary to create one test-apk for each such combination + // and arch. + + /** + * Ensure we fetch the correct native library directories in the multi-arch case where + * the primary ABI is 64-bit. + */ + @SmallTest + @Test public void testGetWebViewLibDirMultiArchPrimary64bit() { + final String nativeLib = "nativeLib"; + final String secondaryNativeLib = "secondaryNativeLib"; + PackageInfo packageInfo = new PackageInfo(); + ApplicationInfo ai = new ApplicationInfoBuilder(). + // See VMRuntime.ABI_TO_INSTRUCTION_SET_MAP + setPrimaryCpuAbi("arm64-v8a"). + setNativeLibraryDir(nativeLib). + setSecondaryCpuAbi("armeabi"). + setSecondaryNativeLibraryDir(secondaryNativeLib). + create(); + packageInfo.applicationInfo = ai; + String actual32Lib = + WebViewLibraryLoader.getWebViewNativeLibraryDirectory(ai, false /* is64bit */); + String actual64Lib = + WebViewLibraryLoader.getWebViewNativeLibraryDirectory(ai, true /* is64bit */); + assertEquals(nativeLib, actual64Lib); + assertEquals(secondaryNativeLib, actual32Lib); + } + + /** + * Ensure we fetch the correct native library directory in the 64-bit single-arch case. + */ + @SmallTest + @Test public void testGetWebViewLibDirSingleArch64bit() { + final String nativeLib = "nativeLib"; + PackageInfo packageInfo = new PackageInfo(); + ApplicationInfo ai = new ApplicationInfoBuilder(). + // See VMRuntime.ABI_TO_INSTRUCTION_SET_MAP + setPrimaryCpuAbi("arm64-v8a"). + setNativeLibraryDir(nativeLib). + create(); + packageInfo.applicationInfo = ai; + String actual64Lib = + WebViewLibraryLoader.getWebViewNativeLibraryDirectory(ai, true /* is64bit */); + assertEquals(nativeLib, actual64Lib); + } + + /** + * Ensure we fetch the correct native library directory in the 32-bit single-arch case. + */ + @SmallTest + @Test public void testGetWebViewLibDirSingleArch32bit() { + final String nativeLib = "nativeLib"; + PackageInfo packageInfo = new PackageInfo(); + ApplicationInfo ai = new ApplicationInfoBuilder(). + // See VMRuntime.ABI_TO_INSTRUCTION_SET_MAP + setPrimaryCpuAbi("armeabi-v7a"). + setNativeLibraryDir(nativeLib). + create(); + packageInfo.applicationInfo = ai; + String actual32Lib = + WebViewLibraryLoader.getWebViewNativeLibraryDirectory(ai, false /* is64bit */); + assertEquals(nativeLib, actual32Lib); + } + + /** + * Ensure we fetch the correct 32-bit library path from an APK with 32-bit and 64-bit + * libraries unzipped onto disk. + */ + @MediumTest + @Test public void testGetWebViewLibraryPathOnDisk32Bit() + throws WebViewFactory.MissingWebViewPackageException { + WebViewLibraryLoader.WebViewNativeLibrary actualNativeLib = + WebViewLibraryLoader.getWebViewNativeLibrary( + webviewOnDiskPackageInfo, false /* is64bit */); + String expectedLibaryDirectory = is64BitDevice() ? + webviewOnDiskPackageInfo.applicationInfo.secondaryNativeLibraryDir : + webviewOnDiskPackageInfo.applicationInfo.nativeLibraryDir; + String lib32Path = expectedLibaryDirectory + "/" + WEBVIEW_LOADING_TEST_NATIVE_LIB; + assertEquals("Fetched incorrect 32-bit path from WebView library.", + lib32Path, actualNativeLib.path); + } + + /** + * Ensure we fetch the correct 64-bit library path from an APK with 32-bit and 64-bit + * libraries unzipped onto disk. + */ + @MediumTest + @Test public void testGetWebViewLibraryPathOnDisk64Bit() + throws WebViewFactory.MissingWebViewPackageException { + // A 32-bit device will not unpack 64-bit libraries. + if (!is64BitDevice()) return; + + WebViewLibraryLoader.WebViewNativeLibrary actualNativeLib = + WebViewLibraryLoader.getWebViewNativeLibrary( + webviewOnDiskPackageInfo, true /* is64bit */); + String lib64Path = webviewOnDiskPackageInfo.applicationInfo.nativeLibraryDir + + "/" + WEBVIEW_LOADING_TEST_NATIVE_LIB; + assertEquals("Fetched incorrect 64-bit path from WebView library.", + lib64Path, actualNativeLib.path); + } + + /** + * Check the size of the 32-bit library fetched from an APK with both 32-bit and 64-bit + * libraries unzipped onto disk. + */ + @MediumTest + @Test public void testGetWebView32BitLibrarySizeOnDiskIsNonZero() + throws WebViewFactory.MissingWebViewPackageException { + WebViewLibraryLoader.WebViewNativeLibrary actual32BitNativeLib = + WebViewLibraryLoader.getWebViewNativeLibrary( + webviewOnDiskPackageInfo, false /* is64bit */); + assertTrue(actual32BitNativeLib.size > 0); + } + + /** + * Check the size of the 64-bit library fetched from an APK with both 32-bit and 64-bit + * libraries unzipped onto disk. + */ + @MediumTest + @Test public void testGetWebView64BitLibrarySizeOnDiskIsNonZero() + throws WebViewFactory.MissingWebViewPackageException { + // A 32-bit device will not unpack 64-bit libraries. + if (!is64BitDevice()) return; + WebViewLibraryLoader.WebViewNativeLibrary actual64BitNativeLib = + WebViewLibraryLoader.getWebViewNativeLibrary( + webviewOnDiskPackageInfo, true /* is64bit */); + assertTrue(actual64BitNativeLib.size > 0); + } + + /** + * Ensure we fetch the correct 32-bit library path from an APK with both 32-bit and 64-bit + * libraries stored uncompressed in the APK. + */ + @MediumTest + @Test public void testGetWebView32BitLibraryPathFromApk() + throws WebViewFactory.MissingWebViewPackageException, IOException { + WebViewLibraryLoader.WebViewNativeLibrary actualNativeLib = + WebViewLibraryLoader.getWebViewNativeLibrary( + webviewFromApkPackageInfo, false /* is64bit */); + // The device might have ignored the app's request to not extract native libs, so first + // check whether the library paths match those of extracted libraries. + String expectedLibaryDirectory = is64BitDevice() ? + webviewFromApkPackageInfo.applicationInfo.secondaryNativeLibraryDir : + webviewFromApkPackageInfo.applicationInfo.nativeLibraryDir; + String lib32Path = expectedLibaryDirectory + "/" + WEBVIEW_LOADING_TEST_NATIVE_LIB; + if (lib32Path.equals(actualNativeLib.path)) { + // If the libraries were extracted to disk, ensure that they're actually there. + assertTrue("The given WebView library doesn't exist.", + new File(actualNativeLib.path).exists()); + } else { // The libraries were not extracted to disk. + assertIsValidZipEntryPath(actualNativeLib.path, + webviewFromApkPackageInfo.applicationInfo.sourceDir); + } + } + + /** + * Ensure we fetch the correct 32-bit library path from an APK with both 32-bit and 64-bit + * libraries stored uncompressed in the APK. + */ + @MediumTest + @Test public void testGetWebView64BitLibraryPathFromApk() + throws WebViewFactory.MissingWebViewPackageException, IOException { + // A 32-bit device will not unpack 64-bit libraries. + if (!is64BitDevice()) return; + + WebViewLibraryLoader.WebViewNativeLibrary actualNativeLib = + WebViewLibraryLoader.getWebViewNativeLibrary( + webviewFromApkPackageInfo, true /* is64bit */); + assertIsValidZipEntryPath(actualNativeLib.path, + webviewFromApkPackageInfo.applicationInfo.sourceDir); + } + + private static void assertIsValidZipEntryPath(String path, String zipFilePath) + throws IOException { + assertTrue("The path to a zip entry must start with the path to the zip file itself." + + "Expected zip path: " + zipFilePath + ", actual zip entry: " + path, + path.startsWith(zipFilePath + "!/")); + String[] pathSplit = path.split("!/"); + assertEquals("A zip file path should have two parts, the zip path, and the zip entry path.", + 2, pathSplit.length); + ZipFile zipFile = new ZipFile(pathSplit[0]); + assertNotNull("Path doesn't point to a valid zip entry: " + path, + zipFile.getEntry(pathSplit[1])); + } + + + /** + * Check the size of the 32-bit library fetched from an APK with both 32-bit and 64-bit + * libraries stored uncompressed in the APK. + */ + @MediumTest + @Test public void testGetWebView32BitLibrarySizeFromApkIsNonZero() + throws WebViewFactory.MissingWebViewPackageException { + WebViewLibraryLoader.WebViewNativeLibrary actual32BitNativeLib = + WebViewLibraryLoader.getWebViewNativeLibrary( + webviewFromApkPackageInfo, false /* is64bit */); + assertTrue(actual32BitNativeLib.size > 0); + } + + /** + * Check the size of the 64-bit library fetched from an APK with both 32-bit and 64-bit + * libraries stored uncompressed in the APK. + */ + @MediumTest + @Test public void testGetWebView64BitLibrarySizeFromApkIsNonZero() + throws WebViewFactory.MissingWebViewPackageException { + // A 32-bit device will not unpack 64-bit libraries. + if (!is64BitDevice()) return; + + WebViewLibraryLoader.WebViewNativeLibrary actual64BitNativeLib = + WebViewLibraryLoader.getWebViewNativeLibrary( + webviewFromApkPackageInfo, true /* is64bit */); + assertTrue(actual64BitNativeLib.size > 0); + } + + private static class ApplicationInfoBuilder { + ApplicationInfo ai; + + public ApplicationInfoBuilder setPrimaryCpuAbi(String primaryCpuAbi) { + ai.primaryCpuAbi = primaryCpuAbi; + return this; + } + + public ApplicationInfoBuilder setSecondaryCpuAbi(String secondaryCpuAbi) { + ai.secondaryCpuAbi = secondaryCpuAbi; + return this; + } + + public ApplicationInfoBuilder setNativeLibraryDir(String nativeLibraryDir) { + ai.nativeLibraryDir = nativeLibraryDir; + return this; + } + + public ApplicationInfoBuilder setSecondaryNativeLibraryDir( + String secondaryNativeLibraryDir) { + ai.secondaryNativeLibraryDir = secondaryNativeLibraryDir; + return this; + } + + public ApplicationInfoBuilder setMetaData(Bundle metaData) { + ai.metaData = metaData; + return this; + } + + public ApplicationInfoBuilder() { + ai = new android.content.pm.ApplicationInfo(); + } + + public ApplicationInfo create() { + return ai; + } + } +} diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 6f2b03838b06..2f9ae578ade1 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -24,6 +24,10 @@ applications that come with the platform <permission name="android.permission.PROVIDE_RESOLVER_RANKER_SERVICE" /> </privapp-permissions> + <privapp-permissions package="com.android.apps.tag"> + <permission name="android.permission.WRITE_SECURE_SETTINGS"/> + </privapp-permissions> + <privapp-permissions package="com.android.backupconfirm"> <permission name="android.permission.BACKUP"/> <permission name="android.permission.CRYPT_KEEPER"/> @@ -54,6 +58,7 @@ applications that come with the platform <permission name="android.permission.CONTROL_INCALL_EXPERIENCE"/> <permission name="android.permission.GET_ACCOUNTS_PRIVILEGED"/> <permission name="android.permission.MODIFY_PHONE_STATE"/> + <permission name="android.permission.STATUS_BAR"/> <permission name="android.permission.STOP_APP_SWITCHES"/> <permission name="com.android.voicemail.permission.READ_VOICEMAIL"/> <permission name="com.android.voicemail.permission.WRITE_VOICEMAIL"/> @@ -353,6 +358,7 @@ applications that come with the platform <permission name="android.permission.WRITE_DREAM_STATE"/> <permission name="android.permission.WRITE_MEDIA_STORAGE"/> <permission name="android.permission.WRITE_SECURE_SETTINGS"/> + <permission name="com.android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS"/> </privapp-permissions> <privapp-permissions package="com.android.tv"> diff --git a/legacy-test/Android.mk b/legacy-test/Android.mk index 0e6b31e6ebd9..bec22c934fc6 100644 --- a/legacy-test/Android.mk +++ b/legacy-test/Android.mk @@ -93,6 +93,8 @@ include $(CLEAR_VARS) LOCAL_MODULE := legacy.test.stubs LOCAL_SOURCE_FILES_ALL_GENERATED := true +LOCAL_SDK_VERSION := current +LOCAL_ADDITIONAL_DEPENDENCIES := $(legacy_test_api_gen_stamp) # Make sure to run droiddoc first to generate the stub source files. LOCAL_ADDITIONAL_DEPENDENCIES := $(legacy_test_api_gen_stamp) @@ -162,3 +164,5 @@ LOCAL_MODULE := legacy-performance-test-hostdex include $(BUILD_HOST_DALVIK_STATIC_JAVA_LIBRARY) endif # HOST_OS == linux + +legacy_test_api_gen_stamp := diff --git a/libs/hwui/renderstate/RenderState.cpp b/libs/hwui/renderstate/RenderState.cpp index 5fc5cb275741..6c606f738aaf 100644 --- a/libs/hwui/renderstate/RenderState.cpp +++ b/libs/hwui/renderstate/RenderState.cpp @@ -105,8 +105,8 @@ static void layerDestroyedVkContext(Layer* layer) { } void RenderState::onVkContextDestroyed() { - mLayerPool->clear(); std::for_each(mActiveLayers.begin(), mActiveLayers.end(), layerDestroyedVkContext); + destroyLayersInUpdater(); GpuMemoryTracker::onGpuContextDestroyed(); } diff --git a/libs/protoutil/include/android/util/ProtoOutputStream.h b/libs/protoutil/include/android/util/ProtoOutputStream.h index 0f1ccedeaae2..10be6499417f 100644 --- a/libs/protoutil/include/android/util/ProtoOutputStream.h +++ b/libs/protoutil/include/android/util/ProtoOutputStream.h @@ -49,7 +49,7 @@ public: bool write(uint64_t fieldId, long long val); bool write(uint64_t fieldId, bool val); bool write(uint64_t fieldId, std::string val); - bool write(uint64_t fieldId, const char* val); + bool write(uint64_t fieldId, const char* val, size_t size); /** * Starts a sub-message write session. @@ -103,4 +103,4 @@ private: } } -#endif // ANDROID_UTIL_PROTOOUTPUT_STREAM_H
\ No newline at end of file +#endif // ANDROID_UTIL_PROTOOUTPUT_STREAM_H diff --git a/libs/protoutil/src/ProtoOutputStream.cpp b/libs/protoutil/src/ProtoOutputStream.cpp index 15144ac2eb28..9dadf1c20510 100644 --- a/libs/protoutil/src/ProtoOutputStream.cpp +++ b/libs/protoutil/src/ProtoOutputStream.cpp @@ -225,14 +225,13 @@ ProtoOutputStream::write(uint64_t fieldId, string val) } bool -ProtoOutputStream::write(uint64_t fieldId, const char* val) +ProtoOutputStream::write(uint64_t fieldId, const char* val, size_t size) { if (mCompact) return false; const uint32_t id = (uint32_t)fieldId; - int size = 0; - while (val[size] != '\0') size++; switch (fieldId & FIELD_TYPE_MASK) { case TYPE_STRING: + case TYPE_BYTES: writeUtf8StringImpl(id, val, size); return true; default: diff --git a/location/java/android/location/GnssClock.java b/location/java/android/location/GnssClock.java index 25d247deff24..671c57cb6ad9 100644 --- a/location/java/android/location/GnssClock.java +++ b/location/java/android/location/GnssClock.java @@ -332,6 +332,9 @@ public final class GnssClock implements Parcelable { /** * Gets the clock's Drift in nanoseconds per second. * + * <p>This value is the instantaneous time-derivative of the value provided by + * {@link #getBiasNanos()}. + * * <p>A positive value indicates that the frequency is higher than the nominal (e.g. GPS master * clock) frequency. The error estimate for this reported drift is * {@link #getDriftUncertaintyNanosPerSecond()}. diff --git a/packages/EasterEgg/src/com/android/egg/octo/Ocquarium.java b/packages/EasterEgg/src/com/android/egg/octo/Ocquarium.java index bbbdf80612d7..8a06cc604f1e 100644 --- a/packages/EasterEgg/src/com/android/egg/octo/Ocquarium.java +++ b/packages/EasterEgg/src/com/android/egg/octo/Ocquarium.java @@ -26,6 +26,7 @@ import com.android.egg.R; public class Ocquarium extends Activity { ImageView mImageView; + private OctopusDrawable mOcto; @Override protected void onCreate(Bundle savedInstanceState) { @@ -43,10 +44,9 @@ public class Ocquarium extends Activity { bg.addView(mImageView, new FrameLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); - final OctopusDrawable octo = new OctopusDrawable(getApplicationContext()); - octo.setSizePx((int) (OctopusDrawable.randfrange(40f,180f) * dp)); - mImageView.setImageDrawable(octo); - octo.startDrift(); + mOcto = new OctopusDrawable(getApplicationContext()); + mOcto.setSizePx((int) (OctopusDrawable.randfrange(40f,180f) * dp)); + mImageView.setImageDrawable(mOcto); mImageView.setOnTouchListener(new View.OnTouchListener() { boolean touching; @@ -54,24 +54,36 @@ public class Ocquarium extends Activity { public boolean onTouch(View view, MotionEvent motionEvent) { switch (motionEvent.getActionMasked()) { case MotionEvent.ACTION_DOWN: - if (octo.hitTest(motionEvent.getX(), motionEvent.getY())) { + if (mOcto.hitTest(motionEvent.getX(), motionEvent.getY())) { touching = true; - octo.stopDrift(); + mOcto.stopDrift(); } break; case MotionEvent.ACTION_MOVE: if (touching) { - octo.moveTo(motionEvent.getX(), motionEvent.getY()); + mOcto.moveTo(motionEvent.getX(), motionEvent.getY()); } break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: touching = false; - octo.startDrift(); + mOcto.startDrift(); break; } return true; } }); } + + @Override + protected void onPause() { + mOcto.stopDrift(); + super.onPause(); + } + + @Override + protected void onResume() { + super.onResume(); + mOcto.startDrift(); + } } diff --git a/packages/SettingsLib/OWNERS b/packages/SettingsLib/OWNERS new file mode 100644 index 000000000000..4211c272b774 --- /dev/null +++ b/packages/SettingsLib/OWNERS @@ -0,0 +1,21 @@ +# People who can approve changes for submission +asapperstein@google.com +asargent@google.com +dehboxturtle@google.com +dhnishi@google.com +dling@google.com +dsandler@google.com +evanlaird@google.com +jackqdyulei@google.com +jmonk@google.com +mfritze@google.com +nicoya@google.com +rogerxue@google.com +virgild@google.com +zhfan@google.com + +# Emergency approvers in case the above are not available +miket@google.com + +# Exempt resource files (because they are in a flat directory and too hard to manage via OWNERS) +per-file *.xml=*
\ No newline at end of file diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java index 8def03648558..d90386f904ec 100644 --- a/packages/SettingsLib/src/com/android/settingslib/Utils.java +++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java @@ -12,9 +12,7 @@ import android.content.res.ColorStateList; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Bitmap; -import android.graphics.BitmapFactory; import android.graphics.Color; -import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; import android.net.ConnectivityManager; import android.os.BatteryManager; @@ -297,4 +295,9 @@ public class Utils { } return defaultDays; } + + public static boolean isWifiOnly(Context context) { + return !context.getSystemService(ConnectivityManager.class) + .isNetworkSupported(ConnectivityManager.TYPE_MOBILE); + } } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java index d1621da09a6c..213002fb9726 100755 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java @@ -19,7 +19,7 @@ package com.android.settingslib.bluetooth; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothInputDevice; +import android.bluetooth.BluetoothHidHost; import android.bluetooth.BluetoothProfile; import android.content.Context; import android.util.Log; @@ -35,7 +35,7 @@ public class HidProfile implements LocalBluetoothProfile { private static final String TAG = "HidProfile"; private static boolean V = true; - private BluetoothInputDevice mService; + private BluetoothHidHost mService; private boolean mIsProfileReady; private final LocalBluetoothAdapter mLocalAdapter; @@ -53,7 +53,7 @@ public class HidProfile implements LocalBluetoothProfile { public void onServiceConnected(int profile, BluetoothProfile proxy) { if (V) Log.d(TAG,"Bluetooth service connected"); - mService = (BluetoothInputDevice) proxy; + mService = (BluetoothHidHost) proxy; // We just bound to the service, so refresh the UI for any connected HID devices. List<BluetoothDevice> deviceList = mService.getConnectedDevices(); while (!deviceList.isEmpty()) { @@ -87,7 +87,7 @@ public class HidProfile implements LocalBluetoothProfile { mDeviceManager = deviceManager; mProfileManager = profileManager; adapter.getProfileProxy(context, new InputDeviceServiceListener(), - BluetoothProfile.INPUT_DEVICE); + BluetoothProfile.HID_HOST); } public boolean isConnectable() { @@ -190,7 +190,7 @@ public class HidProfile implements LocalBluetoothProfile { if (V) Log.d(TAG, "finalize()"); if (mService != null) { try { - BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.INPUT_DEVICE, + BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.HID_HOST, mService); mService = null; }catch (Throwable t) { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java index 0750dc750051..9cda669379dd 100755 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java @@ -21,9 +21,9 @@ import android.bluetooth.BluetoothA2dpSink; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHeadset; import android.bluetooth.BluetoothHeadsetClient; +import android.bluetooth.BluetoothHidHost; import android.bluetooth.BluetoothMap; import android.bluetooth.BluetoothMapClient; -import android.bluetooth.BluetoothInputDevice; import android.bluetooth.BluetoothPan; import android.bluetooth.BluetoothPbapClient; import android.bluetooth.BluetoothProfile; @@ -123,7 +123,7 @@ public class LocalBluetoothProfileManager { // Always add HID and PAN profiles mHidProfile = new HidProfile(context, mLocalAdapter, mDeviceManager, this); addProfile(mHidProfile, HidProfile.NAME, - BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED); + BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED); mPanProfile = new PanProfile(context); addPanProfile(mPanProfile, PanProfile.NAME, diff --git a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractSimStatusImeiInfoPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractSimStatusImeiInfoPreferenceController.java new file mode 100644 index 000000000000..a78440c271c9 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractSimStatusImeiInfoPreferenceController.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2017 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.settingslib.deviceinfo; + +import android.content.Context; +import android.os.UserManager; + +import com.android.settingslib.Utils; +import com.android.settingslib.core.AbstractPreferenceController; + +public abstract class AbstractSimStatusImeiInfoPreferenceController + extends AbstractPreferenceController { + public AbstractSimStatusImeiInfoPreferenceController(Context context) { + super(context); + } + + @Override + public boolean isAvailable() { + return mContext.getSystemService(UserManager.class).isAdminUser() + && !Utils.isWifiOnly(mContext); + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java b/packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java index 817989ab96ae..f4c9bb31f01a 100755 --- a/packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java +++ b/packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java @@ -415,8 +415,8 @@ public class BatteryMeterDrawableBase extends Drawable { : (mLevel == 100 ? 0.38f : 0.5f))); mTextHeight = -mTextPaint.getFontMetrics().ascent; pctText = String.valueOf(SINGLE_DIGIT_PERCENT ? (level / 10) : level); - pctX = mWidth * 0.5f; - pctY = (mHeight + mTextHeight) * 0.47f; + pctX = mWidth * 0.5f + left; + pctY = (mHeight + mTextHeight) * 0.47f + top; pctOpaque = levelTop > pctY; if (!pctOpaque) { mTextPath.reset(); @@ -439,8 +439,8 @@ public class BatteryMeterDrawableBase extends Drawable { if (!mCharging && !mPowerSaveEnabled) { if (level <= mCriticalLevel) { // draw the warning text - final float x = mWidth * 0.5f; - final float y = (mHeight + mWarningTextHeight) * 0.48f; + final float x = mWidth * 0.5f + left; + final float y = (mHeight + mWarningTextHeight) * 0.48f + top; c.drawText(mWarningString, x, y, mWarningTextPaint); } else if (pctOpaque) { // draw the percentage text diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/OWNERS b/packages/SettingsLib/src/com/android/settingslib/inputmethod/OWNERS new file mode 100644 index 000000000000..a0e28baee3f6 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/OWNERS @@ -0,0 +1,5 @@ +# Default reviewers for this and subdirectories. +takaoka@google.com +yukawa@google.com + +# Emergency approvers in case the above are not available
\ No newline at end of file diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/SimStatusImeiInfoPreferenceControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/SimStatusImeiInfoPreferenceControllerTest.java new file mode 100644 index 000000000000..28409fae45e6 --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/SimStatusImeiInfoPreferenceControllerTest.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2017 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.settingslib.deviceinfo; + +import static com.google.common.truth.Truth.assertThat; + +import static org.robolectric.shadow.api.Shadow.extract; + +import android.net.ConnectivityManager; +import android.os.UserManager; +import android.util.SparseBooleanArray; + +import com.android.settingslib.SettingsLibRobolectricTestRunner; +import com.android.settingslib.TestConfig; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; + +@RunWith(SettingsLibRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION, + shadows = {SimStatusImeiInfoPreferenceControllerTest.ShadowUserManager.class, + SimStatusImeiInfoPreferenceControllerTest.ShadowConnectivityManager.class}) +public class SimStatusImeiInfoPreferenceControllerTest { + + private AbstractSimStatusImeiInfoPreferenceController mController; + + @Before + public void setUp() { + mController = new AbstractSimStatusImeiInfoPreferenceController( + RuntimeEnvironment.application) { + @Override + public String getPreferenceKey() { + return null; + } + }; + } + + @Test + public void testIsAvailable_isAdminAndHasMobile_shouldReturnTrue() { + ShadowUserManager userManager = + extract(RuntimeEnvironment.application.getSystemService(UserManager.class)); + userManager.setIsAdminUser(true); + ShadowConnectivityManager connectivityManager = + extract(RuntimeEnvironment.application.getSystemService(ConnectivityManager.class)); + connectivityManager.setNetworkSupported(ConnectivityManager.TYPE_MOBILE, true); + + assertThat(mController.isAvailable()).isTrue(); + } + + @Test + public void testIsAvailable_isAdminButNoMobile_shouldReturnFalse() { + ShadowUserManager userManager = + extract(RuntimeEnvironment.application.getSystemService(UserManager.class)); + userManager.setIsAdminUser(true); + ShadowConnectivityManager connectivityManager = + extract(RuntimeEnvironment.application.getSystemService(ConnectivityManager.class)); + connectivityManager.setNetworkSupported(ConnectivityManager.TYPE_MOBILE, false); + + assertThat(mController.isAvailable()).isFalse(); + } + + @Test + public void testIsAvailable_isNotAdmin_shouldReturnFalse() { + ShadowUserManager userManager = + extract(RuntimeEnvironment.application.getSystemService(UserManager.class)); + userManager.setIsAdminUser(false); + + assertThat(mController.isAvailable()).isFalse(); + } + + @Implements(UserManager.class) + public static class ShadowUserManager extends org.robolectric.shadows.ShadowUserManager { + + private boolean mAdminUser; + + public void setIsAdminUser(boolean isAdminUser) { + mAdminUser = isAdminUser; + } + + @Implementation + public boolean isAdminUser() { + return mAdminUser; + } + } + + @Implements(ConnectivityManager.class) + public static class ShadowConnectivityManager + extends org.robolectric.shadows.ShadowConnectivityManager { + + private final SparseBooleanArray mSupportedNetworkTypes = new SparseBooleanArray(); + + public void setNetworkSupported(int networkType, boolean supported) { + mSupportedNetworkTypes.put(networkType, supported); + } + + @Implementation + public boolean isNetworkSupported(int networkType) { + return mSupportedNetworkTypes.get(networkType); + } + } +} diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/graph/BatteryMeterDrawableBaseTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/graph/BatteryMeterDrawableBaseTest.java new file mode 100644 index 000000000000..3522b8abfe62 --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/graph/BatteryMeterDrawableBaseTest.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2017 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.settingslib.graph; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.verify; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; + +import com.android.settingslib.SettingsLibRobolectricTestRunner; +import com.android.settingslib.TestConfig; +import com.android.settingslib.testutils.shadow.SettingsLibShadowResources; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +@RunWith(SettingsLibRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION, + shadows = SettingsLibShadowResources.class) +public class BatteryMeterDrawableBaseTest { + private static final int PADDING = 5; + private static final int HEIGHT = 80; + private static final int WIDTH = 40; + @Mock + private Canvas mCanvas; + private Context mContext; + private BatteryMeterDrawableBase mBatteryMeterDrawableBase; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mContext = RuntimeEnvironment.application; + mBatteryMeterDrawableBase = new BatteryMeterDrawableBase(mContext, 0 /* frameColor */); + } + + @Test + public void testDraw_hasPaddingAndBounds_drawWarningInCorrectPosition() { + mBatteryMeterDrawableBase.setPadding(PADDING, PADDING, PADDING, PADDING); + mBatteryMeterDrawableBase.setBounds(0, 0, WIDTH + 2 * PADDING, HEIGHT + 2 * PADDING); + mBatteryMeterDrawableBase.setBatteryLevel(3); + + mBatteryMeterDrawableBase.draw(mCanvas); + + // WIDTH * 0.5 + PADDING = 25 + // (HEIGHT + TEXT_HEIGHT) * 0.48 + PADDING = 43.3999998 + verify(mCanvas).drawText(eq("!"), eq(25f), eq(43.399998f), any(Paint.class)); + } + + @Test + public void testDraw_hasPaddingAndBounds_drawBatteryLevelInCorrectPosition() { + mBatteryMeterDrawableBase.setPadding(PADDING, PADDING, PADDING, PADDING); + mBatteryMeterDrawableBase.setBounds(0, 0, WIDTH + 2 * PADDING, HEIGHT + 2 * PADDING); + mBatteryMeterDrawableBase.setBatteryLevel(20); + mBatteryMeterDrawableBase.setShowPercent(true); + + mBatteryMeterDrawableBase.draw(mCanvas); + + // WIDTH * 0.5 + PADDING = 25 + // (HEIGHT + TEXT_HEIGHT) * 0.47 + PADDING = 42.6 + verify(mCanvas).drawText(eq("20"), eq(25f), eq(42.6f), any(Paint.class)); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java index 8869e8dd3821..ddd991023273 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java @@ -185,10 +185,10 @@ public class QSDetailItems extends FrameLayout { } view.setVisibility(mItemsVisible ? VISIBLE : INVISIBLE); final ImageView iv = (ImageView) view.findViewById(android.R.id.icon); - if (item.iconDrawable != null) { - iv.setImageDrawable(item.iconDrawable.getDrawable(iv.getContext())); + if (item.icon != null) { + iv.setImageDrawable(item.icon.getDrawable(iv.getContext())); } else { - iv.setImageResource(item.icon); + iv.setImageResource(item.iconResId); } iv.getOverlay().clear(); if (item.overlay != null) { @@ -258,8 +258,8 @@ public class QSDetailItems extends FrameLayout { } public static class Item { - public int icon; - public QSTile.Icon iconDrawable; + public int iconResId; + public QSTile.Icon icon; public Drawable overlay; public CharSequence line1; public CharSequence line2; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java index bc3ccb41cce0..1aecdceb378c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java @@ -134,7 +134,7 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { if (lastDevice != null) { int batteryLevel = lastDevice.getBatteryLevel(); if (batteryLevel != BluetoothDevice.BATTERY_LEVEL_UNKNOWN) { - state.icon = new BluetoothBatteryDrawable(batteryLevel, + state.icon = new BluetoothBatteryTileIcon(batteryLevel, mContext.getResources().getFraction( R.fraction.bt_battery_scale_fraction, 1, 1)); } @@ -212,15 +212,11 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { return new BluetoothDetailAdapter(); } - private class BluetoothBatteryDrawable extends Icon { + private class BluetoothBatteryTileIcon extends Icon { private int mLevel; private float mIconScale; - BluetoothBatteryDrawable(int level) { - this(level, 1 /* iconScale */); - } - - BluetoothBatteryDrawable(int level, float iconScale) { + BluetoothBatteryTileIcon(int level, float iconScale) { mLevel = level; mIconScale = iconScale; } @@ -302,15 +298,16 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { for (CachedBluetoothDevice device : devices) { if (mController.getBondState(device) == BluetoothDevice.BOND_NONE) continue; final Item item = new Item(); - item.icon = R.drawable.ic_qs_bluetooth_on; + item.iconResId = R.drawable.ic_qs_bluetooth_on; item.line1 = device.getName(); item.tag = device; int state = device.getMaxConnectionState(); if (state == BluetoothProfile.STATE_CONNECTED) { - item.icon = R.drawable.ic_qs_bluetooth_connected; + item.iconResId = R.drawable.ic_qs_bluetooth_connected; int batteryLevel = device.getBatteryLevel(); if (batteryLevel != BluetoothDevice.BATTERY_LEVEL_UNKNOWN) { - item.iconDrawable = new BluetoothBatteryDrawable(batteryLevel); + item.icon = new BluetoothBatteryTileIcon(batteryLevel, + 1 /* iconScale */); item.line2 = mContext.getString( R.string.quick_settings_connected_battery_level, Utils.formatPercentage(batteryLevel)); @@ -321,7 +318,7 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { items.add(connectedDevices, item); connectedDevices++; } else if (state == BluetoothProfile.STATE_CONNECTING) { - item.icon = R.drawable.ic_qs_bluetooth_connecting; + item.iconResId = R.drawable.ic_qs_bluetooth_connecting; item.line2 = mContext.getString(R.string.quick_settings_connecting); items.add(connectedDevices, item); } else { 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 fb396b9ddb07..678aa7116f73 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java @@ -19,26 +19,17 @@ package com.android.systemui.qs.tiles; import static android.media.MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY; import android.app.Dialog; -import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -import android.content.IntentFilter; -import android.media.MediaRouter; -import android.os.UserHandle; import android.provider.Settings; import android.service.quicksettings.Tile; import android.util.Log; -import android.view.ContextThemeWrapper; import android.view.View; import android.view.View.OnAttachStateChangeListener; -import android.view.View.OnClickListener; import android.view.ViewGroup; -import android.view.WindowManager; import android.view.WindowManager.LayoutParams; import android.widget.Button; -import com.android.internal.app.MediaRouteChooserDialog; -import com.android.internal.app.MediaRouteControllerDialog; import com.android.internal.app.MediaRouteDialogPresenter; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; @@ -280,7 +271,7 @@ public class CastTile extends QSTileImpl<BooleanState> { for (CastDevice device : devices) { if (device.state == CastDevice.STATE_CONNECTED) { final Item item = new Item(); - item.icon = R.drawable.ic_qs_cast_on; + item.iconResId = R.drawable.ic_qs_cast_on; item.line1 = getDeviceName(device); item.line2 = mContext.getString(R.string.quick_settings_connected); item.tag = device; @@ -300,7 +291,7 @@ public class CastTile extends QSTileImpl<BooleanState> { final CastDevice device = mVisibleOrder.get(id); if (!devices.contains(device)) continue; final Item item = new Item(); - item.icon = R.drawable.ic_qs_cast_off; + item.iconResId = R.drawable.ic_qs_cast_off; item.line1 = getDeviceName(device); if (device.state == CastDevice.STATE_CONNECTING) { item.line2 = mContext.getString(R.string.quick_settings_connecting); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java index 23702736b0db..977a725829bb 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java @@ -402,7 +402,7 @@ public class WifiTile extends QSTileImpl<SignalState> { final AccessPoint ap = mAccessPoints[i]; final Item item = new Item(); item.tag = ap; - item.icon = mWifiController.getIcon(ap); + item.iconResId = mWifiController.getIcon(ap); item.line1 = ap.getSsid(); item.line2 = ap.isActive() ? ap.getSummary() : null; item.icon2 = ap.getSecurity() != AccessPoint.SECURITY_NONE diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java index 68fe9a83c707..84b7015f474e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java @@ -29,7 +29,6 @@ import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.ViewAnimationUtils; -import android.view.ViewConfiguration; import android.view.accessibility.AccessibilityManager; import android.view.animation.Interpolator; import android.view.animation.PathInterpolator; @@ -173,12 +172,12 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView private int mOverrideTint; private float mOverrideAmount; private boolean mShadowHidden; - private boolean mWasActivatedOnDown; /** * Similar to mDimmed but is also true if it's not dimmable but should be */ private boolean mNeedsDimming; private int mDimmedAlpha; + private boolean mBlockNextTouch; public ActivatableNotificationView(Context context, AttributeSet attrs) { super(context, attrs); @@ -204,7 +203,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView } else { makeInactive(true /* animate */); } - }, this::performClick, this::handleSlideBack, mFalsingManager::onNotificationDoubleTap); + }, super::performClick, this::handleSlideBack, mFalsingManager::onNotificationDoubleTap); } @Override @@ -241,9 +240,15 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView @Override public boolean onInterceptTouchEvent(MotionEvent ev) { - if (mNeedsDimming && !mActivated && ev.getActionMasked() == MotionEvent.ACTION_DOWN + if (mNeedsDimming && ev.getActionMasked() == MotionEvent.ACTION_DOWN && disallowSingleClick(ev) && !isTouchExplorationEnabled()) { - return true; + if (!mActivated) { + return true; + } else if (!mDoubleTapHelper.isWithinDoubleTapSlop(ev)) { + mBlockNextTouch = true; + makeInactive(true /* animate */); + return true; + } } return super.onInterceptTouchEvent(ev); } @@ -263,10 +268,11 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView @Override public boolean onTouchEvent(MotionEvent event) { boolean result; - if (event.getAction() == MotionEvent.ACTION_DOWN) { - mWasActivatedOnDown = mActivated; + if (mBlockNextTouch) { + mBlockNextTouch = false; + return false; } - if ((mNeedsDimming && !mActivated) && !isTouchExplorationEnabled() && isInteractive()) { + if (mNeedsDimming && !isTouchExplorationEnabled() && isInteractive()) { boolean wasActivated = mActivated; result = handleTouchEventDimmed(event); if (wasActivated && result && event.getAction() == MotionEvent.ACTION_UP) { @@ -312,7 +318,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView @Override public boolean performClick() { - if (mWasActivatedOnDown || !mNeedsDimming || isTouchExplorationEnabled()) { + if (!mNeedsDimming || isTouchExplorationEnabled()) { return super.performClick(); } return false; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DoubleTapHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DoubleTapHelper.java index dcb6a3801844..0d62703cbced 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DoubleTapHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DoubleTapHelper.java @@ -142,7 +142,7 @@ public class DoubleTapHelper { && Math.abs(event.getY() - mDownY) < mTouchSlop; } - private boolean isWithinDoubleTapSlop(MotionEvent event) { + public boolean isWithinDoubleTapSlop(MotionEvent event) { if (!mActivated) { // If we're not activated there's no double tap slop to satisfy. return true; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java index d24e51c73151..40ee8386e895 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java @@ -382,13 +382,6 @@ public class NetworkControllerImpl extends BroadcastReceiver new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... args) { - // Disable tethering if enabling Wifi - final int wifiApState = mWifiManager.getWifiApState(); - if (enabled && ((wifiApState == WifiManager.WIFI_AP_STATE_ENABLING) || - (wifiApState == WifiManager.WIFI_AP_STATE_ENABLED))) { - mWifiManager.setWifiApEnabled(null, false); - } - mWifiManager.setWifiEnabled(enabled); return null; } diff --git a/proto/Android.bp b/proto/Android.bp new file mode 100644 index 000000000000..95f453c3e523 --- /dev/null +++ b/proto/Android.bp @@ -0,0 +1,17 @@ +java_library_static { + name: "framework-protos", + host_supported: true, + proto: { + type: "nano", + }, + srcs: ["src/**/*.proto"], + no_framework_libs: true, + target: { + android: { + jarjar_rules: "jarjar-rules.txt", + }, + host: { + static_libs: ["libprotobuf-java-nano"], + }, + }, +} diff --git a/proto/Android.mk b/proto/Android.mk deleted file mode 100644 index 1c03d1616f9f..000000000000 --- a/proto/Android.mk +++ /dev/null @@ -1,33 +0,0 @@ -LOCAL_PATH:= $(call my-dir) - -include $(CLEAR_VARS) - -LOCAL_MODULE := framework-protos - -LOCAL_PROTOC_OPTIMIZE_TYPE := nano -LOCAL_SRC_FILES:= $(call all-proto-files-under, src) -LOCAL_JARJAR_RULES := $(LOCAL_PATH)/jarjar-rules.txt - -LOCAL_NO_STANDARD_LIBRARIES := true -LOCAL_JAVA_LIBRARIES := core-oj core-libart - -LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk $(LOCAL_PATH)/jarjar-rules.txt - -include $(BUILD_STATIC_JAVA_LIBRARY) - -# Host-side version of framework-protos -# ============================================================ -include $(CLEAR_VARS) - -LOCAL_MODULE := host-framework-protos -LOCAL_MODULE_TAGS := optional - -LOCAL_PROTOC_OPTIMIZE_TYPE := nano -LOCAL_SRC_FILES:= $(call all-proto-files-under, src) - -LOCAL_NO_STANDARD_LIBRARIES := true -LOCAL_STATIC_JAVA_LIBRARIES := host-libprotobuf-java-nano - -LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk $(LOCAL_PATH)/jarjar-rules.txt - -include $(BUILD_HOST_JAVA_LIBRARY)
\ No newline at end of file diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto index 8e8835959084..93aa5207a9ec 100644 --- a/proto/src/metrics_constants.proto +++ b/proto/src/metrics_constants.proto @@ -4562,6 +4562,11 @@ message MetricsEvent { // OS: O MR NOTIFICATION_SNOOZE_OPTIONS = 1142; + // OPEN: Settings > Display > Colors + // CATEGORY: SETTINGS + // OS: O MR + COLOR_MODE_SETTINGS = 1143; + // ---- End O-MR1 Constants, all O-MR1 constants go above this line ---- // OPEN: Settings > Network & Internet > Mobile network @@ -4699,6 +4704,11 @@ message MetricsEvent { // OS: P AUTOFILL_SAVE_EXPLICITLY_TRIGGERED = 1229; + // OPEN: Settings > Network & Internet > Mobile network > Wi-Fi calling + // CATEGORY: SETTINGS + // OS: P + WIFI_CALLING_FOR_SUB = 1230; + // Add new aosp constants above this line. // END OF AOSP CONSTANTS } diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java index 76e77825b227..6c154389526f 100644 --- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java +++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java @@ -107,8 +107,6 @@ import com.android.server.LocalServices; import com.android.server.WidgetBackupProvider; import com.android.server.policy.IconUtilities; -import libcore.io.IoUtils; - import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; @@ -174,21 +172,27 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku Slog.i(TAG, "Received broadcast: " + action + " on user " + userId); } - if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) { - onConfigurationChanged(); - } else if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action) - || Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action)) { - synchronized (mLock) { - reloadWidgetsMaskedState(userId); - } - } else if (Intent.ACTION_PACKAGES_SUSPENDED.equals(action)) { - String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); - updateWidgetPackageSuspensionMaskedState(packages, true, getSendingUserId()); - } else if (Intent.ACTION_PACKAGES_UNSUSPENDED.equals(action)) { - String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); - updateWidgetPackageSuspensionMaskedState(packages, false, getSendingUserId()); - } else { - onPackageBroadcastReceived(intent, userId); + switch (action) { + case Intent.ACTION_CONFIGURATION_CHANGED: + onConfigurationChanged(); + break; + case Intent.ACTION_MANAGED_PROFILE_AVAILABLE: + case Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE: + synchronized (mLock) { + reloadWidgetsMaskedState(userId); + } + break; + case Intent.ACTION_PACKAGES_SUSPENDED: + onPackageBroadcastReceived(intent, getSendingUserId()); + updateWidgetPackageSuspensionMaskedState(intent, true, getSendingUserId()); + break; + case Intent.ACTION_PACKAGES_UNSUSPENDED: + onPackageBroadcastReceived(intent, getSendingUserId()); + updateWidgetPackageSuspensionMaskedState(intent, false, getSendingUserId()); + break; + default: + onPackageBroadcastReceived(intent, getSendingUserId()); + break; } } }; @@ -378,25 +382,32 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku boolean changed = false; boolean componentsModified = false; - String pkgList[] = null; - if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) { - pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); - added = true; - } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) { - pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); - added = false; - } else { - Uri uri = intent.getData(); - if (uri == null) { - return; - } - String pkgName = uri.getSchemeSpecificPart(); - if (pkgName == null) { - return; + final String pkgList[]; + switch (action) { + case Intent.ACTION_PACKAGES_SUSPENDED: + case Intent.ACTION_PACKAGES_UNSUSPENDED: + pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); + changed = true; + break; + case Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE: + added = true; + // Follow through + case Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE: + pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); + break; + default: { + Uri uri = intent.getData(); + if (uri == null) { + return; + } + String pkgName = uri.getSchemeSpecificPart(); + if (pkgName == null) { + return; + } + pkgList = new String[] { pkgName }; + added = Intent.ACTION_PACKAGE_ADDED.equals(action); + changed = Intent.ACTION_PACKAGE_CHANGED.equals(action); } - pkgList = new String[] { pkgName }; - added = Intent.ACTION_PACKAGE_ADDED.equals(action); - changed = Intent.ACTION_PACKAGE_CHANGED.equals(action); } if (pkgList == null || pkgList.length == 0) { return; @@ -516,12 +527,13 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku /** * Incrementally update the masked state due to package suspension state. */ - private void updateWidgetPackageSuspensionMaskedState(String[] packagesArray, boolean suspended, + private void updateWidgetPackageSuspensionMaskedState(Intent intent, boolean suspended, int profileId) { + String[] packagesArray = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); if (packagesArray == null) { return; } - Set<String> packages = new ArraySet<String>(Arrays.asList(packagesArray)); + Set<String> packages = new ArraySet<>(Arrays.asList(packagesArray)); synchronized (mLock) { final int N = mProviders.size(); for (int i = 0; i < N; i++) { @@ -2630,11 +2642,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku // No file written for this user - nothing to do. AtomicFile file = getSavedStateFile(profileId); - try { - FileInputStream stream = file.openRead(); + try (FileInputStream stream = file.openRead()) { version = readProfileStateFromFileLocked(stream, profileId, loadedWidgets); - IoUtils.closeQuietly(stream); - } catch (FileNotFoundException e) { + } catch (IOException e) { Slog.w(TAG, "Failed to read state: " + e); } } diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java index f9213aabe272..622b84239711 100644 --- a/services/backup/java/com/android/server/backup/BackupManagerService.java +++ b/services/backup/java/com/android/server/backup/BackupManagerService.java @@ -2877,7 +2877,7 @@ public class BackupManagerService implements BackupManagerServiceInterface { // The backend reports that our dataset has been wiped. Note this in // the event log; the no-success code below will reset the backup // state as well. - EventLog.writeEvent(EventLogTags.BACKUP_RESET, mTransport.transportDirName()); + EventLog.writeEvent(EventLogTags.BACKUP_RESET, transportName); } } catch (Exception e) { Slog.e(TAG, "Error in backup thread", e); @@ -9781,7 +9781,8 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF } Slog.i(TAG, "Initializing (wiping) backup transport storage: " + transportName); - EventLog.writeEvent(EventLogTags.BACKUP_START, transport.transportDirName()); + String transportDirName = transport.transportDirName(); + EventLog.writeEvent(EventLogTags.BACKUP_START, transportDirName); long startRealtime = SystemClock.elapsedRealtime(); int status = transport.initializeDevice(); @@ -9794,7 +9795,7 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF Slog.i(TAG, "Device init successful"); int millis = (int) (SystemClock.elapsedRealtime() - startRealtime); EventLog.writeEvent(EventLogTags.BACKUP_INITIALIZE); - resetBackupState(new File(mBaseStateDir, transport.transportDirName())); + resetBackupState(new File(mBaseStateDir, transportDirName)); EventLog.writeEvent(EventLogTags.BACKUP_SUCCESS, 0, millis); synchronized (mQueueLock) { recordInitPendingLocked(false, transportName); diff --git a/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java b/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java index 7a8a920e7df7..c0caa557b4ae 100644 --- a/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java +++ b/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java @@ -339,7 +339,7 @@ public class PerformBackupTask implements BackupRestoreTask { // The backend reports that our dataset has been wiped. Note this in // the event log; the no-success code below will reset the backup // state as well. - EventLog.writeEvent(EventLogTags.BACKUP_RESET, mTransport.transportDirName()); + EventLog.writeEvent(EventLogTags.BACKUP_RESET, transportName); } } catch (Exception e) { Slog.e(TAG, "Error in backup thread", e); diff --git a/services/backup/java/com/android/server/backup/internal/PerformInitializeTask.java b/services/backup/java/com/android/server/backup/internal/PerformInitializeTask.java index 939b1ae11b95..690922fd9aa9 100644 --- a/services/backup/java/com/android/server/backup/internal/PerformInitializeTask.java +++ b/services/backup/java/com/android/server/backup/internal/PerformInitializeTask.java @@ -79,7 +79,8 @@ public class PerformInitializeTask implements Runnable { } Slog.i(TAG, "Initializing (wiping) backup transport storage: " + transportName); - EventLog.writeEvent(EventLogTags.BACKUP_START, transport.transportDirName()); + String transportDirName = transport.transportDirName(); + EventLog.writeEvent(EventLogTags.BACKUP_START, transportDirName); long startRealtime = SystemClock.elapsedRealtime(); int status = transport.initializeDevice(); @@ -94,7 +95,7 @@ public class PerformInitializeTask implements Runnable { EventLog.writeEvent(EventLogTags.BACKUP_INITIALIZE); backupManagerService .resetBackupState(new File(backupManagerService.getBaseStateDir(), - transport.transportDirName())); + transportDirName)); EventLog.writeEvent(EventLogTags.BACKUP_SUCCESS, 0, millis); synchronized (backupManagerService.getQueueLock()) { backupManagerService.recordInitPendingLocked(false, transportName); diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java index 4c08f6290c6c..3904fc96caca 100644 --- a/services/core/java/com/android/server/AlarmManagerService.java +++ b/services/core/java/com/android/server/AlarmManagerService.java @@ -63,6 +63,7 @@ import android.util.SparseArray; import android.util.SparseBooleanArray; import android.util.SparseLongArray; import android.util.TimeUtils; +import android.util.proto.ProtoOutputStream; import java.io.ByteArrayOutputStream; import java.io.FileDescriptor; @@ -190,7 +191,8 @@ class AlarmManagerService extends SystemService { /** * For each uid, this is the last time we dispatched an "allow while idle" alarm, - * used to determine the earliest we can dispatch the next such alarm. + * used to determine the earliest we can dispatch the next such alarm. Times are in the + * 'elapsed' timebase. */ final SparseLongArray mLastAllowWhileIdleDispatch = new SparseLongArray(); @@ -355,6 +357,22 @@ class AlarmManagerService extends SystemService { TimeUtils.formatDuration(ALLOW_WHILE_IDLE_WHITELIST_DURATION, pw); pw.println(); } + + void dumpProto(ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + + proto.write(ConstantsProto.MIN_FUTURITY_DURATION_MS, MIN_FUTURITY); + proto.write(ConstantsProto.MIN_INTERVAL_DURATION_MS, MIN_INTERVAL); + proto.write(ConstantsProto.LISTENER_TIMEOUT_DURATION_MS, LISTENER_TIMEOUT); + proto.write(ConstantsProto.ALLOW_WHILE_IDLE_SHORT_DURATION_MS, + ALLOW_WHILE_IDLE_SHORT_TIME); + proto.write(ConstantsProto.ALLOW_WHILE_IDLE_LONG_DURATION_MS, + ALLOW_WHILE_IDLE_LONG_TIME); + proto.write(ConstantsProto.ALLOW_WHILE_IDLE_WHITELIST_DURATION_MS, + ALLOW_WHILE_IDLE_WHITELIST_DURATION); + + proto.end(token); + } } final Constants mConstants; @@ -632,6 +650,20 @@ class AlarmManagerService extends SystemService { b.append('}'); return b.toString(); } + + public void writeToProto(ProtoOutputStream proto, long fieldId, long nowElapsed, + long nowRTC) { + final long token = proto.start(fieldId); + + proto.write(BatchProto.START_REALTIME, start); + proto.write(BatchProto.END_REALTIME, end); + proto.write(BatchProto.FLAGS, flags); + for (Alarm a : alarms) { + a.writeToProto(proto, BatchProto.ALARMS, nowElapsed, nowRTC); + } + + proto.end(token); + } } static class BatchTimeOrder implements Comparator<Batch> { @@ -1007,6 +1039,29 @@ class AlarmManagerService extends SystemService { + ", alarmType=" + mAlarmType + "}"; } + + public void writeToProto(ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + + proto.write(InFlightProto.UID, mUid); + proto.write(InFlightProto.TAG, mTag); + proto.write(InFlightProto.WHEN_ELAPSED_MS, mWhenElapsed); + proto.write(InFlightProto.ALARM_TYPE, mAlarmType); + if (mPendingIntent != null) { + mPendingIntent.writeToProto(proto, InFlightProto.PENDING_INTENT); + } + if (mBroadcastStats != null) { + mBroadcastStats.writeToProto(proto, InFlightProto.BROADCAST_STATS); + } + if (mFilterStats != null) { + mFilterStats.writeToProto(proto, InFlightProto.FILTER_STATS); + } + if (mWorkSource != null) { + mWorkSource.writeToProto(proto, InFlightProto.WORK_SOURCE); + } + + proto.end(token); + } } static final class FilterStats { @@ -1037,6 +1092,20 @@ class AlarmManagerService extends SystemService { + ", nesting=" + nesting + "}"; } + + public void writeToProto(ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + + proto.write(FilterStatsProto.TAG, mTag); + proto.write(FilterStatsProto.LAST_FLIGHT_TIME_REALTIME, lastTime); + proto.write(FilterStatsProto.TOTAL_FLIGHT_DURATION_MS, aggregateTime); + proto.write(FilterStatsProto.COUNT, count); + proto.write(FilterStatsProto.WAKEUP_COUNT, numWakeup); + proto.write(FilterStatsProto.START_TIME_REALTIME, startTime); + proto.write(FilterStatsProto.NESTING, nesting); + + proto.end(token); + } } static final class BroadcastStats { @@ -1067,6 +1136,20 @@ class AlarmManagerService extends SystemService { + ", nesting=" + nesting + "}"; } + + public void writeToProto(ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + + proto.write(BroadcastStatsProto.UID, mUid); + proto.write(BroadcastStatsProto.PACKAGE_NAME, mPackageName); + proto.write(BroadcastStatsProto.TOTAL_FLIGHT_DURATION_MS, aggregateTime); + proto.write(BroadcastStatsProto.COUNT, count); + proto.write(BroadcastStatsProto.WAKEUP_COUNT, numWakeup); + proto.write(BroadcastStatsProto.START_TIME_REALTIME, startTime); + proto.write(BroadcastStatsProto.NESTING, nesting); + + proto.end(token); + } } final SparseArray<ArrayMap<String, BroadcastStats>> mBroadcastStats @@ -1128,14 +1211,14 @@ class AlarmManagerService extends SystemService { | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS); mDateChangeSender = PendingIntent.getBroadcastAsUser(getContext(), 0, intent, Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT, UserHandle.ALL); - + // now that we have initied the driver schedule the alarm mClockReceiver = new ClockReceiver(); mClockReceiver.scheduleTimeTickEvent(); mClockReceiver.scheduleDateChangedEvent(); mInteractiveStateReceiver = new InteractiveStateReceiver(); mUninstallReceiver = new UninstallReceiver(); - + if (mNativeData != 0) { AlarmThread waitThread = new AlarmThread(); waitThread.start(); @@ -1568,7 +1651,12 @@ class AlarmManagerService extends SystemService { @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpAndUsageStatsPermission(getContext(), TAG, pw)) return; - dumpImpl(pw); + + if (args.length > 0 && "--proto".equals(args[0])) { + dumpProto(fd); + } else { + dumpImpl(pw); + } } }; @@ -1681,7 +1769,7 @@ class AlarmManagerService extends SystemService { pw.print(" Idling until: "); if (mPendingIdleUntil != null) { pw.println(mPendingIdleUntil); - mPendingIdleUntil.dump(pw, " ", nowRTC, nowELAPSED, sdf); + mPendingIdleUntil.dump(pw, " ", nowELAPSED, nowRTC, sdf); } else { pw.println("null"); } @@ -1691,7 +1779,7 @@ class AlarmManagerService extends SystemService { if (mNextWakeFromIdle != null) { pw.println(); pw.print(" Next wake from idle: "); pw.println(mNextWakeFromIdle); - mNextWakeFromIdle.dump(pw, " ", nowRTC, nowELAPSED, sdf); + mNextWakeFromIdle.dump(pw, " ", nowELAPSED, nowRTC, sdf); } pw.println(); @@ -1760,6 +1848,7 @@ class AlarmManagerService extends SystemService { } }; int len = 0; + // Get the top 10 FilterStats, ordered by aggregateTime. for (int iu=0; iu<mBroadcastStats.size(); iu++) { ArrayMap<String, BroadcastStats> uidStats = mBroadcastStats.valueAt(iu); for (int ip=0; ip<uidStats.size(); ip++) { @@ -1886,6 +1975,244 @@ class AlarmManagerService extends SystemService { } } + void dumpProto(FileDescriptor fd) { + final ProtoOutputStream proto = new ProtoOutputStream(fd); + + synchronized (mLock) { + final long nowRTC = System.currentTimeMillis(); + final long nowElapsed = SystemClock.elapsedRealtime(); + proto.write(AlarmManagerServiceProto.CURRENT_TIME, nowRTC); + proto.write(AlarmManagerServiceProto.ELAPSED_REALTIME, nowElapsed); + proto.write(AlarmManagerServiceProto.LAST_TIME_CHANGE_CLOCK_TIME, + mLastTimeChangeClockTime); + proto.write(AlarmManagerServiceProto.LAST_TIME_CHANGE_REALTIME, + mLastTimeChangeRealtime); + + mConstants.dumpProto(proto, AlarmManagerServiceProto.SETTINGS); + + final int foregroundUidsSize = mForegroundUids.size(); + for (int i = 0; i < foregroundUidsSize; i++) { + if (mForegroundUids.valueAt(i)) { + proto.write(AlarmManagerServiceProto.FOREGROUND_UIDS, mForegroundUids.keyAt(i)); + } + } + for (String pkg : mForcedAppStandbyPackages) { + proto.write(AlarmManagerServiceProto.FORCED_APP_STANDBY_PACKAGES, pkg); + } + + proto.write(AlarmManagerServiceProto.IS_INTERACTIVE, mInteractive); + if (!mInteractive) { + // Durations + proto.write(AlarmManagerServiceProto.TIME_SINCE_NON_INTERACTIVE_MS, + nowElapsed - mNonInteractiveStartTime); + proto.write(AlarmManagerServiceProto.MAX_WAKEUP_DELAY_MS, + currentNonWakeupFuzzLocked(nowElapsed)); + proto.write(AlarmManagerServiceProto.TIME_SINCE_LAST_DISPATCH_MS, + nowElapsed - mLastAlarmDeliveryTime); + proto.write(AlarmManagerServiceProto.TIME_UNTIL_NEXT_NON_WAKEUP_DELIVERY_MS, + nowElapsed - mNextNonWakeupDeliveryTime); + } + + proto.write(AlarmManagerServiceProto.TIME_UNTIL_NEXT_NON_WAKEUP_ALARM_MS, + mNextNonWakeup - nowElapsed); + proto.write(AlarmManagerServiceProto.TIME_UNTIL_NEXT_WAKEUP_MS, + mNextWakeup - nowElapsed); + proto.write(AlarmManagerServiceProto.TIME_SINCE_LAST_WAKEUP_MS, + nowElapsed - mLastWakeup); + proto.write(AlarmManagerServiceProto.TIME_SINCE_LAST_WAKEUP_SET_MS, + nowElapsed - mLastWakeupSet); + proto.write(AlarmManagerServiceProto.TIME_CHANGE_EVENT_COUNT, mNumTimeChanged); + for (int i : mDeviceIdleUserWhitelist) { + proto.write(AlarmManagerServiceProto.DEVICE_IDLE_USER_WHITELIST_APP_IDS, i); + } + + final TreeSet<Integer> users = new TreeSet<>(); + final int nextAlarmClockForUserSize = mNextAlarmClockForUser.size(); + for (int i = 0; i < nextAlarmClockForUserSize; i++) { + users.add(mNextAlarmClockForUser.keyAt(i)); + } + final int pendingSendNextAlarmClockChangedForUserSize = + mPendingSendNextAlarmClockChangedForUser.size(); + for (int i = 0; i < pendingSendNextAlarmClockChangedForUserSize; i++) { + users.add(mPendingSendNextAlarmClockChangedForUser.keyAt(i)); + } + for (int user : users) { + final AlarmManager.AlarmClockInfo next = mNextAlarmClockForUser.get(user); + final long time = next != null ? next.getTriggerTime() : 0; + final boolean pendingSend = mPendingSendNextAlarmClockChangedForUser.get(user); + final long aToken = proto.start(AlarmManagerServiceProto.NEXT_ALARM_CLOCK_METADATA); + proto.write(AlarmClockMetadataProto.USER, user); + proto.write(AlarmClockMetadataProto.IS_PENDING_SEND, pendingSend); + proto.write(AlarmClockMetadataProto.TRIGGER_TIME_MS, time); + proto.end(aToken); + } + for (Batch b : mAlarmBatches) { + b.writeToProto(proto, AlarmManagerServiceProto.PENDING_ALARM_BATCHES, + nowElapsed, nowRTC); + } + for (int i = 0; i < mPendingBackgroundAlarms.size(); i++) { + final ArrayList<Alarm> blockedAlarms = mPendingBackgroundAlarms.valueAt(i); + if (blockedAlarms != null) { + for (Alarm a : blockedAlarms) { + a.writeToProto(proto, + AlarmManagerServiceProto.PENDING_USER_BLOCKED_BACKGROUND_ALARMS, + nowElapsed, nowRTC); + } + } + } + if (mPendingIdleUntil != null) { + mPendingIdleUntil.writeToProto( + proto, AlarmManagerServiceProto.PENDING_IDLE_UNTIL, nowElapsed, nowRTC); + } + for (Alarm a : mPendingWhileIdleAlarms) { + a.writeToProto(proto, AlarmManagerServiceProto.PENDING_WHILE_IDLE_ALARMS, + nowElapsed, nowRTC); + } + if (mNextWakeFromIdle != null) { + mNextWakeFromIdle.writeToProto(proto, AlarmManagerServiceProto.NEXT_WAKE_FROM_IDLE, + nowElapsed, nowRTC); + } + + for (Alarm a : mPendingNonWakeupAlarms) { + a.writeToProto(proto, AlarmManagerServiceProto.PAST_DUE_NON_WAKEUP_ALARMS, + nowElapsed, nowRTC); + } + + proto.write(AlarmManagerServiceProto.DELAYED_ALARM_COUNT, mNumDelayedAlarms); + proto.write(AlarmManagerServiceProto.TOTAL_DELAY_TIME_MS, mTotalDelayTime); + proto.write(AlarmManagerServiceProto.MAX_DELAY_DURATION_MS, mMaxDelayTime); + proto.write(AlarmManagerServiceProto.MAX_NON_INTERACTIVE_DURATION_MS, + mNonInteractiveTime); + + proto.write(AlarmManagerServiceProto.BROADCAST_REF_COUNT, mBroadcastRefCount); + proto.write(AlarmManagerServiceProto.PENDING_INTENT_SEND_COUNT, mSendCount); + proto.write(AlarmManagerServiceProto.PENDING_INTENT_FINISH_COUNT, mSendFinishCount); + proto.write(AlarmManagerServiceProto.LISTENER_SEND_COUNT, mListenerCount); + proto.write(AlarmManagerServiceProto.LISTENER_FINISH_COUNT, mListenerFinishCount); + + for (InFlight f : mInFlight) { + f.writeToProto(proto, AlarmManagerServiceProto.OUTSTANDING_DELIVERIES); + } + + proto.write(AlarmManagerServiceProto.ALLOW_WHILE_IDLE_MIN_DURATION_MS, + mAllowWhileIdleMinTime); + for (int i = 0; i < mLastAllowWhileIdleDispatch.size(); ++i) { + final long token = proto.start( + AlarmManagerServiceProto.LAST_ALLOW_WHILE_IDLE_DISPATCH_TIMES); + proto.write(AlarmManagerServiceProto.LastAllowWhileIdleDispatch.UID, + mLastAllowWhileIdleDispatch.keyAt(i)); + proto.write(AlarmManagerServiceProto.LastAllowWhileIdleDispatch.TIME_MS, + mLastAllowWhileIdleDispatch.valueAt(i)); + proto.end(token); + } + + mLog.writeToProto(proto, AlarmManagerServiceProto.RECENT_PROBLEMS); + + final FilterStats[] topFilters = new FilterStats[10]; + final Comparator<FilterStats> comparator = new Comparator<FilterStats>() { + @Override + public int compare(FilterStats lhs, FilterStats rhs) { + if (lhs.aggregateTime < rhs.aggregateTime) { + return 1; + } else if (lhs.aggregateTime > rhs.aggregateTime) { + return -1; + } + return 0; + } + }; + int len = 0; + // Get the top 10 FilterStats, ordered by aggregateTime. + for (int iu = 0; iu < mBroadcastStats.size(); ++iu) { + ArrayMap<String, BroadcastStats> uidStats = mBroadcastStats.valueAt(iu); + for (int ip = 0; ip < uidStats.size(); ++ip) { + BroadcastStats bs = uidStats.valueAt(ip); + for (int is = 0; is < bs.filterStats.size(); ++is) { + FilterStats fs = bs.filterStats.valueAt(is); + int pos = len > 0 + ? Arrays.binarySearch(topFilters, 0, len, fs, comparator) : 0; + if (pos < 0) { + pos = -pos - 1; + } + if (pos < topFilters.length) { + int copylen = topFilters.length - pos - 1; + if (copylen > 0) { + System.arraycopy(topFilters, pos, topFilters, pos+1, copylen); + } + topFilters[pos] = fs; + if (len < topFilters.length) { + len++; + } + } + } + } + } + for (int i = 0; i < len; ++i) { + final long token = proto.start(AlarmManagerServiceProto.TOP_ALARMS); + FilterStats fs = topFilters[i]; + + proto.write(AlarmManagerServiceProto.TopAlarm.UID, fs.mBroadcastStats.mUid); + proto.write(AlarmManagerServiceProto.TopAlarm.PACKAGE_NAME, + fs.mBroadcastStats.mPackageName); + fs.writeToProto(proto, AlarmManagerServiceProto.TopAlarm.FILTER); + + proto.end(token); + } + + final ArrayList<FilterStats> tmpFilters = new ArrayList<FilterStats>(); + for (int iu = 0; iu < mBroadcastStats.size(); ++iu) { + ArrayMap<String, BroadcastStats> uidStats = mBroadcastStats.valueAt(iu); + for (int ip = 0; ip < uidStats.size(); ++ip) { + final long token = proto.start(AlarmManagerServiceProto.ALARM_STATS); + + BroadcastStats bs = uidStats.valueAt(ip); + bs.writeToProto(proto, AlarmManagerServiceProto.AlarmStat.BROADCAST); + + // uidStats is an ArrayMap, which we can't sort. + tmpFilters.clear(); + for (int is = 0; is < bs.filterStats.size(); ++is) { + tmpFilters.add(bs.filterStats.valueAt(is)); + } + Collections.sort(tmpFilters, comparator); + for (FilterStats fs : tmpFilters) { + fs.writeToProto(proto, AlarmManagerServiceProto.AlarmStat.FILTERS); + } + + proto.end(token); + } + } + + if (RECORD_DEVICE_IDLE_ALARMS) { + for (int i = 0; i < mAllowWhileIdleDispatches.size(); i++) { + IdleDispatchEntry ent = mAllowWhileIdleDispatches.get(i); + final long token = proto.start( + AlarmManagerServiceProto.ALLOW_WHILE_IDLE_DISPATCHES); + + proto.write(IdleDispatchEntryProto.UID, ent.uid); + proto.write(IdleDispatchEntryProto.PKG, ent.pkg); + proto.write(IdleDispatchEntryProto.TAG, ent.tag); + proto.write(IdleDispatchEntryProto.OP, ent.op); + proto.write(IdleDispatchEntryProto.ENTRY_CREATION_REALTIME, + ent.elapsedRealtime); + proto.write(IdleDispatchEntryProto.ARG_REALTIME, ent.argRealtime); + + proto.end(token); + } + } + + if (WAKEUP_STATS) { + for (WakeupEvent event : mRecentWakeups) { + final long token = proto.start(AlarmManagerServiceProto.RECENT_WAKEUP_HISTORY); + proto.write(WakeupEventProto.UID, event.uid); + proto.write(WakeupEventProto.ACTION, event.action); + proto.write(WakeupEventProto.WHEN, event.when); + proto.end(token); + } + } + } + + proto.flush(); + } + private void logBatchesLocked(SimpleDateFormat sdf) { ByteArrayOutputStream bs = new ByteArrayOutputStream(2048); PrintWriter pw = new PrintWriter(bs); @@ -2328,24 +2655,24 @@ class AlarmManagerService extends SystemService { alarmSeconds = when / 1000; alarmNanoseconds = (when % 1000) * 1000 * 1000; } - + set(mNativeData, type, alarmSeconds, alarmNanoseconds); } else { Message msg = Message.obtain(); msg.what = ALARM_EVENT; - + mHandler.removeMessages(ALARM_EVENT); mHandler.sendMessageAtTime(msg, when); } } private static final void dumpAlarmList(PrintWriter pw, ArrayList<Alarm> list, - String prefix, String label, long nowRTC, long nowELAPSED, SimpleDateFormat sdf) { + String prefix, String label, long nowELAPSED, long nowRTC, SimpleDateFormat sdf) { for (int i=list.size()-1; i>=0; i--) { Alarm a = list.get(i); pw.print(prefix); pw.print(label); pw.print(" #"); pw.print(i); pw.print(": "); pw.println(a); - a.dump(pw, prefix + " ", nowRTC, nowELAPSED, sdf); + a.dump(pw, prefix + " ", nowELAPSED, nowRTC, sdf); } } @@ -2355,8 +2682,6 @@ class AlarmManagerService extends SystemService { case RTC_WAKEUP : return "RTC_WAKEUP"; case ELAPSED_REALTIME : return "ELAPSED"; case ELAPSED_REALTIME_WAKEUP: return "ELAPSED_WAKEUP"; - default: - break; } return "--unknown--"; } @@ -2368,7 +2693,7 @@ class AlarmManagerService extends SystemService { final String label = labelForType(a.type); pw.print(prefix); pw.print(label); pw.print(" #"); pw.print(i); pw.print(": "); pw.println(a); - a.dump(pw, prefix + " ", nowRTC, nowELAPSED, sdf); + a.dump(pw, prefix + " ", nowELAPSED, nowRTC, sdf); } } @@ -2533,7 +2858,7 @@ class AlarmManagerService extends SystemService { return 0; } } - + private static class Alarm { public final int type; public final long origWhen; @@ -2627,7 +2952,7 @@ class AlarmManagerService extends SystemService { return sb.toString(); } - public void dump(PrintWriter pw, String prefix, long nowRTC, long nowELAPSED, + public void dump(PrintWriter pw, String prefix, long nowELAPSED, long nowRTC, SimpleDateFormat sdf) { final boolean isRtc = (type == RTC || type == RTC_WAKEUP); pw.print(prefix); pw.print("tag="); pw.println(statsTag); @@ -2656,6 +2981,30 @@ class AlarmManagerService extends SystemService { pw.print(prefix); pw.print("listener="); pw.println(listener.asBinder()); } } + + public void writeToProto(ProtoOutputStream proto, long fieldId, long nowElapsed, + long nowRTC) { + final long token = proto.start(fieldId); + + proto.write(AlarmProto.TAG, statsTag); + proto.write(AlarmProto.TYPE, type); + proto.write(AlarmProto.WHEN_ELAPSED_MS, whenElapsed - nowElapsed); + proto.write(AlarmProto.WINDOW_LENGTH_MS, windowLength); + proto.write(AlarmProto.REPEAT_INTERVAL_MS, repeatInterval); + proto.write(AlarmProto.COUNT, count); + proto.write(AlarmProto.FLAGS, flags); + if (alarmClock != null) { + alarmClock.writeToProto(proto, AlarmProto.ALARM_CLOCK); + } + if (operation != null) { + operation.writeToProto(proto, AlarmProto.OPERATION); + } + if (listener != null) { + proto.write(AlarmProto.LISTENER, listener.asBinder().toString()); + } + + proto.end(token); + } } void recordWakeupAlarms(ArrayList<Batch> batches, long nowELAPSED, long nowRTC) { @@ -2752,7 +3101,7 @@ class AlarmManagerService extends SystemService { { super("AlarmManager"); } - + public void run() { ArrayList<Alarm> triggerList = new ArrayList<Alarm>(); @@ -2918,10 +3267,10 @@ class AlarmManagerService extends SystemService { public static final int SEND_NEXT_ALARM_CLOCK_CHANGED = 2; public static final int LISTENER_TIMEOUT = 3; public static final int REPORT_ALARMS_ACTIVE = 4; - + public AlarmHandler() { } - + public void handleMessage(Message msg) { switch (msg.what) { case ALARM_EVENT: { @@ -2969,7 +3318,7 @@ class AlarmManagerService extends SystemService { } } } - + class ClockReceiver extends BroadcastReceiver { public ClockReceiver() { IntentFilter filter = new IntentFilter(); @@ -2977,7 +3326,7 @@ class AlarmManagerService extends SystemService { filter.addAction(Intent.ACTION_DATE_CHANGED); getContext().registerReceiver(this, filter); } - + @Override public void onReceive(Context context, Intent intent) { if (intent.getAction().equals(Intent.ACTION_TIME_TICK)) { @@ -2996,7 +3345,7 @@ class AlarmManagerService extends SystemService { scheduleDateChangedEvent(); } } - + public void scheduleTimeTickEvent() { final long currentTime = System.currentTimeMillis(); final long nextTime = 60000 * ((currentTime / 60000) + 1); @@ -3026,7 +3375,7 @@ class AlarmManagerService extends SystemService { Process.myUid(), "android"); } } - + class InteractiveStateReceiver extends BroadcastReceiver { public InteractiveStateReceiver() { IntentFilter filter = new IntentFilter(); @@ -3059,7 +3408,7 @@ class AlarmManagerService extends SystemService { sdFilter.addAction(Intent.ACTION_UID_REMOVED); getContext().registerReceiver(this, sdFilter); } - + @Override public void onReceive(Context context, Intent intent) { synchronized (mLock) { diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java index 46b671bd1419..b824fab7a835 100644 --- a/services/core/java/com/android/server/BatteryService.java +++ b/services/core/java/com/android/server/BatteryService.java @@ -43,6 +43,7 @@ import android.hardware.health.V2_0.IHealthInfoCallback; import android.hardware.health.V2_0.IHealth; import android.hardware.health.V2_0.Result; import android.os.BatteryManager; +import android.os.BatteryManagerProto; import android.os.BatteryManagerInternal; import android.os.BatteryProperty; import android.os.Binder; @@ -913,13 +914,13 @@ public final class BatteryService extends SystemService { synchronized (mLock) { proto.write(BatteryServiceDumpProto.ARE_UPDATES_STOPPED, mUpdatesStopped); - int batteryPluggedValue = BatteryServiceDumpProto.BATTERY_PLUGGED_NONE; + int batteryPluggedValue = BatteryManagerProto.PLUG_TYPE_NONE; if (mHealthInfo.legacy.chargerAcOnline) { - batteryPluggedValue = BatteryServiceDumpProto.BATTERY_PLUGGED_AC; + batteryPluggedValue = BatteryManagerProto.PLUG_TYPE_AC; } else if (mHealthInfo.legacy.chargerUsbOnline) { - batteryPluggedValue = BatteryServiceDumpProto.BATTERY_PLUGGED_USB; + batteryPluggedValue = BatteryManagerProto.PLUG_TYPE_USB; } else if (mHealthInfo.legacy.chargerWirelessOnline) { - batteryPluggedValue = BatteryServiceDumpProto.BATTERY_PLUGGED_WIRELESS; + batteryPluggedValue = BatteryManagerProto.PLUG_TYPE_WIRELESS; } proto.write(BatteryServiceDumpProto.PLUGGED, batteryPluggedValue); proto.write(BatteryServiceDumpProto.MAX_CHARGING_CURRENT, mHealthInfo.legacy.maxChargingCurrent); @@ -1060,6 +1061,7 @@ public final class BatteryService extends SystemService { } public int getProperty(int id, final BatteryProperty prop) throws RemoteException { IHealth service = mHealthServiceWrapper.getLastService(); + if (service == null) throw new RemoteException("no health service"); final MutableInt outResult = new MutableInt(Result.NOT_SUPPORTED); switch(id) { case BatteryManager.BATTERY_PROPERTY_CHARGE_COUNTER: @@ -1101,8 +1103,10 @@ public final class BatteryService extends SystemService { } return outResult.value; } - public void scheduleUpdate() { - Slog.e(TAG, "health: must not call scheduleUpdate on battery properties"); + public void scheduleUpdate() throws RemoteException { + IHealth service = mHealthServiceWrapper.getLastService(); + if (service == null) throw new RemoteException("no health service"); + service.update(); } } diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 348c79979685..d819a33d9207 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -70,7 +70,6 @@ import android.net.ProxyInfo; import android.net.RouteInfo; import android.net.UidRange; import android.net.Uri; -import android.net.metrics.DefaultNetworkEvent; import android.net.metrics.IpConnectivityLog; import android.net.metrics.NetworkEvent; import android.net.util.MultinetworkPolicyTracker; @@ -127,6 +126,7 @@ import com.android.internal.util.WakeupMessage; import com.android.internal.util.XmlUtils; import com.android.server.am.BatteryStatsService; import com.android.server.connectivity.DataConnectionStats; +import com.android.server.connectivity.IpConnectivityMetrics; import com.android.server.connectivity.KeepaliveTracker; import com.android.server.connectivity.LingerMonitor; import com.android.server.connectivity.MockableSystemProperties; @@ -2265,7 +2265,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // Let rematchAllNetworksAndRequests() below record a new default network event // if there is a fallback. Taken together, the two form a X -> 0, 0 -> Y sequence // whose timestamps tell how long it takes to recover a default network. - logDefaultNetworkEvent(null, nai); + metricsLogger().defaultNetworkMetrics().logDefaultNetworkEvent(null, nai); } notifyIfacesChangedForNetworkStats(); // TODO - we shouldn't send CALLBACK_LOST to requests that can be satisfied @@ -4995,7 +4995,8 @@ public class ConnectivityService extends IConnectivityManager.Stub // Notify system services that this network is up. makeDefault(newNetwork); // Log 0 -> X and Y -> X default network transitions, where X is the new default. - logDefaultNetworkEvent(newNetwork, oldDefaultNetwork); + metricsLogger().defaultNetworkMetrics().logDefaultNetworkEvent( + newNetwork, oldDefaultNetwork); // Have a new default network, release the transition wakelock in scheduleReleaseNetworkTransitionWakelock(); } @@ -5554,25 +5555,10 @@ public class ConnectivityService extends IConnectivityManager.Stub return ServiceManager.checkService(name) != null; } - private void logDefaultNetworkEvent(NetworkAgentInfo newNai, NetworkAgentInfo prevNai) { - int newNetid = NETID_UNSET; - int prevNetid = NETID_UNSET; - int[] transports = new int[0]; - boolean hadIPv4 = false; - boolean hadIPv6 = false; - - if (newNai != null) { - newNetid = newNai.network.netId; - transports = newNai.networkCapabilities.getTransportTypes(); - } - if (prevNai != null) { - prevNetid = prevNai.network.netId; - final LinkProperties lp = prevNai.linkProperties; - hadIPv4 = lp.hasIPv4Address() && lp.hasIPv4DefaultRoute(); - hadIPv6 = lp.hasGlobalIPv6Address() && lp.hasIPv6DefaultRoute(); - } - - mMetricsLog.log(new DefaultNetworkEvent(newNetid, transports, prevNetid, hadIPv4, hadIPv6)); + @VisibleForTesting + protected IpConnectivityMetrics.Logger metricsLogger() { + return checkNotNull(LocalServices.getService(IpConnectivityMetrics.Logger.class), + "no IpConnectivityMetrics service"); } private void logNetworkEvent(NetworkAgentInfo nai, int evtype) { diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java index cf1d33c86173..a139ac4f3f68 100644 --- a/services/core/java/com/android/server/IpSecService.java +++ b/services/core/java/com/android/server/IpSecService.java @@ -754,7 +754,7 @@ public class IpSecService extends IIpSecService.Stub { * and re-binding, during which the system could *technically* hand that port out to someone * else. */ - private void bindToRandomPort(FileDescriptor sockFd) throws IOException { + private int bindToRandomPort(FileDescriptor sockFd) throws IOException { for (int i = MAX_PORT_BIND_ATTEMPTS; i > 0; i--) { try { FileDescriptor probeSocket = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); @@ -763,7 +763,7 @@ public class IpSecService extends IIpSecService.Stub { Os.close(probeSocket); Log.v(TAG, "Binding to port " + port); Os.bind(sockFd, INADDR_ANY, port); - return; + return port; } catch (ErrnoException e) { // Someone miraculously claimed the port just after we closed probeSocket. if (e.errno == OsConstants.EADDRINUSE) { @@ -803,7 +803,7 @@ public class IpSecService extends IIpSecService.Stub { Log.v(TAG, "Binding to port " + port); Os.bind(sockFd, INADDR_ANY, port); } else { - bindToRandomPort(sockFd); + port = bindToRandomPort(sockFd); } // This code is common to both the unspecified and specified port cases Os.setsockoptInt( diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index cd256109e1bd..bfbce408cb28 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -641,8 +641,8 @@ class StorageManagerService extends IStorageManager.Stub implements Watchdog.Mon break; } case H_PARTITION_FORGET: { - final String partGuid = (String) msg.obj; - forgetPartition(partGuid); + final VolumeRecord rec = (VolumeRecord) msg.obj; + forgetPartition(rec.partGuid, rec.fsUuid); break; } case H_RESET: { @@ -1694,7 +1694,7 @@ class StorageManagerService extends IStorageManager.Stub implements Watchdog.Mon synchronized (mLock) { final VolumeRecord rec = mRecords.remove(fsUuid); if (rec != null && !TextUtils.isEmpty(rec.partGuid)) { - mHandler.obtainMessage(H_PARTITION_FORGET, rec.partGuid).sendToTarget(); + mHandler.obtainMessage(H_PARTITION_FORGET, rec).sendToTarget(); } mCallbacks.notifyVolumeForgotten(fsUuid); @@ -1718,7 +1718,7 @@ class StorageManagerService extends IStorageManager.Stub implements Watchdog.Mon final String fsUuid = mRecords.keyAt(i); final VolumeRecord rec = mRecords.valueAt(i); if (!TextUtils.isEmpty(rec.partGuid)) { - mHandler.obtainMessage(H_PARTITION_FORGET, rec.partGuid).sendToTarget(); + mHandler.obtainMessage(H_PARTITION_FORGET, rec).sendToTarget(); } mCallbacks.notifyVolumeForgotten(fsUuid); } @@ -1733,9 +1733,9 @@ class StorageManagerService extends IStorageManager.Stub implements Watchdog.Mon } } - private void forgetPartition(String partGuid) { + private void forgetPartition(String partGuid, String fsUuid) { try { - mVold.forgetPartition(partGuid); + mVold.forgetPartition(partGuid, fsUuid); } catch (Exception e) { Slog.wtf(TAG, e); } diff --git a/services/core/java/com/android/server/am/ActivityDisplay.java b/services/core/java/com/android/server/am/ActivityDisplay.java index aa0231f2cbff..c04ddf8ae6de 100644 --- a/services/core/java/com/android/server/am/ActivityDisplay.java +++ b/services/core/java/com/android/server/am/ActivityDisplay.java @@ -38,7 +38,9 @@ import static com.android.server.am.proto.ActivityDisplayProto.CONFIGURATION_CON import static com.android.server.am.proto.ActivityDisplayProto.STACKS; import static com.android.server.am.proto.ActivityDisplayProto.ID; +import android.annotation.Nullable; import android.app.ActivityManagerInternal; +import android.app.ActivityOptions; import android.app.WindowConfiguration; import android.util.IntArray; import android.util.Slog; @@ -206,6 +208,18 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { } /** + * Returns an existing stack compatible with the input params or creates one + * if a compatible stack doesn't exist. + * @see #getOrCreateStack(int, int, boolean) + */ + <T extends ActivityStack> T getOrCreateStack(@Nullable ActivityRecord r, + @Nullable ActivityOptions options, @Nullable TaskRecord candidateTask, int activityType, + boolean onTop) { + final int windowingMode = resolveWindowingMode(r, options, candidateTask, activityType); + return getOrCreateStack(windowingMode, activityType, onTop); + } + + /** * Creates a stack matching the input windowing mode and activity type on this display. * @param windowingMode The windowing mode the stack should be created in. If * {@link WindowConfiguration#WINDOWING_MODE_UNDEFINED} then the stack will @@ -235,7 +249,7 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { } final ActivityManagerService service = mSupervisor.mService; - if (!mSupervisor.isWindowingModeSupported(windowingMode, service.mSupportsMultiWindow, + if (!isWindowingModeSupported(windowingMode, service.mSupportsMultiWindow, service.mSupportsSplitScreenMultiWindow, service.mSupportsFreeformWindowManagement, service.mSupportsPictureInPicture, activityType)) { throw new IllegalArgumentException("Can't create stack for unsupported windowingMode=" @@ -252,35 +266,29 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { } } - windowingMode = updateWindowingModeForSplitScreenIfNeeded(windowingMode, activityType); - final int stackId = mSupervisor.getNextStackId(); + return createStackUnchecked(windowingMode, activityType, stackId, onTop); + } - final T stack = createStackUnchecked(windowingMode, activityType, stackId, onTop); + @VisibleForTesting + <T extends ActivityStack> T createStackUnchecked(int windowingMode, int activityType, + int stackId, boolean onTop) { + if (windowingMode == WINDOWING_MODE_PINNED) { + return (T) new PinnedActivityStack(this, stackId, mSupervisor, onTop); + } + final T stack = (T) new ActivityStack( + this, stackId, mSupervisor, windowingMode, activityType, onTop); if (mDisplayId == DEFAULT_DISPLAY && windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { - // Make sure recents stack exist when creating a dock stack as it normally need to be on - // the other side of the docked stack and we make visibility decisions based on that. + // Make sure recents stack exist when creating a dock stack as it normally needs to be + // on the other side of the docked stack and we make visibility decisions based on that. // TODO: Not sure if this is needed after we change to calculate visibility based on // stack z-order vs. id. getOrCreateStack(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_RECENTS, onTop); } - return stack; } - @VisibleForTesting - <T extends ActivityStack> T createStackUnchecked(int windowingMode, int activityType, - int stackId, boolean onTop) { - switch (windowingMode) { - case WINDOWING_MODE_PINNED: - return (T) new PinnedActivityStack(this, stackId, mSupervisor, onTop); - default: - return (T) new ActivityStack( - this, stackId, mSupervisor, windowingMode, activityType, onTop); - } - } - /** * Removes stacks in the input windowing modes from the system if they are of activity type * ACTIVITY_TYPE_STANDARD or ACTIVITY_TYPE_UNDEFINED @@ -372,6 +380,105 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { } } + /** + * Returns true if the {@param windowingMode} is supported based on other parameters passed in. + * @param windowingMode The windowing mode we are checking support for. + * @param supportsMultiWindow If we should consider support for multi-window mode in general. + * @param supportsSplitScreen If we should consider support for split-screen multi-window. + * @param supportsFreeform If we should consider support for freeform multi-window. + * @param supportsPip If we should consider support for picture-in-picture mutli-window. + * @param activityType The activity type under consideration. + * @return true if the windowing mode is supported. + */ + private boolean isWindowingModeSupported(int windowingMode, boolean supportsMultiWindow, + boolean supportsSplitScreen, boolean supportsFreeform, boolean supportsPip, + int activityType) { + + if (windowingMode == WINDOWING_MODE_UNDEFINED + || windowingMode == WINDOWING_MODE_FULLSCREEN) { + return true; + } + if (!supportsMultiWindow) { + return false; + } + + if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY + || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) { + return supportsSplitScreen && WindowConfiguration.supportSplitScreenWindowingMode( + windowingMode, activityType); + } + + if (!supportsFreeform && windowingMode == WINDOWING_MODE_FREEFORM) { + return false; + } + + if (!supportsPip && windowingMode == WINDOWING_MODE_PINNED) { + return false; + } + return true; + } + + int resolveWindowingMode(@Nullable ActivityRecord r, @Nullable ActivityOptions options, + @Nullable TaskRecord task, int activityType) { + + // First preference if the windowing mode in the activity options if set. + int windowingMode = (options != null) + ? options.getLaunchWindowingMode() : WINDOWING_MODE_UNDEFINED; + + // If windowing mode is unset, then next preference is the candidate task, then the + // activity record. + if (windowingMode == WINDOWING_MODE_UNDEFINED) { + if (task != null) { + windowingMode = task.getWindowingMode(); + } + if (windowingMode == WINDOWING_MODE_UNDEFINED && r != null) { + windowingMode = r.getWindowingMode(); + } + if (windowingMode == WINDOWING_MODE_UNDEFINED) { + // Use the display's windowing mode. + windowingMode = getWindowingMode(); + } + } + + // Make sure the windowing mode we are trying to use makes sense for what is supported. + final ActivityManagerService service = mSupervisor.mService; + boolean supportsMultiWindow = service.mSupportsMultiWindow; + boolean supportsSplitScreen = service.mSupportsSplitScreenMultiWindow; + boolean supportsFreeform = service.mSupportsFreeformWindowManagement; + boolean supportsPip = service.mSupportsPictureInPicture; + if (supportsMultiWindow) { + if (task != null) { + supportsMultiWindow = task.isResizeable(); + supportsSplitScreen = task.supportsSplitScreenWindowingMode(); + // TODO: Do we need to check for freeform and Pip support here? + } else if (r != null) { + supportsMultiWindow = r.isResizeable(); + supportsSplitScreen = r.supportsSplitScreenWindowingMode(); + supportsFreeform = r.supportsFreeform(); + supportsPip = r.supportsPictureInPicture(); + } + } + + final boolean inSplitScreenMode = hasSplitScreenPrimaryStack(); + if (!inSplitScreenMode + && windowingMode == WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY) { + // Switch to fullscreen windowing mode if we are not in split-screen mode and we are + // trying to launch in split-screen secondary. + windowingMode = WINDOWING_MODE_FULLSCREEN; + } else if (inSplitScreenMode && windowingMode == WINDOWING_MODE_FULLSCREEN + && supportsSplitScreen) { + windowingMode = WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; + } + + if (windowingMode != WINDOWING_MODE_UNDEFINED + && isWindowingModeSupported(windowingMode, supportsMultiWindow, supportsSplitScreen, + supportsFreeform, supportsPip, activityType)) { + return windowingMode; + } + // Return the display's windowing mode + return getWindowingMode(); + } + /** Returns the top visible stack activity type that isn't in the exclude windowing mode. */ int getTopVisibleStackActivityType(int excludeWindowingMode) { for (int i = mStacks.size() - 1; i >= 0; --i) { @@ -408,6 +515,14 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { } } + /** We are in the process of exiting split-screen mode. */ + void onExitingSplitScreenMode() { + // Remove reference to the primary-split-screen stack so it no longer has any effect on the + // display. For example, we want to be able to create fullscreen stack for standard activity + // types when exiting split-screen mode. + mSplitScreenPrimaryStack = null; + } + ActivityStack getSplitScreenPrimaryStack() { return mSplitScreenPrimaryStack; } @@ -424,21 +539,6 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { return mPinnedStack != null; } - int updateWindowingModeForSplitScreenIfNeeded(int windowingMode, int activityType) { - final boolean inSplitScreenMode = hasSplitScreenPrimaryStack(); - if (!inSplitScreenMode - && windowingMode == WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY) { - // Switch to fullscreen windowing mode if we are not in split-screen mode and we are - // trying to launch in split-screen secondary. - return WINDOWING_MODE_FULLSCREEN; - } else if (inSplitScreenMode && windowingMode == WINDOWING_MODE_FULLSCREEN - && WindowConfiguration.supportSplitScreenWindowingMode( - windowingMode, activityType)) { - return WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; - } - return windowingMode; - } - @Override public String toString() { return "ActivityDisplay={" + mDisplayId + " numStacks=" + mStacks.size() + "}"; diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index e1e53b3b0b31..0cb120b00c65 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -31,6 +31,7 @@ import static android.app.ActivityManager.StackId.INVALID_STACK_ID; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.content.pm.PackageManager.FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS; @@ -333,6 +334,7 @@ import android.text.style.SuggestionSpan; import android.util.ArrayMap; import android.util.ArraySet; import android.util.AtomicFile; +import android.util.StatsLog; import android.util.TimingsTraceLog; import android.util.DebugUtils; import android.util.DisplayMetrics; @@ -368,6 +370,7 @@ import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.internal.notification.SystemNotificationChannels; import com.android.internal.os.BackgroundThread; import com.android.internal.os.BatteryStatsImpl; +import com.android.internal.os.BinderInternal; import com.android.internal.os.IResultReceiver; import com.android.internal.os.ProcessCpuTracker; import com.android.internal.os.TransferPipe; @@ -2030,7 +2033,9 @@ public class ActivityManagerService extends IActivityManager.Stub synchronized (ActivityManagerService.this) { for (int i = mLruProcesses.size() - 1 ; i >= 0 ; i--) { ProcessRecord r = mLruProcesses.get(i); - if (r.thread != null) { + // Don't dispatch to isolated processes as they can't access + // ConnectivityManager and don't have network privileges anyway. + if (r.thread != null && !r.isolated) { try { r.thread.setHttpProxy(host, port, exclList, pacFileUrl); } catch (RemoteException ex) { @@ -8081,7 +8086,7 @@ public class ActivityManagerService extends IActivityManager.Stub // Adjust the source bounds by the insets for the transition down final Rect sourceBounds = new Rect(r.pictureInPictureArgs.getSourceRectHint()); mStackSupervisor.moveActivityToPinnedStackLocked(r, sourceBounds, aspectRatio, - true /* moveHomeStackToFront */, "enterPictureInPictureMode"); + "enterPictureInPictureMode"); final PinnedActivityStack stack = r.getStack(); stack.setPictureInPictureAspectRatio(aspectRatio); stack.setPictureInPictureActions(actions); @@ -10229,11 +10234,7 @@ public class ActivityManagerService extends IActivityManager.Stub Slog.e(TAG, "moveTaskToFront: Attempt to violate Lock Task Mode"); return; } - final ActivityRecord prev = mStackSupervisor.topRunningActivityLocked(); - if (prev != null) { - task.setTaskToReturnTo(prev); - } - mStackSupervisor.findTaskToMoveToFrontLocked(task, flags, options, "moveTaskToFront", + mStackSupervisor.findTaskToMoveToFront(task, flags, options, "moveTaskToFront", false /* forceNonResizable */); final ActivityRecord topActivity = task.getTopActivity(); @@ -10318,7 +10319,8 @@ public class ActivityManagerService extends IActivityManager.Stub } // TODO(multi-display): Have the caller pass in the windowing mode and activity type. final ActivityStack stack = display.createStack( - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /*onTop*/); + WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD, + ON_TOP); return (stack != null) ? stack.mStackId : INVALID_STACK_ID; } } @@ -12330,6 +12332,9 @@ public class ActivityManagerService extends IActivityManager.Stub + android.Manifest.permission.SHUTDOWN); } + // TODO: Where should the corresponding '1' (start) write go? + StatsLog.write(StatsLog.DEVICE_ON_STATUS_CHANGED, 0); + boolean timedout = false; synchronized(this) { @@ -13512,6 +13517,7 @@ public class ActivityManagerService extends IActivityManager.Stub stats.getPackageStatsLocked(sourceUid >= 0 ? sourceUid : uid, sourcePkg != null ? sourcePkg : rec.key.packageName); pkg.noteWakeupAlarmLocked(tag); + StatsLog.write(StatsLog.WAKEUP_ALARM_OCCURRED, sourceUid >= 0 ? sourceUid : uid); } } } @@ -14083,6 +14089,23 @@ public class ActivityManagerService extends IActivityManager.Stub } mStackSupervisor.resumeFocusedStackTopActivityLocked(); mUserController.sendUserSwitchBroadcasts(-1, currentUserId); + + BinderInternal.nSetBinderProxyCountEnabled(true); + BinderInternal.setBinderProxyCountCallback( + new BinderInternal.BinderProxyLimitListener() { + @Override + public void onLimitReached(int uid) { + Slog.wtf(TAG, "Uid " + uid + " sent too many Binders to uid " + + Process.myUid()); + if (uid == Process.SYSTEM_UID) { + Slog.i(TAG, "Skipping kill (uid is SYSTEM)"); + } else { + killUid(UserHandle.getAppId(uid), UserHandle.getUserId(uid), + "Too many Binders sent to SYSTEM"); + } + } + }, mHandler); + traceLog.traceEnd(); // ActivityManagerStartApps traceLog.traceEnd(); // PhaseActivityManagerReady } @@ -14239,12 +14262,12 @@ public class ActivityManagerService extends IActivityManager.Stub } } sb.append("\n"); - if (info.crashInfo != null && info.crashInfo.stackTrace != null) { - sb.append(info.crashInfo.stackTrace); + if (info.hasStackTrace()) { + sb.append(info.getStackTrace()); sb.append("\n"); } - if (info.message != null) { - sb.append(info.message); + if (info.getViolationDetails() != null) { + sb.append(info.getViolationDetails()); sb.append("\n"); } @@ -14900,6 +14923,19 @@ public class ActivityManagerService extends IActivityManager.Stub mRecentTasks.dump(pw, true /* dumpAll */, dumpPackage); } } + } else if ("binder-proxies".equals(cmd)) { + if (opti >= args.length) { + dumpBinderProxiesCounts(pw, BinderInternal.nGetBinderProxyPerUidCounts(), + "Counts of Binder Proxies held by SYSTEM"); + } else { + String uid = args[opti]; + opti++; + // Ensure Binder Proxy Count is as up to date as possible + System.gc(); + System.runFinalization(); + System.gc(); + pw.println(BinderInternal.nGetBinderProxyCount(Integer.parseInt(uid))); + } } else if ("broadcasts".equals(cmd) || "b".equals(cmd)) { String[] newArgs; String name; @@ -15418,6 +15454,34 @@ public class ActivityManagerService extends IActivityManager.Stub return printed; } + boolean dumpBinderProxiesCounts(PrintWriter pw, SparseIntArray counts, String header) { + if(counts != null) { + pw.println(header); + for (int i = 0; i < counts.size(); i++) { + final int uid = counts.keyAt(i); + final int binderCount = counts.valueAt(i); + pw.print(" UID "); + pw.print(uid); + pw.print(", binder count = "); + pw.print(binderCount); + pw.print(", package(s)= "); + final String[] pkgNames = mContext.getPackageManager().getPackagesForUid(uid); + if (pkgNames != null) { + for (int j = 0; j < pkgNames.length; j++) { + pw.print(pkgNames[j]); + pw.print("; "); + } + } else { + pw.print("NO PACKAGE NAME FOUND"); + } + pw.println(); + } + pw.println(); + return true; + } + return false; + } + void dumpProcessesLocked(FileDescriptor fd, PrintWriter pw, String[] args, int opti, boolean dumpAll, String dumpPackage) { boolean needSep = false; @@ -15623,7 +15687,8 @@ public class ActivityManagerService extends IActivityManager.Stub } pw.println(" mPreviousProcess: " + mPreviousProcess); } - if (dumpAll) { + if (dumpAll && (mPreviousProcess == null || dumpPackage == null + || mPreviousProcess.pkgList.containsKey(dumpPackage))) { StringBuilder sb = new StringBuilder(128); sb.append(" mPreviousProcessVisibleTime: "); TimeUtils.formatDuration(mPreviousProcessVisibleTime, sb); @@ -15642,7 +15707,9 @@ public class ActivityManagerService extends IActivityManager.Stub mStackSupervisor.dumpDisplayConfigs(pw, " "); } if (dumpAll) { - pw.println(" mConfigWillChange: " + getFocusedStack().mConfigWillChange); + if (dumpPackage == null) { + pw.println(" mConfigWillChange: " + getFocusedStack().mConfigWillChange); + } if (mCompatModePackages.getPackages().size() > 0) { boolean printed = false; for (Map.Entry<String, Integer> entry @@ -15722,8 +15789,8 @@ public class ActivityManagerService extends IActivityManager.Stub pw.println(" mRunningVoice=" + mRunningVoice); pw.println(" mVoiceWakeLock" + mVoiceWakeLock); } + pw.println(" mVrController=" + mVrController); } - pw.println(" mVrController=" + mVrController); if (mDebugApp != null || mOrigDebugApp != null || mDebugTransient || mOrigWaitForDebugger) { if (dumpPackage == null || dumpPackage.equals(mDebugApp) diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index f03d2d5352b7..4cf2794c3584 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -222,6 +222,8 @@ final class ActivityManagerShellCommand extends ShellCommand { return runSetInactive(pw); case "get-inactive": return runGetInactive(pw); + case "set-standby-bucket": + return runSetStandbyBucket(pw); case "send-trim-memory": return runSendTrimMemory(pw); case "display": @@ -1824,6 +1826,27 @@ final class ActivityManagerShellCommand extends ShellCommand { return 0; } + int runSetStandbyBucket(PrintWriter pw) throws RemoteException { + int userId = UserHandle.USER_CURRENT; + + String opt; + while ((opt=getNextOption()) != null) { + if (opt.equals("--user")) { + userId = UserHandle.parseUserArg(getNextArgRequired()); + } else { + getErrPrintWriter().println("Error: Unknown option: " + opt); + return -1; + } + } + String packageName = getNextArgRequired(); + String value = getNextArgRequired(); + + IUsageStatsManager usm = IUsageStatsManager.Stub.asInterface(ServiceManager.getService( + Context.USAGE_STATS_SERVICE)); + usm.setAppStandbyBucket(packageName, Integer.parseInt(value), userId); + return 0; + } + int runGetInactive(PrintWriter pw) throws RemoteException { int userId = UserHandle.USER_CURRENT; @@ -2571,6 +2594,8 @@ final class ActivityManagerShellCommand extends ShellCommand { pw.println(" Sets the inactive state of an app."); pw.println(" get-inactive [--user <USER_ID>] <PACKAGE>"); pw.println(" Returns the inactive state of an app."); + pw.println(" set-standby-bucket [--user <USER_ID>] <PACKAGE> <BUCKET>"); + pw.println(" Puts an app in the standby bucket."); pw.println(" send-trim-memory [--user <USER_ID>] <PROCESS>"); pw.println(" [HIDDEN|RUNNING_MODERATE|BACKGROUND|RUNNING_LOW|MODERATE|RUNNING_CRITICAL|COMPLETE]"); pw.println(" Send a memory trim event to a <PROCESS>. May also supply a raw trim int level."); diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java index 5dc6adc1a817..0e8fc2ce2b54 100644 --- a/services/core/java/com/android/server/am/ActivityRecord.java +++ b/services/core/java/com/android/server/am/ActivityRecord.java @@ -165,6 +165,7 @@ import android.view.IAppTransitionAnimationSpecsFuture; import android.view.IApplicationToken; import android.view.WindowManager.LayoutParams; +import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.ResolverActivity; import com.android.internal.content.ReferrerIntent; @@ -232,7 +233,9 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo final String processName; // process where this component wants to run final String taskAffinity; // as per ActivityInfo.taskAffinity final boolean stateNotNeeded; // As per ActivityInfo.flags - boolean fullscreen; // covers the full screen? + boolean fullscreen; // The activity is opaque and fills the entire space of this task. + // TODO: See if it possible to combine this with the fullscreen field. + final boolean hasWallpaper; // Has a wallpaper window as a background. final boolean noDisplay; // activity is not displayed? private final boolean componentSpecified; // did caller specify an explicit component? final boolean rootVoiceInteraction; // was this the root activity of a voice interaction? @@ -274,6 +277,7 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo ActivityState state; // current state we are in Bundle icicle; // last saved activity state PersistableBundle persistentState; // last persistently saved activity state + // TODO: See if this is still needed. boolean frontOfTask; // is this the root activity of its task? boolean launchFailed; // set if a launched failed, to abort on 2nd try boolean haveState; // have we gotten the last activity state? @@ -883,9 +887,14 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo Entry ent = AttributeCache.instance().get(packageName, realTheme, com.android.internal.R.styleable.Window, userId); - fullscreen = ent != null && !ActivityInfo.isTranslucentOrFloating(ent.array); - noDisplay = ent != null && ent.array.getBoolean( - com.android.internal.R.styleable.Window_windowNoDisplay, false); + if (ent != null) { + fullscreen = !ActivityInfo.isTranslucentOrFloating(ent.array); + hasWallpaper = ent.array.getBoolean(R.styleable.Window_windowShowWallpaper, false); + noDisplay = ent.array.getBoolean(R.styleable.Window_windowNoDisplay, false); + } else { + hasWallpaper = false; + noDisplay = false; + } setActivityType(_componentSpecified, _launchedFromUid, _intent, options, sourceRecord); diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index a16105a504b8..7c0df048408d 100644 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -16,13 +16,10 @@ package com.android.server.am; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; -import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; @@ -107,7 +104,6 @@ import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.res.Configuration; -import android.graphics.Point; import android.graphics.Rect; import android.net.Uri; import android.os.Binder; @@ -346,7 +342,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai private final SparseArray<Rect> mTmpBounds = new SparseArray<>(); private final SparseArray<Rect> mTmpInsetBounds = new SparseArray<>(); private final Rect mTmpRect2 = new Rect(); - private final Point mTmpSize = new Point(); /** Run all ActivityStacks through this */ protected final ActivityStackSupervisor mStackSupervisor; @@ -489,26 +484,23 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai activityType = ACTIVITY_TYPE_STANDARD; } final ActivityDisplay display = getDisplay(); - if (display != null) { - if (activityType == ACTIVITY_TYPE_STANDARD + if (display != null && activityType == ACTIVITY_TYPE_STANDARD && windowingMode == WINDOWING_MODE_UNDEFINED) { - // Standard activity types will mostly take on the windowing mode of the display if - // one isn't specified, so look-up a compatible stack based on the display's - // windowing mode. - windowingMode = display.getWindowingMode(); - } - windowingMode = - display.updateWindowingModeForSplitScreenIfNeeded(windowingMode, activityType); + // Standard activity types will mostly take on the windowing mode of the display if one + // isn't specified, so look-up a compatible stack based on the display's windowing mode. + windowingMode = display.getWindowingMode(); } return super.isCompatible(windowingMode, activityType); } /** Adds the stack to specified display and calls WindowManager to do the same. */ void reparent(ActivityDisplay activityDisplay, boolean onTop) { + // TODO: We should probably resolve the windowing mode for the stack on the new display here + // so that it end up in a compatible mode in the new display. e.g. split-screen secondary. removeFromDisplay(); mTmpRect2.setEmpty(); postAddToDisplay(activityDisplay, mTmpRect2.isEmpty() ? null : mTmpRect2, onTop); - adjustFocusToNextFocusableStackLocked("reparent", true /* allowFocusSelf */); + adjustFocusToNextFocusableStack("reparent", true /* allowFocusSelf */); mStackSupervisor.resumeFocusedStackTopActivityLocked(); // Update visibility of activities before notifying WM. This way it won't try to resize // windows that are no longer visible. @@ -837,6 +829,12 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai return mDisplayId == DEFAULT_DISPLAY; } + private boolean returnsToHomeStack() { + return !inMultiWindowMode() + && !mTaskHistory.isEmpty() + && mTaskHistory.get(0).returnsToHomeStack(); + } + void moveToFront(String reason) { moveToFront(reason, null); } @@ -850,6 +848,12 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai return; } + if (!isActivityTypeHome() && returnsToHomeStack()) { + // Make sure the home stack is behind this stack since that is where we should return to + // when this stack is no longer visible. + mStackSupervisor.moveHomeStackToFront(reason + " returnToHome"); + } + getDisplay().positionChildAtTop(this); mStackSupervisor.setFocusStackUnchecked(reason, this); if (task != null) { @@ -1496,25 +1500,17 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai } } - /** Returns true if the stack contains a fullscreen task. */ - private boolean hasFullscreenTask() { - for (int i = mTaskHistory.size() - 1; i >= 0; --i) { - final TaskRecord task = mTaskHistory.get(i); - if (task.mFullscreen) { - return true; - } - } - return false; - } - /** * Returns true if the stack is translucent and can have other contents visible behind it if * needed. A stack is considered translucent if it don't contain a visible or * starting (about to be visible) activity that is fullscreen (opaque). * @param starting The currently starting activity or null if there is none. - * @param stackBehind The stack directly behind this one. */ - private boolean isStackTranslucent(ActivityRecord starting, ActivityStack stackBehind) { + @VisibleForTesting + boolean isStackTranslucent(ActivityRecord starting) { + if (!isAttached() || mForceHidden) { + return true; + } for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) { final TaskRecord task = mTaskHistory.get(taskNdx); final ArrayList<ActivityRecord> activities = task.mActivities; @@ -1533,21 +1529,11 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai continue; } - if (r.fullscreen) { + if (r.fullscreen || r.hasWallpaper) { // Stack isn't translucent if it has at least one fullscreen activity // that is visible. return false; } - - final boolean stackBehindHomeOrRecent = stackBehind != null - && stackBehind.isHomeOrRecentsStack(); - if (!isHomeOrRecentsStack() && r.frontOfTask && task.isOverHomeStack() - && !stackBehindHomeOrRecent && !isActivityTypeAssistant()) { - // Stack isn't translucent if it's top activity should have the home stack - // behind it and the stack currently behind it isn't the home or recents stack - // or the assistant stack. - return false; - } } } return true; @@ -1572,129 +1558,68 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai if (!isAttached() || mForceHidden) { return false; } - - final ActivityDisplay display = getDisplay(); - if (isTopStackOnDisplay() || mStackSupervisor.isFocusedStack(this)) { - return true; - } - - final int stackIndex = display.getIndexOf(this); - - // Check position and visibility of this stack relative to the front stack on its display. - final ActivityStack topStack = getDisplay().getTopStack(); - final int windowingMode = getWindowingMode(); - final int activityType = getActivityType(); - - if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { - // If the assistant stack is focused and translucent, then the docked stack is always - // visible - if (topStack.isActivityTypeAssistant()) { - return topStack.isStackTranslucent(starting, this); - } + if (mStackSupervisor.isFocusedStack(this)) { return true; } - // Set home stack to invisible when it is below but not immediately below the docked stack - // A case would be if recents stack exists but has no tasks and is below the docked stack - // and home stack is below recents - if (activityType == ACTIVITY_TYPE_HOME) { - final ActivityStack splitScreenStack = display.getSplitScreenPrimaryStack(); - int dockedStackIndex = display.getIndexOf(splitScreenStack); - if (dockedStackIndex > stackIndex && stackIndex != dockedStackIndex - 1) { - return false; - } - } - - // Find the first stack behind front stack that actually got something visible. - int stackBehindTopIndex = display.getIndexOf(topStack) - 1; - while (stackBehindTopIndex >= 0 && - display.getChildAt(stackBehindTopIndex).topRunningActivityLocked() == null) { - stackBehindTopIndex--; - } - final ActivityStack stackBehindTop = (stackBehindTopIndex >= 0) - ? display.getChildAt(stackBehindTopIndex) : null; - int stackBehindTopWindowingMode = WINDOWING_MODE_UNDEFINED; - int stackBehindTopActivityType = ACTIVITY_TYPE_UNDEFINED; - if (stackBehindTop != null) { - stackBehindTopWindowingMode = stackBehindTop.getWindowingMode(); - stackBehindTopActivityType = stackBehindTop.getActivityType(); - } - - final boolean alwaysOnTop = topStack.getWindowConfiguration().isAlwaysOnTop(); - if (topStack.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY || alwaysOnTop) { - if (this == stackBehindTop) { - // Stacks directly behind the docked or pinned stack are always visible. - return true; - } else if (alwaysOnTop && stackIndex == stackBehindTopIndex - 1) { - // Otherwise, this stack can also be visible if it is directly behind a docked stack - // or translucent assistant stack behind an always-on-top top-most stack - if (stackBehindTopWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { - return true; - } else if (stackBehindTopActivityType == ACTIVITY_TYPE_ASSISTANT) { - return stackBehindTop.isStackTranslucent(starting, this); - } - } + final ActivityRecord top = topRunningActivityLocked(); + if (top == null && isInStackLocked(starting) == null && !isTopStackOnDisplay()) { + // Shouldn't be visible if you don't have any running activities, not starting one, and + // not the top stack on display. + return false; } - if (topStack.isBackdropToTranslucentActivity() - && topStack.isStackTranslucent(starting, stackBehindTop)) { - // Stacks behind the fullscreen or assistant stack with a translucent activity are - // always visible so they can act as a backdrop to the translucent activity. - // For example, dialog activities - if (stackIndex == stackBehindTopIndex) { + final ActivityDisplay display = getDisplay(); + boolean gotOpaqueSplitScreenPrimary = false; + boolean gotOpaqueSplitScreenSecondary = false; + final int windowingMode = getWindowingMode(); + for (int i = display.getChildCount() - 1; i >= 0; --i) { + final ActivityStack other = display.getChildAt(i); + if (other == this) { + // Should be visible if there is no other stack occluding it. return true; } - if (stackBehindTopIndex >= 0) { - if ((stackBehindTopWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY - || stackBehindTopWindowingMode == WINDOWING_MODE_PINNED) - && stackIndex == (stackBehindTopIndex - 1)) { - // The stack behind the docked or pinned stack is also visible so we can have a - // complete backdrop to the translucent activity when the docked stack is up. - return true; - } - } - } - - if (isOnHomeDisplay()) { - // Visibility of any stack on default display should have been determined by the - // conditions above. - return false; - } - final int stackCount = display.getChildCount(); - for (int i = stackIndex + 1; i < stackCount; i++) { - final ActivityStack stack = display.getChildAt(i); + final int otherWindowingMode = other.getWindowingMode(); + // TODO: Can be removed once we are no longer using returnToType for back functionality + final ActivityStack stackBehind = i > 0 ? display.getChildAt(i - 1) : null; - if (!stack.mFullscreen && !stack.hasFullscreenTask()) { - continue; - } - - if (!stack.isDynamicStacksVisibleBehindAllowed()) { - // These stacks can't have any dynamic stacks visible behind them. + if (otherWindowingMode == WINDOWING_MODE_FULLSCREEN) { + if (other.isStackTranslucent(starting)) { + // Can be visible behind a translucent fullscreen stack. + continue; + } return false; + } else if (otherWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY + && !gotOpaqueSplitScreenPrimary) { + gotOpaqueSplitScreenPrimary = + !other.isStackTranslucent(starting); + if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY + && gotOpaqueSplitScreenPrimary) { + // Can not be visible behind another opaque stack in split-screen-primary mode. + return false; + } + } else if (otherWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY + && !gotOpaqueSplitScreenSecondary) { + gotOpaqueSplitScreenSecondary = + !other.isStackTranslucent(starting); + if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY + && gotOpaqueSplitScreenSecondary) { + // Can not be visible behind another opaque stack in split-screen-secondary mode. + return false; + } } - - if (!stack.isStackTranslucent(starting, null /* stackBehind */)) { + if (gotOpaqueSplitScreenPrimary && gotOpaqueSplitScreenSecondary) { + // Can not be visible if we are in split-screen windowing mode and both halves of + // the screen are opaque. return false; } } + // Well, nothing is stopping you from being visible... return true; } - private boolean isBackdropToTranslucentActivity() { - if (isActivityTypeAssistant()) { - return true; - } - final int windowingMode = getWindowingMode(); - return windowingMode == WINDOWING_MODE_FULLSCREEN - || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; - } - - private boolean isDynamicStacksVisibleBehindAllowed() { - return isActivityTypeAssistant() || getWindowingMode() == WINDOWING_MODE_PINNED; - } - final int rankTaskLayers(int baseLayer) { int layer = 0; for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) { @@ -1713,6 +1638,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai * Make sure that all activities that need to be visible (that is, they * currently can be seen by the user) actually are. */ + // TODO: Should be re-worked based on the fact that each task as a stack in most cases. final void ensureActivitiesVisibleLocked(ActivityRecord starting, int configChanges, boolean preserveWindows) { mTopActivityOccludesKeyguard = false; @@ -1757,7 +1683,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai visibleIgnoringKeyguard, isTop); if (visibleIgnoringKeyguard) { behindFullscreenActivity = updateBehindFullscreen(!stackShouldBeVisible, - behindFullscreenActivity, task, r); + behindFullscreenActivity, r); } if (reallyVisible) { if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Make visible? " + r @@ -1816,16 +1742,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai // show activities in the next application stack behind them vs. another // task in the home stack like recents. behindFullscreenActivity = true; - } else if (windowingMode == WINDOWING_MODE_FULLSCREEN - || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) { - if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Skipping after task=" + task - + " returning to non-application type=" + task.getTaskToReturnTo()); - // Once we reach a fullscreen stack task that has a running activity and should - // return to another stack task, then no other activities behind that one should - // be visible. - if (task.topRunningActivityLocked() != null && !task.returnsToStandardTask()) { - behindFullscreenActivity = true; - } } } @@ -1861,24 +1777,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai } /** - * Returns true if we try to maintain focus in the current stack when the top activity finishes. - */ - private boolean keepFocusInStackIfPossible() { - final int windowingMode = getWindowingMode(); - return windowingMode == WINDOWING_MODE_FREEFORM - || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY - || windowingMode == WINDOWING_MODE_PINNED; - } - - /** - * Returns true if the top task in the task is allowed to return home when finished and - * there are other tasks in the stack. - */ - boolean allowTopTaskToReturnHome() { - return !inPinnedWindowingMode(); - } - - /** * @return the top most visible activity that wants to dismiss Keyguard */ ActivityRecord getTopDismissingKeyguardActivity() { @@ -2039,18 +1937,13 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai } private boolean updateBehindFullscreen(boolean stackInvisible, boolean behindFullscreenActivity, - TaskRecord task, ActivityRecord r) { + ActivityRecord r) { if (r.fullscreen) { if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Fullscreen: at " + r + " stackInvisible=" + stackInvisible + " behindFullscreenActivity=" + behindFullscreenActivity); // At this point, nothing else needs to be shown in this task. behindFullscreenActivity = true; - } else if (!isHomeOrRecentsStack() && r.frontOfTask && task.isOverHomeStack()) { - if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Showing home: at " + r - + " stackInvisible=" + stackInvisible - + " behindFullscreenActivity=" + behindFullscreenActivity); - behindFullscreenActivity = true; } return behindFullscreenActivity; } @@ -2209,7 +2102,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai final boolean hasRunningActivity = next != null; // TODO: Maybe this entire condition can get removed? - if (hasRunningActivity && getDisplay() == null) { + if (hasRunningActivity && !isAttached()) { return false; } @@ -2239,28 +2132,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai return false; } - final TaskRecord nextTask = next.getTask(); - final TaskRecord prevTask = prev != null ? prev.getTask() : null; - if (prevTask != null && prevTask.getStack() == this && - prevTask.isOverHomeStack() && prev.finishing && prev.frontOfTask) { - if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked(); - if (prevTask == nextTask) { - prevTask.setFrontOfTask(); - } else if (prevTask != topTask()) { - // This task is going away but it was supposed to return to the home stack. - // Now the task above it has to return to the home task instead. - final int taskNdx = mTaskHistory.indexOf(prevTask) + 1; - mTaskHistory.get(taskNdx).setTaskToReturnTo(ACTIVITY_TYPE_HOME); - } else if (!isOnHomeDisplay()) { - return false; - } else if (!isActivityTypeHome()){ - if (DEBUG_STATES) Slog.d(TAG_STATES, - "resumeTopActivityLocked: Launching home next"); - return isOnHomeDisplay() && - mStackSupervisor.resumeHomeStackTask(prev, "prevFinished"); - } - } - // If we are sleeping, and there is no resumed activity, and the top // activity is paused, well that is the state we want. if (shouldSleepOrShutDownActivities() @@ -2644,7 +2515,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai private boolean resumeTopActivityInNextFocusableStack(ActivityRecord prev, ActivityOptions options, String reason) { - if (adjustFocusToNextFocusableStackLocked(reason)) { + if (adjustFocusToNextFocusableStack(reason)) { // Try to move focus to the next visible stack with a running activity if this // stack is not covering the entire screen or is on a secondary display (with no home // stack). @@ -2718,7 +2589,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai } private void insertTaskAtTop(TaskRecord task, ActivityRecord starting) { - updateTaskReturnToForTopInsertion(task); // TODO: Better place to put all the code below...may be addTask... mTaskHistory.remove(task); // Now put task at top. @@ -2729,57 +2599,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai true /* includingParents */); } - /** - * Updates the {@param task}'s return type before it is moved to the top. - */ - private void updateTaskReturnToForTopInsertion(TaskRecord task) { - boolean isLastTaskOverHome = false; - // If the moving task is over the home or assistant stack, transfer its return type to next - // task so that they return to the same stack - if (task.isOverHomeStack() || task.isOverAssistantStack()) { - final TaskRecord nextTask = getNextTask(task); - if (nextTask != null) { - nextTask.setTaskToReturnTo(task.getTaskToReturnTo()); - } else { - isLastTaskOverHome = true; - } - } - - // If this is not on the default display, then just set the return type to application - if (!isOnHomeDisplay()) { - task.setTaskToReturnTo(ACTIVITY_TYPE_STANDARD); - return; - } - - final ActivityStack lastStack = mStackSupervisor.getLastStack(); - - // If there is no last task, do not set task to return to - if (lastStack == null) { - return; - } - - // If the task was launched from the assistant stack, set the return type to assistant - if (lastStack.isActivityTypeAssistant()) { - task.setTaskToReturnTo(ACTIVITY_TYPE_ASSISTANT); - return; - } - - // If this is being moved to the top by another activity or being launched from the home - // activity, set mTaskToReturnTo accordingly. - final boolean fromHomeOrRecents = lastStack.isHomeOrRecentsStack(); - final TaskRecord topTask = lastStack.topTask(); - if (!isHomeOrRecentsStack() && (fromHomeOrRecents || topTask() != task)) { - // If it's a last task over home - we default to keep its return to type not to - // make underlying task focused when this one will be finished. - int returnToType = isLastTaskOverHome - ? task.getTaskToReturnTo() : ACTIVITY_TYPE_STANDARD; - if (fromHomeOrRecents && allowTopTaskToReturnHome()) { - returnToType = topTask == null ? ACTIVITY_TYPE_HOME : topTask.getActivityType(); - } - task.setTaskToReturnTo(returnToType); - } - } - final void startActivityLocked(ActivityRecord r, ActivityRecord focusedTopActivity, boolean newTask, boolean keepCurTransition, ActivityOptions options) { TaskRecord rTask = r.getTask(); @@ -3049,7 +2868,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai } mWindowContainerController.positionChildAtBottom( - targetTask.getWindowContainerController()); + targetTask.getWindowContainerController(), false /* includingParents */); replyChainEnd = -1; } else if (forceReset || finishOnTaskLaunch || clearWhenTaskReset) { // If the activity should just be removed -- either @@ -3285,7 +3104,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai } /** Returns true if the task is one of the task finishing on-top of the top running task. */ - boolean isATopFinishingTask(TaskRecord task) { + private boolean isATopFinishingTask(TaskRecord task) { for (int i = mTaskHistory.size() - 1; i >= 0; --i) { final TaskRecord current = mTaskHistory.get(i); final ActivityRecord r = current.topRunningActivityLocked(); @@ -3300,7 +3119,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai return false; } - private void adjustFocusedActivityStackLocked(ActivityRecord r, String reason) { + private void adjustFocusedActivityStack(ActivityRecord r, String reason) { if (!mStackSupervisor.isFocusedStack(this) || ((mResumedActivity != r) && (mResumedActivity != null))) { return; @@ -3309,66 +3128,44 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai final ActivityRecord next = topRunningActivityLocked(); final String myReason = reason + " adjustFocus"; - if (next != r) { - if (next != null && keepFocusInStackIfPossible() && isFocusable()) { - // For freeform, docked, and pinned stacks we always keep the focus within the - // stack as long as there is a running activity. - return; - } else { - // Task is not guaranteed to be non-null. For example, destroying the - // {@link ActivityRecord} will disassociate the task from the activity. - final TaskRecord task = r.getTask(); + if (next == r) { + mStackSupervisor.moveFocusableActivityStackToFrontLocked( + mStackSupervisor.topRunningActivityLocked(), myReason); + return; + } - if (task == null) { - throw new IllegalStateException("activity no longer associated with task:" + r); - } + if (next != null && isFocusable()) { + // Keep focus in stack if we have a top running activity and are focusable. + return; + } - final boolean isAssistantOrOverAssistant = - task.getStack().isActivityTypeAssistant() || task.isOverAssistantStack(); - if (r.frontOfTask && isATopFinishingTask(task) - && (task.isOverHomeStack() || isAssistantOrOverAssistant)) { - // For non-fullscreen or assistant stack, we want to move the focus to the next - // visible stack to prevent the home screen from moving to the top and obscuring - // other visible stacks. - if ((!mFullscreen || isAssistantOrOverAssistant) - && adjustFocusToNextFocusableStackLocked(myReason)) { - return; - } - // Move the home stack to the top if this stack is fullscreen or there is no - // other visible stack. - if (task.isOverHomeStack() && - mStackSupervisor.moveHomeStackTaskToTop(myReason)) { - // Activity focus was already adjusted. Nothing else to do... - return; - } - } - } + // Task is not guaranteed to be non-null. For example, destroying the + // {@link ActivityRecord} will disassociate the task from the activity. + final TaskRecord task = r.getTask(); + + if (task == null) { + throw new IllegalStateException("activity no longer associated with task:" + r); } - mStackSupervisor.moveFocusableActivityStackToFrontLocked( - mStackSupervisor.topRunningActivityLocked(), myReason); + // Move focus to next focusable stack if possible. + if (adjustFocusToNextFocusableStack(myReason)) { + return; + } + + // Whatever...go home. + mStackSupervisor.moveHomeStackTaskToTop(myReason); } /** Find next proper focusable stack and make it focused. */ - private boolean adjustFocusToNextFocusableStackLocked(String reason) { - return adjustFocusToNextFocusableStackLocked(reason, false /* allowFocusSelf */); + private boolean adjustFocusToNextFocusableStack(String reason) { + return adjustFocusToNextFocusableStack(reason, false /* allowFocusSelf */); } /** * Find next proper focusable stack and make it focused. * @param allowFocusSelf Is the focus allowed to remain on the same stack. */ - private boolean adjustFocusToNextFocusableStackLocked(String reason, boolean allowFocusSelf) { - if (isActivityTypeAssistant() && bottomTask() != null - && bottomTask().returnsToHomeTask()) { - // If the current stack is the assistant stack, then use the return-to type to determine - // whether to return to the home screen. This is needed to workaround an issue where - // launching a fullscreen task (and subequently returning from that task) will cause - // the fullscreen stack to be found as the next focusable stack below, even if the - // assistant was launched over home. - return mStackSupervisor.moveHomeStackTaskToTop(reason); - } - + private boolean adjustFocusToNextFocusableStack(String reason, boolean allowFocusSelf) { final ActivityStack stack = mStackSupervisor.getNextFocusableStackLocked( allowFocusSelf ? null : this); final String myReason = reason + " adjustFocusToNextFocusableStack"; @@ -3378,22 +3175,12 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai final ActivityRecord top = stack.topRunningActivityLocked(); - if (stack.isHomeOrRecentsStack() && (top == null || !top.visible)) { + if (stack.isActivityTypeHome() && (top == null || !top.visible)) { // If we will be focusing on the home stack next and its current top activity isn't - // visible, then use the task return to value to determine the home task to display - // next. + // visible, then use the move the home stack task to top to make the activity visible. return mStackSupervisor.moveHomeStackTaskToTop(reason); } - if (stack.isActivityTypeAssistant() && top != null - && top.getTask().returnsToHomeTask()) { - // It is possible for the home stack to not be directly underneath the assistant stack. - // For example, the assistant may start an activity in the fullscreen stack. Upon - // returning to the assistant stack, we must ensure that the home stack is underneath - // when appropriate. - mStackSupervisor.moveHomeStackTaskToTop("adjustAssistantReturnToHome"); - } - stack.moveToFront(myReason); return true; } @@ -3408,7 +3195,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai if (requestFinishActivityLocked(r.appToken, Activity.RESULT_CANCELED, null, "stop-no-history", false)) { // If {@link requestFinishActivityLocked} returns {@code true}, - // {@link adjustFocusedActivityStackLocked} would have been already called. + // {@link adjustFocusedActivityStack} would have been already called. r.resumeKeyDispatchingLocked(); return; } @@ -3420,7 +3207,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai } if (r.app != null && r.app.thread != null) { - adjustFocusedActivityStackLocked(r, "stopActivity"); + adjustFocusedActivityStack(r, "stopActivity"); r.resumeKeyDispatchingLocked(); try { r.stopped = false; @@ -3659,7 +3446,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai r.pauseKeyDispatchingLocked(); - adjustFocusedActivityStackLocked(r, "finishActivity"); + adjustFocusedActivityStack(r, "finishActivity"); finishActivityResultsLocked(r, resultCode, resultData); @@ -3826,7 +3613,21 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai } } - final boolean shouldUpRecreateTaskLocked(ActivityRecord srec, String destAffinity) { + /** @return true if the stack behind this one is a standard activity type. */ + boolean inFrontOfStandardStack() { + final ActivityDisplay display = getDisplay(); + if (display == null) { + return false; + } + final int index = display.getIndexOf(this); + if (index == 0) { + return false; + } + final ActivityStack stackBehind = display.getChildAt(index - 1); + return stackBehind.isActivityTypeStandard(); + } + + boolean shouldUpRecreateTaskLocked(ActivityRecord srec, String destAffinity) { // Basic case: for simple app-centric recents, we need to recreate // the task if the affinity has changed. if (srec == null || srec.getTask().affinity == null || @@ -3838,10 +3639,9 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai // of a document, unless simply finishing it will return them to the the // correct app behind. final TaskRecord task = srec.getTask(); - if (srec.frontOfTask && task != null && task.getBaseIntent() != null - && task.getBaseIntent().isDocument()) { + if (srec.frontOfTask && task.getBaseIntent() != null && task.getBaseIntent().isDocument()) { // Okay, this activity is at the root of its task. What to do, what to do... - if (!task.returnsToStandardTask()) { + if (!inFrontOfStandardStack()) { // Finishing won't return to an application, so we need to recreate. return true; } @@ -4041,11 +3841,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai + " onlyHasTaskOverlays=" + onlyHasTaskOverlays); } - if (mStackSupervisor.isFocusedStack(this) && task == topTask() && - task.isOverHomeStack()) { - mStackSupervisor.moveHomeStackTaskToTop(reason); - } - // The following block can be executed multiple times if there is more than one overlay. // {@link ActivityStackSupervisor#removeTaskByIdLocked} handles this by reverse lookup // of the task by id and exiting early if not found. @@ -4567,60 +4362,19 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION, "Prepare to back transition: task=" + taskId); - boolean prevIsHome = false; - - // If true, we should resume the home activity next if the task we are moving to the - // back is over the home stack. We force to false if the task we are moving to back - // is the home task and we don't want it resumed after moving to the back. - final boolean canGoHome = !tr.isActivityTypeHome() && tr.isOverHomeStack(); - if (canGoHome) { - final TaskRecord nextTask = getNextTask(tr); - if (nextTask != null) { - nextTask.setTaskToReturnTo(tr.getTaskToReturnTo()); - } else { - prevIsHome = true; - } - } - - boolean requiresMove = mTaskHistory.indexOf(tr) != 0; - if (requiresMove) { - mTaskHistory.remove(tr); - mTaskHistory.add(0, tr); - updateTaskMovement(tr, false); + mTaskHistory.remove(tr); + mTaskHistory.add(0, tr); + updateTaskMovement(tr, false); - mWindowManager.prepareAppTransition(TRANSIT_TASK_TO_BACK, false); - mWindowContainerController.positionChildAtBottom(tr.getWindowContainerController()); - } + mWindowManager.prepareAppTransition(TRANSIT_TASK_TO_BACK, false); + mWindowContainerController.positionChildAtBottom(tr.getWindowContainerController(), + true /* includingParents */); if (inPinnedWindowingMode()) { mStackSupervisor.removeStack(this); return true; } - // Otherwise, there is an assumption that moving a task to the back moves it behind the - // home activity. We make sure here that some activity in the stack will launch home. - int numTasks = mTaskHistory.size(); - for (int taskNdx = numTasks - 1; taskNdx >= 1; --taskNdx) { - final TaskRecord task = mTaskHistory.get(taskNdx); - if (task.isOverHomeStack()) { - break; - } - if (taskNdx == 1) { - // Set the last task before tr to go to home. - task.setTaskToReturnTo(ACTIVITY_TYPE_HOME); - } - } - - final TaskRecord task = mResumedActivity != null ? mResumedActivity.getTask() : null; - if (prevIsHome || (task == tr && canGoHome) || (numTasks <= 1 && isOnHomeDisplay())) { - if (!mService.mBooting && !mService.mBooted) { - // Not ready yet! - return false; - } - tr.setTaskToReturnTo(ACTIVITY_TYPE_STANDARD); - return mStackSupervisor.resumeHomeStackTask(null, "moveTaskToBack"); - } - mStackSupervisor.resumeFocusedStackTopActivityLocked(); return true; } @@ -5057,14 +4811,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai onActivityRemovedFromStack(record); } - final int taskNdx = mTaskHistory.indexOf(task); - final int topTaskNdx = mTaskHistory.size() - 1; - if (task.isOverHomeStack() && taskNdx < topTaskNdx) { - final TaskRecord nextTask = mTaskHistory.get(taskNdx + 1); - if (!nextTask.isOverHomeStack() && !nextTask.isOverAssistantStack()) { - nextTask.setTaskToReturnTo(ACTIVITY_TYPE_HOME); - } - } mTaskHistory.remove(task); removeActivitiesFromLRUListLocked(task); updateTaskMovement(task, true); @@ -5094,7 +4840,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai if (isOnHomeDisplay() && mode != REMOVE_TASK_MODE_MOVING_TO_TOP && mStackSupervisor.isFocusedStack(this)) { String myReason = reason + " leftTaskHistoryEmpty"; - if (mFullscreen || !adjustFocusToNextFocusableStackLocked(myReason)) { + if (mFullscreen || !adjustFocusToNextFocusableStack(myReason)) { mStackSupervisor.moveHomeStackToFront(myReason); } } @@ -5123,24 +4869,14 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai addTask(task, toTop, "createTaskRecord"); final boolean isLockscreenShown = mService.mStackSupervisor.mKeyguardController .isKeyguardShowing(mDisplayId != INVALID_DISPLAY ? mDisplayId : DEFAULT_DISPLAY); - if (!layoutTaskInStack(task, info.windowLayout) && mBounds != null && task.isResizeable() - && !isLockscreenShown) { + if (!mStackSupervisor.getLaunchingBoundsController().layoutTask(task, info.windowLayout) + && mBounds != null && task.isResizeable() && !isLockscreenShown) { task.updateOverrideConfiguration(mBounds); } task.createWindowContainer(toTop, (info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0); return task; } - boolean layoutTaskInStack(TaskRecord task, ActivityInfo.WindowLayout windowLayout) { - if (!task.inFreeformWindowingMode()) { - return false; - } - mStackSupervisor.getLaunchingTaskPositioner() - .updateDefaultBounds(task, mTaskHistory, windowLayout); - - return true; - } - ArrayList<TaskRecord> getAllTasks() { return new ArrayList<>(mTaskHistory); } @@ -5167,10 +4903,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai mTaskHistory.add(position, task); task.setStack(this); - if (toTop) { - updateTaskReturnToForTopInsertion(task); - } - updateTaskMovement(task, toTop); postAddTask(task, prevStack, schedulePictureInPictureModeChange); @@ -5263,7 +4995,8 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai public String toString() { return "ActivityStack{" + Integer.toHexString(System.identityHashCode(this)) + " stackId=" + mStackId + " type=" + activityTypeToString(getActivityType()) - + " mode=" + windowingModeToString(getWindowingMode()) + ", " + + " mode=" + windowingModeToString(getWindowingMode()) + + " visible=" + shouldBeVisible(null /* starting */) + ", " + mTaskHistory.size() + " tasks}"; } diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 0bdcbf9f2c72..5d8636edaa56 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -110,7 +110,6 @@ import android.app.AppOpsManager; import android.app.ProfilerInfo; import android.app.ResultInfo; import android.app.WaitResult; -import android.app.WindowConfiguration; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -223,13 +222,6 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D // at the top of its container (e.g. stack). static final boolean ON_TOP = true; - // Used to indicate that an objects (e.g. task) removal from its container - // (e.g. stack) is due to it moving to another container. - static final boolean MOVING = true; - - // Force the focus to change to the stack we are moving a task to.. - static final boolean FORCE_FOCUS = true; - // Don't execute any calls to resume. static final boolean DEFER_RESUME = true; @@ -295,7 +287,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D WindowManagerService mWindowManager; DisplayManager mDisplayManager; - LaunchingTaskPositioner mTaskPositioner = new LaunchingTaskPositioner(); + private final LaunchingBoundsController mLaunchingBoundsController; /** Counter for next free stack ID to use for dynamic activity stacks. */ private int mNextFreeStackId = 0; @@ -405,6 +397,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D * object each time. */ private final Rect tempRect = new Rect(); + private final ActivityOptions mTmpOptions = ActivityOptions.makeBasic(); // The default minimal size that will be used if the activity doesn't specify its minimal size. // It will be calculated when the default display gets added. @@ -575,6 +568,9 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D mHandler = new ActivityStackSupervisorHandler(looper); mActivityMetricsLogger = new ActivityMetricsLogger(this, mService.mContext); mKeyguardController = new KeyguardController(service, this); + + mLaunchingBoundsController = new LaunchingBoundsController(); + mLaunchingBoundsController.registerDefaultPositioners(this); } void setRecentTasks(RecentTasks recentTasks) { @@ -685,10 +681,6 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D return false; } - if (prev != null) { - prev.getTask().setTaskToReturnTo(ACTIVITY_TYPE_STANDARD); - } - mHomeStack.moveHomeStackTaskToTop(); ActivityRecord r = getHomeActivity(); final String myReason = reason + " resumeHomeStackTask"; @@ -2097,23 +2089,28 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } } - void findTaskToMoveToFrontLocked(TaskRecord task, int flags, ActivityOptions options, + void findTaskToMoveToFront(TaskRecord task, int flags, ActivityOptions options, String reason, boolean forceNonResizeable) { - if ((flags & ActivityManager.MOVE_TASK_NO_USER_ACTION) == 0) { - mUserLeaving = true; - } - if ((flags & ActivityManager.MOVE_TASK_WITH_HOME) != 0) { - // Caller wants the home activity moved with it. To accomplish this, - // we'll just indicate that this task returns to the home task. - task.setTaskToReturnTo(ACTIVITY_TYPE_HOME); - } final ActivityStack currentStack = task.getStack(); if (currentStack == null) { - Slog.e(TAG, "findTaskToMoveToFrontLocked: can't move task=" + Slog.e(TAG, "findTaskToMoveToFront: can't move task=" + task + " to front. Stack is null"); return; } + if ((flags & ActivityManager.MOVE_TASK_NO_USER_ACTION) == 0) { + mUserLeaving = true; + } + + final ActivityRecord prev = topRunningActivityLocked(); + + if ((flags & ActivityManager.MOVE_TASK_WITH_HOME) != 0 + || (prev != null && prev.isActivityTypeRecents())) { + // Caller wants the home activity moved with it or the previous task is recents in which + // case we always return home from the task we are moving to the front. + moveHomeStackToFront("findTaskToMoveToFront"); + } + if (task.isResizeable() && canUseActivityOptionsLaunchBounds(options)) { final Rect bounds = TaskRecord.validateBounds(options.getLaunchBounds()); task.updateOverrideConfiguration(bounds); @@ -2122,7 +2119,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D if (stack != currentStack) { task.reparent(stack, ON_TOP, REPARENT_KEEP_STACK_AT_FRONT, !ANIMATE, DEFER_RESUME, - "findTaskToMoveToFrontLocked"); + "findTaskToMoveToFront"); stack = currentStack; // moveTaskToStackUncheckedLocked() should already placed the task on top, // still need moveTaskToFrontLocked() below for any transition settings. @@ -2161,8 +2158,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D || mService.mSupportsFreeformWindowManagement; } - LaunchingTaskPositioner getLaunchingTaskPositioner() { - return mTaskPositioner; + LaunchingBoundsController getLaunchingBoundsController() { + return mLaunchingBoundsController; } protected <T extends ActivityStack> T getStack(int stackId) { @@ -2186,89 +2183,6 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D return null; } - /** - * Returns true if the {@param windowingMode} is supported based on other parameters passed in. - * @param windowingMode The windowing mode we are checking support for. - * @param supportsMultiWindow If we should consider support for multi-window mode in general. - * @param supportsSplitScreen If we should consider support for split-screen multi-window. - * @param supportsFreeform If we should consider support for freeform multi-window. - * @param supportsPip If we should consider support for picture-in-picture mutli-window. - * @param activityType The activity type under consideration. - * @return true if the windowing mode is supported. - */ - boolean isWindowingModeSupported(int windowingMode, boolean supportsMultiWindow, - boolean supportsSplitScreen, boolean supportsFreeform, boolean supportsPip, - int activityType) { - - if (windowingMode == WINDOWING_MODE_UNDEFINED - || windowingMode == WINDOWING_MODE_FULLSCREEN) { - return true; - } - if (!supportsMultiWindow) { - return false; - } - - if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY - || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) { - return supportsSplitScreen && WindowConfiguration.supportSplitScreenWindowingMode( - windowingMode, activityType); - } - - if (!supportsFreeform && windowingMode == WINDOWING_MODE_FREEFORM) { - return false; - } - - if (!supportsPip && windowingMode == WINDOWING_MODE_PINNED) { - return false; - } - return true; - } - - private int resolveWindowingMode(@Nullable ActivityRecord r, @Nullable ActivityOptions options, - @Nullable TaskRecord task, int activityType) { - - // First preference if the windowing mode in the activity options if set. - int windowingMode = (options != null) - ? options.getLaunchWindowingMode() : WINDOWING_MODE_UNDEFINED; - - // If windowing mode is unset, then next preference is the candidate task, then the - // activity record. - if (windowingMode == WINDOWING_MODE_UNDEFINED) { - if (task != null) { - windowingMode = task.getWindowingMode(); - } - if (windowingMode == WINDOWING_MODE_UNDEFINED && r != null) { - windowingMode = r.getWindowingMode(); - } - } - - // Make sure the windowing mode we are trying to use makes sense for what is supported. - boolean supportsMultiWindow = mService.mSupportsMultiWindow; - boolean supportsSplitScreen = mService.mSupportsSplitScreenMultiWindow; - boolean supportsFreeform = mService.mSupportsFreeformWindowManagement; - boolean supportsPip = mService.mSupportsPictureInPicture; - if (supportsMultiWindow) { - if (task != null) { - supportsMultiWindow = task.isResizeable(); - supportsSplitScreen = task.supportsSplitScreenWindowingMode(); - // TODO: Do we need to check for freeform and Pip support here? - } else if (r != null) { - supportsMultiWindow = r.isResizeable(); - supportsSplitScreen = r.supportsSplitScreenWindowingMode(); - supportsFreeform = r.supportsFreeform(); - supportsPip = r.supportsPictureInPicture(); - } - } - - if (windowingMode != WINDOWING_MODE_UNDEFINED - && isWindowingModeSupported(windowingMode, supportsMultiWindow, supportsSplitScreen, - supportsFreeform, supportsPip, activityType)) { - return windowingMode; - } - // Return root/systems windowing mode - return getWindowingMode(); - } - int resolveActivityType(@Nullable ActivityRecord r, @Nullable ActivityOptions options, @Nullable TaskRecord task) { // Preference is given to the activity type for the activity then the task since the type @@ -2329,7 +2243,6 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } final int activityType = resolveActivityType(r, options, candidateTask); - int windowingMode = resolveWindowingMode(r, options, candidateTask, activityType); T stack = null; // Next preference for stack goes to the display Id set in the activity options or the @@ -2347,7 +2260,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } final ActivityDisplay display = getActivityDisplayOrCreateLocked(displayId); if (display != null) { - stack = display.getOrCreateStack(windowingMode, activityType, onTop); + stack = display.getOrCreateStack(r, options, candidateTask, activityType, onTop); if (stack != null) { return stack; } @@ -2365,10 +2278,14 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D stack = r.getStack(); } if (stack != null) { - if (stack.isCompatible(windowingMode, activityType)) { - return stack; - } display = stack.getDisplay(); + if (display != null) { + final int windowingMode = + display.resolveWindowingMode(r, options, candidateTask, activityType); + if (stack.isCompatible(windowingMode, activityType)) { + return stack; + } + } } if (display == null @@ -2379,7 +2296,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D display = getDefaultDisplay(); } - return display.getOrCreateStack(windowingMode, activityType, onTop); + return display.getOrCreateStack(r, options, candidateTask, activityType, onTop); } /** @@ -2596,6 +2513,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D final ActivityDisplay toDisplay = getActivityDisplay(toDisplayId); if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { + // Tell the display we are exiting split-screen mode. + toDisplay.onExitingSplitScreenMode(); // We are moving all tasks from the docked stack to the fullscreen stack, // which is dismissing the docked stack, so resize all other stacks to // fullscreen here already so we don't end up with resize trashing. @@ -2625,35 +2544,27 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D final ArrayList<TaskRecord> tasks = fromStack.getAllTasks(); if (!tasks.isEmpty()) { + mTmpOptions.setLaunchWindowingMode(WINDOWING_MODE_FULLSCREEN); final int size = tasks.size(); - final ActivityStack fullscreenStack = toDisplay.getOrCreateStack( - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, onTop); - - if (onTop) { - final int returnToType = - toDisplay.getTopVisibleStackActivityType(WINDOWING_MODE_PINNED); - for (int i = 0; i < size; i++) { - final TaskRecord task = tasks.get(i); + for (int i = 0; i < size; ++i) { + final TaskRecord task = tasks.get(i); + final ActivityStack toStack = toDisplay.getOrCreateStack( + null, mTmpOptions, task, task.getActivityType(), onTop); + + if (onTop) { + final int returnToType = + toDisplay.getTopVisibleStackActivityType(WINDOWING_MODE_PINNED); final boolean isTopTask = i == (size - 1); - if (inPinnedWindowingMode) { - // Update the return-to to reflect where the pinned stack task was moved - // from so that we retain the stack that was previously visible if the - // pinned stack is recreated. See moveActivityToPinnedStackLocked(). - task.setTaskToReturnTo(returnToType); - } // Defer resume until all the tasks have been moved to the fullscreen stack - task.reparent(fullscreenStack, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, + task.reparent(toStack, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, isTopTask /* animate */, DEFER_RESUME, schedulePictureInPictureModeChange, "moveTasksToFullscreenStack - onTop"); - } - } else { - for (int i = 0; i < size; i++) { - final TaskRecord task = tasks.get(i); + } else { // Position the tasks in the fullscreen stack in order at the bottom of the // stack. Also defer resume until all the tasks have been moved to the // fullscreen stack. - task.reparent(fullscreenStack, i /* position */, + task.reparent(toStack, ON_TOP, REPARENT_LEAVE_STACK_IN_PLACE, !ANIMATE, DEFER_RESUME, schedulePictureInPictureModeChange, "moveTasksToFullscreenStack - NOT_onTop"); @@ -3080,23 +2991,16 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D + " task=" + task); } - // We don't allow moving a unresizeable task to the docked stack since the docked stack is - // used for split-screen mode and will cause things like the docked divider to show up. We - // instead leave the task in its current stack or move it to the fullscreen stack if it - // isn't currently in a stack. + // Leave the task in its current stack or a fullscreen stack if it isn't resizeable and the + // preferred stack is in multi-window mode. if (inMultiWindowMode && !task.isResizeable()) { - Slog.w(TAG, "Can not move unresizeable task=" + task + " to docked stack." - + " Moving to stackId=" + stackId + " instead."); - // Temporarily disable resizeablility of the task as we don't want it to be resized if, - // for example, a docked stack is created which will lead to the stack we are moving - // from being resized and and its resizeable tasks being resized. - try { - task.mTemporarilyUnresizable = true; - stack = stack.getDisplay().createStack( - WINDOWING_MODE_FULLSCREEN, stack.getActivityType(), toTop); - } finally { - task.mTemporarilyUnresizable = false; + Slog.w(TAG, "Can not move unresizeable task=" + task + " to multi-window stack=" + stack + + " Moving to a fullscreen stack instead."); + if (prevStack != null) { + return prevStack; } + stack = stack.getDisplay().createStack( + WINDOWING_MODE_FULLSCREEN, stack.getActivityType(), toTop); } return stack; } @@ -3123,12 +3027,12 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } moveActivityToPinnedStackLocked(r, null /* sourceBounds */, 0f /* aspectRatio */, - true /* moveHomeStackToFront */, "moveTopActivityToPinnedStack"); + "moveTopActivityToPinnedStack"); return true; } void moveActivityToPinnedStackLocked(ActivityRecord r, Rect sourceHintBounds, float aspectRatio, - boolean moveHomeStackToFront, String reason) { + String reason) { mWindowManager.deferSurfaceLayout(); @@ -3154,17 +3058,6 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D true /* allowResizeInDockedMode */, !DEFER_RESUME); if (task.mActivities.size() == 1) { - // There is only one activity in the task. So, we can just move the task over to - // the stack without re-parenting the activity in a different task. We don't - // move the home stack forward if we are currently entering picture-in-picture - // while pausing because that changes the focused stack and may prevent the new - // starting activity from resuming. - if (moveHomeStackToFront && task.returnsToHomeTask() - && (r.state == RESUMED || !r.supportsEnterPipOnTaskSwitch)) { - // Move the home stack forward if the task we just moved to the pinned stack - // was launched from home so home should be visible behind it. - moveHomeStackToFront(reason); - } // Defer resume until below, and do not schedule PiP changes until we animate below task.reparent(stack, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, !ANIMATE, DEFER_RESUME, false /* schedulePictureInPictureModeChange */, reason); @@ -4287,9 +4180,9 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D final boolean isSecondaryDisplayPreferred = (preferredDisplayId != DEFAULT_DISPLAY && preferredDisplayId != INVALID_DISPLAY); final boolean inSplitScreenMode = actualStack != null - && actualStack.inSplitScreenWindowingMode(); + && actualStack.getDisplay().hasSplitScreenPrimaryStack(); if (((!inSplitScreenMode && preferredWindowingMode != WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) - && !isSecondaryDisplayPreferred) || task.isActivityTypeHome()) { + && !isSecondaryDisplayPreferred) || !task.isActivityTypeStandardOrUndefined()) { return; } @@ -4606,6 +4499,10 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D "startActivityFromRecents: Task " + taskId + " not found."); } + // We always want to return to the home activity instead of the recents activity from + // whatever is started from the recents activity, so move the home stack forward. + moveHomeStackToFront("startActivityFromRecents"); + // If the user must confirm credentials (e.g. when first launching a work app and the // Work Challenge is present) let startActivityInPackage handle the intercepting. if (!mService.mUserController.shouldConfirmCredentials(task.userId) diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java index 6f74d85115c9..f6905c58d928 100644 --- a/services/core/java/com/android/server/am/ActivityStarter.java +++ b/services/core/java/com/android/server/am/ActivityStarter.java @@ -151,7 +151,7 @@ class ActivityStarter { private boolean mLaunchTaskBehind; private int mLaunchFlags; - private Rect mLaunchBounds; + private Rect mLaunchBounds = new Rect(); private ActivityRecord mNotTop; private boolean mDoResume; @@ -169,9 +169,6 @@ class ActivityStarter { private Intent mNewTaskIntent; private ActivityStack mSourceStack; private ActivityStack mTargetStack; - // Indicates that we moved other task and are going to put something on top soon, so - // we don't want to show it redundantly or accidentally change what's shown below. - private boolean mMovedOtherTask; private boolean mMovedToFront; private boolean mNoAnimation; private boolean mKeepCurTransition; @@ -210,7 +207,7 @@ class ActivityStarter { mLaunchFlags = 0; mLaunchMode = INVALID_LAUNCH_MODE; - mLaunchBounds = null; + mLaunchBounds.setEmpty(); mNotTop = null; mDoResume = false; @@ -227,7 +224,6 @@ class ActivityStarter { mSourceStack = null; mTargetStack = null; - mMovedOtherTask = false; mMovedToFront = false; mNoAnimation = false; mKeepCurTransition = false; @@ -1184,12 +1180,8 @@ class ActivityStarter { mIntent, mStartActivity.getUriPermissionsLocked(), mStartActivity.userId); mService.grantEphemeralAccessLocked(mStartActivity.userId, mIntent, mStartActivity.appInfo.uid, UserHandle.getAppId(mCallingUid)); - if (mSourceRecord != null) { - mStartActivity.getTask().setTaskToReturnTo(mSourceRecord); - } if (newTask) { - EventLog.writeEvent( - EventLogTags.AM_CREATE_TASK, mStartActivity.userId, + EventLog.writeEvent(EventLogTags.AM_CREATE_TASK, mStartActivity.userId, mStartActivity.getTask().taskId); } ActivityStack.logStartActivity( @@ -1254,7 +1246,10 @@ class ActivityStarter { mPreferredDisplayId = getPreferedDisplayId(mSourceRecord, mStartActivity, options); - mLaunchBounds = getOverrideBounds(r, options, inTask); + mLaunchBounds.setEmpty(); + + mSupervisor.getLaunchingBoundsController().calculateBounds(inTask, null /*layout*/, r, + options, mLaunchBounds); mLaunchMode = r.launchMode; @@ -1579,7 +1574,6 @@ class ActivityStarter { if (mLaunchTaskBehind && mSourceRecord != null) { intentActivity.setTaskToAffiliateWith(mSourceRecord.getTask()); } - mMovedOtherTask = true; // If the launch flags carry both NEW_TASK and CLEAR_TASK, the task's activities // will be cleared soon by ActivityStarter in setTaskFromIntentActivity(). @@ -1644,7 +1638,6 @@ class ActivityStarter { intentActivity.showStartingWindow(null /* prev */, false /* newTask */, true /* taskSwitch */); } - updateTaskReturnToType(intentActivity.getTask(), mLaunchFlags, focusStack); } } if (!mMovedToFront && mDoResume) { @@ -1663,27 +1656,6 @@ class ActivityStarter { return intentActivity; } - private void updateTaskReturnToType( - TaskRecord task, int launchFlags, ActivityStack focusedStack) { - if ((launchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME)) - == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME)) { - // Caller wants to appear on home activity. - task.setTaskToReturnTo(ACTIVITY_TYPE_HOME); - return; - } else if (focusedStack == null || focusedStack.isActivityTypeHome()) { - // Task will be launched over the home stack, so return home. - task.setTaskToReturnTo(ACTIVITY_TYPE_HOME); - return; - } else if (focusedStack != task.getStack() && focusedStack.isActivityTypeAssistant()) { - // Task was launched over the assistant stack, so return there - task.setTaskToReturnTo(ACTIVITY_TYPE_ASSISTANT); - return; - } - - // Else we are coming from an application stack so return to an application. - task.setTaskToReturnTo(ACTIVITY_TYPE_STANDARD); - } - private void setTaskFromIntentActivity(ActivityRecord intentActivity) { if ((mLaunchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK)) == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK)) { @@ -1700,11 +1672,6 @@ class ActivityStarter { task.performClearTaskLocked(); mReuseTask = task; mReuseTask.setIntent(mStartActivity); - - // When we clear the task - focus will be adjusted, which will bring another task - // to top before we launch the activity we need. This will temporary swap their - // mTaskToReturnTo values and we don't want to overwrite them accidentally. - mMovedOtherTask = true; } else if ((mLaunchFlags & FLAG_ACTIVITY_CLEAR_TOP) != 0 || isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK)) { ActivityRecord top = intentActivity.getTask().performClearTaskLocked(mStartActivity, @@ -1725,7 +1692,7 @@ class ActivityStarter { // Target stack got cleared when we all activities were removed above. // Go ahead and reset it. mTargetStack = computeStackFocus(mSourceRecord, false /* newTask */, - null /* bounds */, mLaunchFlags, mOptions); + mLaunchFlags, mOptions); mTargetStack.addTask(task, !mLaunchTaskBehind /* toTop */, "startActivityUnchecked"); } @@ -1776,8 +1743,7 @@ class ActivityStarter { private int setTaskFromReuseOrCreateNewTask( TaskRecord taskToAffiliate, ActivityStack topStack) { - mTargetStack = computeStackFocus( - mStartActivity, true, mLaunchBounds, mLaunchFlags, mOptions); + mTargetStack = computeStackFocus(mStartActivity, true, mLaunchFlags, mOptions); // Do no move the target stack to front yet, as we might bail if // isLockTaskModeViolation fails below. @@ -1806,15 +1772,6 @@ class ActivityStarter { return START_RETURN_LOCK_TASK_MODE_VIOLATION; } - if (!mMovedOtherTask) { - // If stack id is specified in activity options, usually it means that activity is - // launched not from currently focused stack (e.g. from SysUI or from shell) - in - // that case we check the target stack. - // TODO: Not sure I understand the value or use of the commented out code and the - // comment above. See if this causes any issues and why... - updateTaskReturnToType(mStartActivity.getTask(), mLaunchFlags, - /*preferredLaunchStackId != INVALID_STACK_ID ? mTargetStack : */topStack); - } if (mDoResume) { mTargetStack.moveToFront("reuseOrNewTask"); } @@ -1962,7 +1919,7 @@ class ActivityStarter { return START_TASK_TO_FRONT; } - if (mLaunchBounds != null) { + if (!mLaunchBounds.isEmpty()) { // TODO: Shouldn't we already know what stack to use by the time we get here? ActivityStack stack = mSupervisor.getLaunchStack(null, null, mInTask, ON_TOP); if (stack != mInTask.getStack()) { @@ -1985,7 +1942,7 @@ class ActivityStarter { } void updateBounds(TaskRecord task, Rect bounds) { - if (bounds == null) { + if (bounds.isEmpty()) { return; } @@ -1998,8 +1955,7 @@ class ActivityStarter { } private void setTaskToCurrentTopOrCreateNewTask() { - mTargetStack = computeStackFocus(mStartActivity, false, null /* bounds */, mLaunchFlags, - mOptions); + mTargetStack = computeStackFocus(mStartActivity, false, mLaunchFlags, mOptions); if (mDoResume) { mTargetStack.moveToFront("addingToTopTask"); } @@ -2062,8 +2018,8 @@ class ActivityStarter { } } - private ActivityStack computeStackFocus(ActivityRecord r, boolean newTask, Rect bounds, - int launchFlags, ActivityOptions aOptions) { + private ActivityStack computeStackFocus(ActivityRecord r, boolean newTask, int launchFlags, + ActivityOptions aOptions) { final TaskRecord task = r.getTask(); ActivityStack stack = getLaunchStack(r, launchFlags, task, aOptions); if (stack != null) { diff --git a/services/core/java/com/android/server/am/AppTaskImpl.java b/services/core/java/com/android/server/am/AppTaskImpl.java index d42a3b9bc1f0..38b3039f1856 100644 --- a/services/core/java/com/android/server/am/AppTaskImpl.java +++ b/services/core/java/com/android/server/am/AppTaskImpl.java @@ -16,6 +16,7 @@ package com.android.server.am; +import static com.android.server.am.ActivityStackSupervisor.MATCH_TASK_IN_STACKS_OR_RECENT_TASKS; import static com.android.server.am.ActivityStackSupervisor.REMOVE_FROM_RECENTS; import android.app.ActivityManager; @@ -76,7 +77,8 @@ class AppTaskImpl extends IAppTask.Stub { synchronized (mService) { long origId = Binder.clearCallingIdentity(); try { - TaskRecord tr = mService.mStackSupervisor.anyTaskForIdLocked(mTaskId); + TaskRecord tr = mService.mStackSupervisor.anyTaskForIdLocked(mTaskId, + MATCH_TASK_IN_STACKS_OR_RECENT_TASKS); if (tr == null) { throw new IllegalArgumentException("Unable to find task ID " + mTaskId); } @@ -110,7 +112,8 @@ class AppTaskImpl extends IAppTask.Stub { TaskRecord tr; IApplicationThread appThread; synchronized (mService) { - tr = mService.mStackSupervisor.anyTaskForIdLocked(mTaskId); + tr = mService.mStackSupervisor.anyTaskForIdLocked(mTaskId, + MATCH_TASK_IN_STACKS_OR_RECENT_TASKS); if (tr == null) { throw new IllegalArgumentException("Unable to find task ID " + mTaskId); } @@ -131,7 +134,8 @@ class AppTaskImpl extends IAppTask.Stub { synchronized (mService) { long origId = Binder.clearCallingIdentity(); try { - TaskRecord tr = mService.mStackSupervisor.anyTaskForIdLocked(mTaskId); + TaskRecord tr = mService.mStackSupervisor.anyTaskForIdLocked(mTaskId, + MATCH_TASK_IN_STACKS_OR_RECENT_TASKS); if (tr == null) { throw new IllegalArgumentException("Unable to find task ID " + mTaskId); } diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index 68ed9aecf31b..db12ae252abc 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -297,31 +297,39 @@ public final class BatteryStatsService extends IBatteryStats.Stub void noteProcessStart(String name, int uid) { synchronized (mStats) { mStats.noteProcessStartLocked(name, uid); + // TODO: decide where this should be and use a constant instead of a literal. + StatsLog.write(StatsLog.PROCESS_LIFE_CYCLE_STATE_CHANGED, uid, name, 1); } } void noteProcessCrash(String name, int uid) { synchronized (mStats) { mStats.noteProcessCrashLocked(name, uid); + // TODO: decide where this should be and use a constant instead of a literal. + StatsLog.write(StatsLog.PROCESS_LIFE_CYCLE_STATE_CHANGED, uid, name, 2); } } void noteProcessAnr(String name, int uid) { synchronized (mStats) { mStats.noteProcessAnrLocked(name, uid); + // TODO: decide where this should be and use a constant instead of a literal. + StatsLog.write(StatsLog.PROCESS_LIFE_CYCLE_STATE_CHANGED, uid, name, 3); } } void noteProcessFinish(String name, int uid) { synchronized (mStats) { mStats.noteProcessFinishLocked(name, uid); + // TODO: decide where this should be and use a constant instead of a literal. + StatsLog.write(StatsLog.PROCESS_LIFE_CYCLE_STATE_CHANGED, uid, name, 0); } } void noteUidProcessState(int uid, int state) { synchronized (mStats) { // TODO: remove this once we figure out properly where and how - StatsLog.write(StatsLog.PROCESS_STATE_CHANGED, uid, state); + StatsLog.write(StatsLog.UID_PROCESS_STATE_CHANGED, uid, state); mStats.noteUidProcessStateLocked(uid, state); } @@ -548,6 +556,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub public void noteScreenBrightness(int brightness) { enforceCallingPermission(); synchronized (mStats) { + StatsLog.write(StatsLog.SCREEN_BRIGHTNESS_CHANGED, brightness); mStats.noteScreenBrightnessLocked(brightness); } } @@ -909,6 +918,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub enforceCallingPermission(); synchronized (mStats) { mStats.noteDeviceIdleModeLocked(mode, activeReason, activeUid); + StatsLog.write(StatsLog.DEVICE_IDLE_MODE_STATE_CHANGED, mode); } } diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java index c62cc38b716f..aa82d000386a 100644 --- a/services/core/java/com/android/server/am/BroadcastQueue.java +++ b/services/core/java/com/android/server/am/BroadcastQueue.java @@ -1198,11 +1198,6 @@ public final class BroadcastQueue { + " (uid " + r.callingUid + ")"); skip = true; } - if (!skip) { - r.manifestCount++; - } else { - r.manifestSkipCount++; - } if (r.curApp != null && r.curApp.crashing) { // If the target process is crashing, just skip it. Slog.w(TAG, "Skipping deliver ordered [" + mQueueName + "] " + r @@ -1283,6 +1278,16 @@ public final class BroadcastQueue { } } + if (!skip && !Intent.ACTION_SHUTDOWN.equals(r.intent.getAction()) + && !mService.mUserController + .isUserRunning(UserHandle.getUserId(info.activityInfo.applicationInfo.uid), + 0 /* flags */)) { + skip = true; + Slog.w(TAG, + "Skipping delivery to " + info.activityInfo.packageName + " / " + + info.activityInfo.applicationInfo.uid + " : user is not running"); + } + if (skip) { if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Skipping delivery of ordered [" + mQueueName + "] " @@ -1291,9 +1296,11 @@ public final class BroadcastQueue { r.receiver = null; r.curFilter = null; r.state = BroadcastRecord.IDLE; + r.manifestSkipCount++; scheduleBroadcastsLocked(); return; } + r.manifestCount++; r.delivery[recIdx] = BroadcastRecord.DELIVERY_DELIVERED; r.state = BroadcastRecord.APP_RECEIVE; @@ -1302,7 +1309,7 @@ public final class BroadcastQueue { if (DEBUG_MU && r.callingUid > UserHandle.PER_USER_RANGE) { Slog.v(TAG_MU, "Updated broadcast record activity info for secondary user, " + info.activityInfo + ", callingUid = " + r.callingUid + ", uid = " - + info.activityInfo.applicationInfo.uid); + + receiverUid); } if (brOptions != null && brOptions.getTemporaryAppWhitelistDuration() > 0) { @@ -1365,7 +1372,7 @@ public final class BroadcastQueue { // and mark the broadcast record as ready for the next. Slog.w(TAG, "Unable to launch app " + info.activityInfo.applicationInfo.packageName + "/" - + info.activityInfo.applicationInfo.uid + " for broadcast " + + receiverUid + " for broadcast " + r.intent + ": process is bad"); logBroadcastReceiverDiscardLocked(r); finishReceiverLocked(r, r.resultCode, r.resultData, diff --git a/services/core/java/com/android/server/am/LaunchingActivityPositioner.java b/services/core/java/com/android/server/am/LaunchingActivityPositioner.java new file mode 100644 index 000000000000..5815e9809756 --- /dev/null +++ b/services/core/java/com/android/server/am/LaunchingActivityPositioner.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2017 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.server.am; + +import android.app.ActivityOptions; +import android.content.pm.ActivityInfo; +import android.graphics.Rect; +import com.android.server.am.LaunchingBoundsController.LaunchingBoundsPositioner; + +/** + * An implementation of {@link LaunchingBoundsPositioner}, which applies the launch bounds specified + * inside {@link ActivityOptions#getLaunchBounds()}. + */ +public class LaunchingActivityPositioner implements LaunchingBoundsPositioner { + private final ActivityStackSupervisor mSupervisor; + + LaunchingActivityPositioner(ActivityStackSupervisor activityStackSupervisor) { + mSupervisor = activityStackSupervisor; + } + + @Override + public int onCalculateBounds(TaskRecord task, ActivityInfo.WindowLayout layout, + ActivityRecord activity, ActivityOptions options, Rect current, Rect result) { + // We only care about figuring out bounds for activities. + if (activity == null) { + return RESULT_SKIP; + } + + // Activity must be resizeable in the specified task. + if (!(mSupervisor.canUseActivityOptionsLaunchBounds(options) + && (activity.isResizeable() || (task != null && task.isResizeable())))) { + return RESULT_SKIP; + } + + final Rect bounds = TaskRecord.validateBounds(options.getLaunchBounds()); + + // Bounds weren't valid. + if (bounds == null) { + return RESULT_SKIP; + } + + result.set(bounds); + + // When this is the most explicit position specification so we should not allow further + // modification of the position. + return RESULT_DONE; + } +} diff --git a/services/core/java/com/android/server/am/LaunchingBoundsController.java b/services/core/java/com/android/server/am/LaunchingBoundsController.java new file mode 100644 index 000000000000..8345ba657c2d --- /dev/null +++ b/services/core/java/com/android/server/am/LaunchingBoundsController.java @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2017 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.server.am; + +import android.annotation.IntDef; +import android.app.ActivityOptions; +import android.content.pm.ActivityInfo.WindowLayout; +import android.graphics.Rect; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.List; + +import static com.android.server.am.LaunchingBoundsController.LaunchingBoundsPositioner.RESULT_CONTINUE; +import static com.android.server.am.LaunchingBoundsController.LaunchingBoundsPositioner.RESULT_DONE; +import static com.android.server.am.LaunchingBoundsController.LaunchingBoundsPositioner.RESULT_SKIP; + +/** + * {@link LaunchingBoundsController} calculates the launch bounds by coordinating between registered + * {@link LaunchingBoundsPositioner}. + */ +class LaunchingBoundsController { + private final List<LaunchingBoundsPositioner> mPositioners = new ArrayList<>(); + + // Temporary {@link Rect} for calculations. This is kept separate from {@code mTmpCurrent} and + // {@code mTmpResult} to prevent clobbering values. + private final Rect mTmpRect = new Rect(); + + private final Rect mTmpCurrent = new Rect(); + private final Rect mTmpResult = new Rect(); + + /** + * Creates a {@link LaunchingBoundsController} with default registered + * {@link LaunchingBoundsPositioner}s. + */ + void registerDefaultPositioners(ActivityStackSupervisor supervisor) { + // {@link LaunchingTaskPositioner} handles window layout preferences. + registerPositioner(new LaunchingTaskPositioner()); + + // {@link LaunchingActivityPositioner} is the most specific positioner and thus should be + // registered last (applied first) out of the defaults. + registerPositioner(new LaunchingActivityPositioner(supervisor)); + } + + /** + * Returns the position calculated by the registered positioners + * @param task The {@link TaskRecord} currently being positioned. + * @param layout The specified {@link WindowLayout}. + * @param activity The {@link ActivityRecord} currently being positioned. + * @param options The {@link ActivityOptions} specified for the activity. + * @param result The resulting bounds. If no bounds are set, {@link Rect#isEmpty()} will be + * true. + */ + void calculateBounds(TaskRecord task, WindowLayout layout, ActivityRecord activity, + ActivityOptions options, Rect result) { + result.setEmpty(); + + // We start at the last registered {@link LaunchingBoundsPositioner} as this represents + // The positioner closest to the product level. Moving back through the list moves closer to + // the platform logic. + for (int i = mPositioners.size() - 1; i >= 0; --i) { + mTmpResult.setEmpty(); + mTmpCurrent.set(result); + final LaunchingBoundsPositioner positioner = mPositioners.get(i); + + switch(positioner.onCalculateBounds(task, layout, activity, options, mTmpCurrent, + mTmpResult)) { + case RESULT_SKIP: + // Do not apply any results when we are told to skip + continue; + case RESULT_DONE: + // Set result and return immediately. + result.set(mTmpResult); + return; + case RESULT_CONTINUE: + // Set result and continue + result.set(mTmpResult); + break; + } + } + } + + /** + * A convenience method for laying out a task. + * @return {@code true} if bounds were set on the task. {@code false} otherwise. + */ + boolean layoutTask(TaskRecord task, WindowLayout layout) { + calculateBounds(task, layout, null /*activity*/, null /*options*/, mTmpRect); + + if (mTmpRect.isEmpty()) { + return false; + } + + task.updateOverrideConfiguration(mTmpRect); + + return true; + } + + /** + * Adds a positioner to participate in future bounds calculation. Note that the last registered + * {@link LaunchingBoundsPositioner} will be the first to calculate the bounds. + */ + void registerPositioner(LaunchingBoundsPositioner positioner) { + if (mPositioners.contains(positioner)) { + return; + } + + mPositioners.add(positioner); + } + + /** + * An interface implemented by those wanting to participate in bounds calculation. + */ + interface LaunchingBoundsPositioner { + @Retention(RetentionPolicy.SOURCE) + @IntDef({RESULT_SKIP, RESULT_DONE, RESULT_CONTINUE}) + @interface Result {} + + // Returned when the positioner does not want to influence the bounds calculation + int RESULT_SKIP = 0; + // Returned when the positioner has changed the bounds and would like its results to be the + // final bounds applied. + int RESULT_DONE = 1; + // Returned when the positioner has changed the bounds but is okay with other positioners + // influencing the bounds. + int RESULT_CONTINUE = 2; + + /** + * Called when asked to calculate bounds. + * @param task The {@link TaskRecord} currently being positioned. + * @param layout The specified {@link WindowLayout}. + * @param activity The {@link ActivityRecord} currently being positioned. + * @param options The {@link ActivityOptions} specified for the activity. + * @param current The current bounds. This can differ from the initial bounds as it + * represents the modified bounds up to this point. + * @param result The {@link Rect} which the positioner should return its modified bounds. + * Any merging of the current bounds should be already applied to this + * value as well before returning. + * @return A {@link Result} representing the result of the bounds calculation. + */ + @Result + int onCalculateBounds(TaskRecord task, WindowLayout layout, ActivityRecord activity, + ActivityOptions options, Rect current, Rect result); + } +} diff --git a/services/core/java/com/android/server/am/LaunchingTaskPositioner.java b/services/core/java/com/android/server/am/LaunchingTaskPositioner.java index 0dc73e98f492..6389075a9b3f 100644 --- a/services/core/java/com/android/server/am/LaunchingTaskPositioner.java +++ b/services/core/java/com/android/server/am/LaunchingTaskPositioner.java @@ -19,7 +19,7 @@ package com.android.server.am; import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; -import android.annotation.Nullable; +import android.app.ActivityOptions; import android.content.pm.ActivityInfo; import android.graphics.Point; import android.graphics.Rect; @@ -36,8 +36,10 @@ import java.util.ArrayList; * and compares corners of the task with corners of existing tasks. If some two pairs of corners are * sufficiently close enough, it shifts the bounds of the new task and tries again. When it exhausts * all possible shifts, it gives up and puts the task in the original position. + * + * Note that the only gravities of concern are the corners and the center. */ -class LaunchingTaskPositioner { +class LaunchingTaskPositioner implements LaunchingBoundsController.LaunchingBoundsPositioner { private static final String TAG = TAG_WITH_CLASS_NAME ? "LaunchingTaskPositioner" : TAG_AM; // Determines how close window frames/corners have to be to call them colliding. @@ -74,44 +76,50 @@ class LaunchingTaskPositioner { * Tries to set task's bound in a way that it won't collide with any other task. By colliding * we mean that two tasks have left-top corner very close to each other, so one might get * obfuscated by the other one. - * - * @param task Task for which we want to find bounds that won't collide with other. - * @param tasks Existing tasks with which we don't want to collide. - * @param windowLayout Optional information from the client about how it would like to be sized - * and positioned. */ - void updateDefaultBounds(TaskRecord task, ArrayList<TaskRecord> tasks, - @Nullable ActivityInfo.WindowLayout windowLayout) { + @Override + public int onCalculateBounds(TaskRecord task, ActivityInfo.WindowLayout layout, + ActivityRecord activity, ActivityOptions options, Rect current, Rect result) { + // We can only apply positioning if we're in a freeform stack. + if (task == null || task.getStack() == null || !task.inFreeformWindowingMode()) { + return RESULT_SKIP; + } + + final ArrayList<TaskRecord> tasks = task.getStack().getAllTasks(); + updateAvailableRect(task, mAvailableRect); - if (windowLayout == null) { - positionCenter(task, tasks, mAvailableRect, getFreeformWidth(mAvailableRect), - getFreeformHeight(mAvailableRect)); - return; + if (layout == null) { + positionCenter(tasks, mAvailableRect, getFreeformWidth(mAvailableRect), + getFreeformHeight(mAvailableRect), result); + return RESULT_CONTINUE; } - int width = getFinalWidth(windowLayout, mAvailableRect); - int height = getFinalHeight(windowLayout, mAvailableRect); - int verticalGravity = windowLayout.gravity & Gravity.VERTICAL_GRAVITY_MASK; - int horizontalGravity = windowLayout.gravity & Gravity.HORIZONTAL_GRAVITY_MASK; + + int width = getFinalWidth(layout, mAvailableRect); + int height = getFinalHeight(layout, mAvailableRect); + int verticalGravity = layout.gravity & Gravity.VERTICAL_GRAVITY_MASK; + int horizontalGravity = layout.gravity & Gravity.HORIZONTAL_GRAVITY_MASK; if (verticalGravity == Gravity.TOP) { if (horizontalGravity == Gravity.RIGHT) { - positionTopRight(task, tasks, mAvailableRect, width, height); + positionTopRight(tasks, mAvailableRect, width, height, result); } else { - positionTopLeft(task, tasks, mAvailableRect, width, height); + positionTopLeft(tasks, mAvailableRect, width, height, result); } } else if (verticalGravity == Gravity.BOTTOM) { if (horizontalGravity == Gravity.RIGHT) { - positionBottomRight(task, tasks, mAvailableRect, width, height); + positionBottomRight(tasks, mAvailableRect, width, height, result); } else { - positionBottomLeft(task, tasks, mAvailableRect, width, height); + positionBottomLeft(tasks, mAvailableRect, width, height, result); } } else { // Some fancy gravity setting that we don't support yet. We just put the activity in the // center. - Slog.w(TAG, "Received unsupported gravity: " + windowLayout.gravity + Slog.w(TAG, "Received unsupported gravity: " + layout.gravity + ", positioning in the center instead."); - positionCenter(task, tasks, mAvailableRect, width, height); + positionCenter(tasks, mAvailableRect, width, height, result); } + + return RESULT_CONTINUE; } private void updateAvailableRect(TaskRecord task, Rect availableRect) { @@ -179,50 +187,50 @@ class LaunchingTaskPositioner { return height; } - private void positionBottomLeft(TaskRecord task, ArrayList<TaskRecord> tasks, - Rect availableRect, int width, int height) { + private void positionBottomLeft(ArrayList<TaskRecord> tasks, Rect availableRect, int width, + int height, Rect result) { mTmpProposal.set(availableRect.left, availableRect.bottom - height, availableRect.left + width, availableRect.bottom); - position(task, tasks, availableRect, mTmpProposal, !ALLOW_RESTART, - SHIFT_POLICY_HORIZONTAL_RIGHT); + position(tasks, availableRect, mTmpProposal, !ALLOW_RESTART, SHIFT_POLICY_HORIZONTAL_RIGHT, + result); } - private void positionBottomRight(TaskRecord task, ArrayList<TaskRecord> tasks, - Rect availableRect, int width, int height) { + private void positionBottomRight(ArrayList<TaskRecord> tasks, Rect availableRect, int width, + int height, Rect result) { mTmpProposal.set(availableRect.right - width, availableRect.bottom - height, availableRect.right, availableRect.bottom); - position(task, tasks, availableRect, mTmpProposal, !ALLOW_RESTART, - SHIFT_POLICY_HORIZONTAL_LEFT); + position(tasks, availableRect, mTmpProposal, !ALLOW_RESTART, SHIFT_POLICY_HORIZONTAL_LEFT, + result); } - private void positionTopLeft(TaskRecord task, ArrayList<TaskRecord> tasks, - Rect availableRect, int width, int height) { + private void positionTopLeft(ArrayList<TaskRecord> tasks, Rect availableRect, int width, + int height, Rect result) { mTmpProposal.set(availableRect.left, availableRect.top, availableRect.left + width, availableRect.top + height); - position(task, tasks, availableRect, mTmpProposal, !ALLOW_RESTART, - SHIFT_POLICY_HORIZONTAL_RIGHT); + position(tasks, availableRect, mTmpProposal, !ALLOW_RESTART, SHIFT_POLICY_HORIZONTAL_RIGHT, + result); } - private void positionTopRight(TaskRecord task, ArrayList<TaskRecord> tasks, - Rect availableRect, int width, int height) { + private void positionTopRight(ArrayList<TaskRecord> tasks, Rect availableRect, int width, + int height, Rect result) { mTmpProposal.set(availableRect.right - width, availableRect.top, availableRect.right, availableRect.top + height); - position(task, tasks, availableRect, mTmpProposal, !ALLOW_RESTART, - SHIFT_POLICY_HORIZONTAL_LEFT); + position(tasks, availableRect, mTmpProposal, !ALLOW_RESTART, SHIFT_POLICY_HORIZONTAL_LEFT, + result); } - private void positionCenter(TaskRecord task, ArrayList<TaskRecord> tasks, - Rect availableRect, int width, int height) { + private void positionCenter(ArrayList<TaskRecord> tasks, Rect availableRect, int width, + int height, Rect result) { final int defaultFreeformLeft = getFreeformStartLeft(availableRect); final int defaultFreeformTop = getFreeformStartTop(availableRect); mTmpProposal.set(defaultFreeformLeft, defaultFreeformTop, defaultFreeformLeft + width, defaultFreeformTop + height); - position(task, tasks, availableRect, mTmpProposal, ALLOW_RESTART, - SHIFT_POLICY_DIAGONAL_DOWN); + position(tasks, availableRect, mTmpProposal, ALLOW_RESTART, SHIFT_POLICY_DIAGONAL_DOWN, + result); } - private void position(TaskRecord task, ArrayList<TaskRecord> tasks, Rect availableRect, - Rect proposal, boolean allowRestart, int shiftPolicy) { + private void position(ArrayList<TaskRecord> tasks, Rect availableRect, + Rect proposal, boolean allowRestart, int shiftPolicy, Rect result) { mTmpOriginal.set(proposal); boolean restarted = false; while (boundsConflict(proposal, tasks)) { @@ -252,7 +260,7 @@ class LaunchingTaskPositioner { break; } } - task.updateOverrideConfiguration(proposal); + result.set(proposal); } private boolean shiftedTooFar(Rect start, Rect availableRect, int shiftPolicy) { diff --git a/services/core/java/com/android/server/am/LockTaskController.java b/services/core/java/com/android/server/am/LockTaskController.java index 1c094c174e6a..11daf3f9138b 100644 --- a/services/core/java/com/android/server/am/LockTaskController.java +++ b/services/core/java/com/android/server/am/LockTaskController.java @@ -430,7 +430,7 @@ public class LockTaskController { } if (andResume) { - mSupervisor.findTaskToMoveToFrontLocked(task, 0, null, reason, + mSupervisor.findTaskToMoveToFront(task, 0, null, reason, lockTaskModeState != LOCK_TASK_MODE_NONE); mSupervisor.resumeFocusedStackTopActivityLocked(); mWindowManager.executeAppTransition(); diff --git a/services/core/java/com/android/server/am/LockTaskNotify.java b/services/core/java/com/android/server/am/LockTaskNotify.java index 5d6e9b58881f..1dcb0addc18b 100644 --- a/services/core/java/com/android/server/am/LockTaskNotify.java +++ b/services/core/java/com/android/server/am/LockTaskNotify.java @@ -17,8 +17,6 @@ package com.android.server.am; import android.content.Context; -import android.os.Handler; -import android.os.Message; import android.os.SystemClock; import android.util.Slog; import android.view.WindowManager; @@ -27,21 +25,19 @@ import android.widget.Toast; import com.android.internal.R; /** - * Helper to manage showing/hiding a image to notify them that they are entering - * or exiting screen pinning mode. + * Helper to manage showing/hiding a image to notify them that they are entering or exiting screen + * pinning mode. All exposed methods should be called from a handler thread. */ public class LockTaskNotify { private static final String TAG = "LockTaskNotify"; private static final long SHOW_TOAST_MINIMUM_INTERVAL = 1000; private final Context mContext; - private final H mHandler; private Toast mLastToast; private long mLastShowToastTime; public LockTaskNotify(Context context) { mContext = context; - mHandler = new H(); } /** Show "Screen pinned" toast. */ @@ -56,10 +52,6 @@ public class LockTaskNotify { /** Show a toast that describes the gesture the user should use to escape pinned mode. */ void showEscapeToast() { - mHandler.obtainMessage(H.SHOW_ESCAPE_TOAST).sendToTarget(); - } - - private void handleShowEscapeToast() { long showToastTime = SystemClock.elapsedRealtime(); if ((showToastTime - mLastShowToastTime) < SHOW_TOAST_MINIMUM_INTERVAL) { Slog.i(TAG, "Ignore toast since it is requested in very short interval."); @@ -79,17 +71,4 @@ public class LockTaskNotify { toast.show(); return toast; } - - private final class H extends Handler { - private static final int SHOW_ESCAPE_TOAST = 3; - - @Override - public void handleMessage(Message msg) { - switch(msg.what) { - case SHOW_ESCAPE_TOAST: - handleShowEscapeToast(); - break; - } - } - } } diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java index e3f8410a6788..f31ce74a2209 100644 --- a/services/core/java/com/android/server/am/TaskRecord.java +++ b/services/core/java/com/android/server/am/TaskRecord.java @@ -30,7 +30,9 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT; +import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS; +import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME; import static android.content.pm.ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY; import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_ALWAYS; import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_DEFAULT; @@ -74,7 +76,6 @@ import static com.android.server.am.proto.TaskRecordProto.MIN_WIDTH; import static com.android.server.am.proto.TaskRecordProto.ORIG_ACTIVITY; import static com.android.server.am.proto.TaskRecordProto.REAL_ACTIVITY; import static com.android.server.am.proto.TaskRecordProto.RESIZE_MODE; -import static com.android.server.am.proto.TaskRecordProto.RETURN_TO_TYPE; import static com.android.server.am.proto.TaskRecordProto.STACK_ID; import static com.android.server.am.proto.TaskRecordProto.ACTIVITY_TYPE; @@ -172,7 +173,6 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi // Current version of the task record we persist. Used to check if we need to run any upgrade // code. private static final int PERSIST_TASK_VERSION = 1; - private static final String TASK_THUMBNAIL_SUFFIX = "_task_thumbnail"; static final int INVALID_TASK_ID = -1; private static final int INVALID_MIN_SIZE = -1; @@ -187,13 +187,13 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi REPARENT_KEEP_STACK_AT_FRONT, REPARENT_LEAVE_STACK_IN_PLACE }) - public @interface ReparentMoveStackMode {} + @interface ReparentMoveStackMode {} // Moves the stack to the front if it was not at the front - public static final int REPARENT_MOVE_STACK_TO_FRONT = 0; + static final int REPARENT_MOVE_STACK_TO_FRONT = 0; // Only moves the stack to the front if it was focused or front most already - public static final int REPARENT_KEEP_STACK_AT_FRONT = 1; + static final int REPARENT_KEEP_STACK_AT_FRONT = 1; // Do not move the stack as a part of reparenting - public static final int REPARENT_LEAVE_STACK_IN_PLACE = 2; + static final int REPARENT_LEAVE_STACK_IN_PLACE = 2; final int taskId; // Unique identifier for this task. String affinity; // The affinity name for this task, or null; may change identity. @@ -231,9 +231,6 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi private boolean mSupportsPictureInPicture; // Whether or not this task and its activities // support PiP. Based on the {@link ActivityInfo#FLAG_SUPPORTS_PICTURE_IN_PICTURE} flag // of the root activity. - boolean mTemporarilyUnresizable; // Separate flag from mResizeMode used to suppress resize - // changes on a temporary basis. - /** Can't be put in lockTask mode. */ final static int LOCK_TASK_AUTH_DONT_LOCK = 0; /** Can enter app pinning with user approval. Can never start over existing lockTask task. */ @@ -268,12 +265,6 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi * (positive) or back (negative). Absolute value indicates time. */ long mLastTimeMoved = System.currentTimeMillis(); - /** Indication of what to run next when task exits. */ - // TODO: Shouldn't be needed if we have things in visual order. I.e. we stop using stacks or - // have a stack per standard application type... - /*@WindowConfiguration.ActivityType*/ - private int mTaskToReturnTo = ACTIVITY_TYPE_STANDARD; - /** If original intent did not allow relinquishing task identity, save that information */ private boolean mNeverRelinquishIdentity = true; @@ -281,7 +272,6 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi // do not want to delete the stack when the task goes empty. private boolean mReuseTask = false; - private final String mFilename; CharSequence lastDescription; // Last description captured for this item. int mAffiliatedTaskId; // taskId of parent affiliation or self if no parent. @@ -327,8 +317,6 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi TaskRecord(ActivityManagerService service, int _taskId, ActivityInfo info, Intent _intent, IVoiceInteractionSession _voiceSession, IVoiceInteractor _voiceInteractor) { mService = service; - mFilename = String.valueOf(_taskId) + TASK_THUMBNAIL_SUFFIX + - TaskPersister.IMAGE_EXTENSION; userId = UserHandle.getUserId(info.applicationInfo.uid); taskId = _taskId; lastActiveTime = SystemClock.elapsedRealtime(); @@ -348,8 +336,6 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi TaskRecord(ActivityManagerService service, int _taskId, ActivityInfo info, Intent _intent, TaskDescription _taskDescription) { mService = service; - mFilename = String.valueOf(_taskId) + TASK_THUMBNAIL_SUFFIX + - TaskPersister.IMAGE_EXTENSION; userId = UserHandle.getUserId(info.applicationInfo.uid); taskId = _taskId; lastActiveTime = SystemClock.elapsedRealtime(); @@ -368,7 +354,6 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi maxRecents = Math.min(Math.max(info.maxRecents, 1), ActivityManager.getMaxAppRecentsLimitStatic()); - mTaskToReturnTo = ACTIVITY_TYPE_HOME; lastTaskDescription = _taskDescription; touchActiveTime(); mService.mTaskChangeNotificationController.notifyTaskCreated(_taskId, realActivity); @@ -385,8 +370,6 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi int resizeMode, boolean supportsPictureInPicture, boolean _realActivitySuspended, boolean userSetupComplete, int minWidth, int minHeight) { mService = service; - mFilename = String.valueOf(_taskId) + TASK_THUMBNAIL_SUFFIX + - TaskPersister.IMAGE_EXTENSION; taskId = _taskId; intent = _intent; affinityIntent = _affinityIntent; @@ -401,7 +384,6 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi isAvailable = true; autoRemoveRecents = _autoRemoveRecents; askedCompatMode = _askedCompatMode; - mTaskToReturnTo = ACTIVITY_TYPE_HOME; userId = _userId; mUserSetupComplete = userSetupComplete; effectiveUid = _effectiveUid; @@ -707,7 +689,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi } else if (toStackWindowingMode == WINDOWING_MODE_FREEFORM) { Rect bounds = getLaunchBounds(); if (bounds == null) { - toStack.layoutTaskInStack(this, null); + mService.mStackSupervisor.getLaunchingBoundsController().layoutTask(this, null); bounds = mBounds; } kept = resize(bounds, RESIZE_MODE_FORCED, !mightReplaceWindow, deferResume); @@ -904,29 +886,9 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi return this.intent.filterEquals(intent); } - void setTaskToReturnTo(/*@WindowConfiguration.ActivityType*/ int taskToReturnTo) { - mTaskToReturnTo = taskToReturnTo == ACTIVITY_TYPE_RECENTS - ? ACTIVITY_TYPE_HOME : taskToReturnTo; - } - - void setTaskToReturnTo(ActivityRecord source) { - if (source.isActivityTypeRecents()) { - setTaskToReturnTo(ACTIVITY_TYPE_RECENTS); - } else if (source.isActivityTypeAssistant()) { - setTaskToReturnTo(ACTIVITY_TYPE_ASSISTANT); - } - } - - int getTaskToReturnTo() { - return mTaskToReturnTo; - } - - boolean returnsToHomeTask() { - return mTaskToReturnTo == ACTIVITY_TYPE_HOME; - } - - boolean returnsToStandardTask() { - return mTaskToReturnTo == ACTIVITY_TYPE_STANDARD; + boolean returnsToHomeStack() { + final int returnHomeFlags = FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME; + return (intent.getFlags() & returnHomeFlags) == returnHomeFlags; } void setPrevAffiliate(TaskRecord prevAffiliate) { @@ -1447,17 +1409,9 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi " mLockTaskAuth=" + lockTaskAuthToString()); } - boolean isOverHomeStack() { - return mTaskToReturnTo == ACTIVITY_TYPE_HOME; - } - - boolean isOverAssistantStack() { - return mTaskToReturnTo == ACTIVITY_TYPE_ASSISTANT; - } - private boolean isResizeable(boolean checkSupportsPip) { return (mService.mForceResizableActivities || ActivityInfo.isResizeableMode(mResizeMode) - || (checkSupportsPip && mSupportsPictureInPicture)) && !mTemporarilyUnresizable; + || (checkSupportsPip && mSupportsPictureInPicture)); } boolean isResizeable() { @@ -2089,7 +2043,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi if (mLastNonFullscreenBounds != null) { updateOverrideConfiguration(mLastNonFullscreenBounds); } else { - inStack.layoutTaskInStack(this, null); + mService.mStackSupervisor.getLaunchingBoundsController().layoutTask(this, null); } } else { updateOverrideConfiguration(inStack.mBounds); @@ -2164,13 +2118,11 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi pw.print(prefix); pw.print("realActivity="); pw.println(realActivity.flattenToShortString()); } - if (autoRemoveRecents || isPersistable || !isActivityTypeStandard() - || mTaskToReturnTo != ACTIVITY_TYPE_STANDARD || numFullscreen != 0) { + if (autoRemoveRecents || isPersistable || !isActivityTypeStandard() || numFullscreen != 0) { pw.print(prefix); pw.print("autoRemoveRecents="); pw.print(autoRemoveRecents); pw.print(" isPersistable="); pw.print(isPersistable); pw.print(" numFullscreen="); pw.print(numFullscreen); - pw.print(" activityType="); pw.print(getActivityType()); - pw.print(" mTaskToReturnTo="); pw.println(mTaskToReturnTo); + pw.print(" activityType="); pw.println(getActivityType()); } if (rootWasReset || mNeverRelinquishIdentity || mReuseTask || mLockTaskAuth != LOCK_TASK_AUTH_PINNABLE) { @@ -2270,7 +2222,6 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi proto.write(ORIG_ACTIVITY, origActivity.flattenToShortString()); } proto.write(ACTIVITY_TYPE, getActivityType()); - proto.write(RETURN_TO_TYPE, mTaskToReturnTo); proto.write(RESIZE_MODE, mResizeMode); proto.write(FULLSCREEN, mFullscreen); if (mBounds != null) { diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index 4aa8adb9dc78..a0c5cfaa7908 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -250,21 +250,21 @@ class UserController implements Handler.Callback { } void stopRunningUsersLU(int maxRunningUsers) { - int num = mUserLru.size(); + int currentlyRunning = mUserLru.size(); int i = 0; - while (num > maxRunningUsers && i < mUserLru.size()) { + while (currentlyRunning > maxRunningUsers && i < mUserLru.size()) { Integer oldUserId = mUserLru.get(i); UserState oldUss = mStartedUsers.get(oldUserId); if (oldUss == null) { // Shouldn't happen, but be sane if it does. mUserLru.remove(i); - num--; + currentlyRunning--; continue; } if (oldUss.state == UserState.STATE_STOPPING || oldUss.state == UserState.STATE_SHUTDOWN) { // This user is already stopping, doesn't count. - num--; + currentlyRunning--; i++; continue; } @@ -272,16 +272,15 @@ class UserController implements Handler.Callback { // Owner/System user and current user can't be stopped. We count it as running // when it is not a pure system user. if (UserInfo.isSystemOnly(oldUserId)) { - num--; + currentlyRunning--; } i++; continue; } // This is a user to be stopped. - if (stopUsersLU(oldUserId, false, null) != USER_OP_SUCCESS) { - num--; + if (stopUsersLU(oldUserId, false, null) == USER_OP_SUCCESS) { + currentlyRunning--; } - num--; i++; } } diff --git a/services/core/java/com/android/server/connectivity/DefaultNetworkMetrics.java b/services/core/java/com/android/server/connectivity/DefaultNetworkMetrics.java new file mode 100644 index 000000000000..8981db11f7f8 --- /dev/null +++ b/services/core/java/com/android/server/connectivity/DefaultNetworkMetrics.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2017 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.server.connectivity; + +import android.net.LinkProperties; +import android.net.metrics.DefaultNetworkEvent; +import android.net.metrics.IpConnectivityLog; + +import com.android.internal.annotations.GuardedBy; +import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; + +/** + * Tracks events related to the default network for the purpose of default network metrics. + * {@hide} + */ +public class DefaultNetworkMetrics { + + private static final int ROLLING_LOG_SIZE = 64; + + // Event buffer used for metrics upload. The buffer is cleared when events are collected. + @GuardedBy("this") + private final List<DefaultNetworkEvent> mEvents = new ArrayList<>(); + + public synchronized void listEvents(PrintWriter pw) { + long localTimeMs = System.currentTimeMillis(); + for (DefaultNetworkEvent ev : mEvents) { + pw.println(ev); + } + } + + public synchronized void listEventsAsProto(PrintWriter pw) { + for (DefaultNetworkEvent ev : mEvents) { + pw.print(IpConnectivityEventBuilder.toProto(ev)); + } + } + + public synchronized void flushEvents(List<IpConnectivityEvent> out) { + for (DefaultNetworkEvent ev : mEvents) { + out.add(IpConnectivityEventBuilder.toProto(ev)); + } + mEvents.clear(); + } + + public synchronized void logDefaultNetworkEvent( + NetworkAgentInfo newNai, NetworkAgentInfo prevNai) { + DefaultNetworkEvent ev = new DefaultNetworkEvent(); + if (newNai != null) { + ev.netId = newNai.network().netId; + ev.transportTypes = newNai.networkCapabilities.getTransportTypes(); + } + if (prevNai != null) { + ev.prevNetId = prevNai.network().netId; + final LinkProperties lp = prevNai.linkProperties; + ev.prevIPv4 = lp.hasIPv4Address() && lp.hasIPv4DefaultRoute(); + ev.prevIPv6 = lp.hasGlobalIPv6Address() && lp.hasIPv6DefaultRoute(); + } + + mEvents.add(ev); + } +} diff --git a/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java b/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java index 67e72167faa7..3d71ecb50d9f 100644 --- a/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java +++ b/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java @@ -132,6 +132,18 @@ final public class IpConnectivityEventBuilder { return out; } + public static IpConnectivityEvent toProto(DefaultNetworkEvent in) { + IpConnectivityLogClass.DefaultNetworkEvent ev = + new IpConnectivityLogClass.DefaultNetworkEvent(); + ev.networkId = netIdOf(in.netId); + ev.previousNetworkId = netIdOf(in.prevNetId); + ev.transportTypes = in.transportTypes; + ev.previousNetworkIpSupport = ipSupportOf(in); + final IpConnectivityEvent out = buildEvent(in.netId, 0, null); + out.setDefaultNetworkEvent(ev); + return out; + } + private static IpConnectivityEvent buildEvent(int netId, long transports, String ifname) { final IpConnectivityEvent ev = new IpConnectivityEvent(); ev.networkId = netId; @@ -164,11 +176,6 @@ final public class IpConnectivityEventBuilder { return true; } - if (in instanceof DefaultNetworkEvent) { - setDefaultNetworkEvent(out, (DefaultNetworkEvent) in); - return true; - } - if (in instanceof NetworkEvent) { setNetworkEvent(out, (NetworkEvent) in); return true; @@ -225,16 +232,6 @@ final public class IpConnectivityEventBuilder { out.setIpReachabilityEvent(ipReachabilityEvent); } - private static void setDefaultNetworkEvent(IpConnectivityEvent out, DefaultNetworkEvent in) { - IpConnectivityLogClass.DefaultNetworkEvent defaultNetworkEvent = - new IpConnectivityLogClass.DefaultNetworkEvent(); - defaultNetworkEvent.networkId = netIdOf(in.netId); - defaultNetworkEvent.previousNetworkId = netIdOf(in.prevNetId); - defaultNetworkEvent.transportTypes = in.transportTypes; - defaultNetworkEvent.previousNetworkIpSupport = ipSupportOf(in); - out.setDefaultNetworkEvent(defaultNetworkEvent); - } - private static void setNetworkEvent(IpConnectivityEvent out, NetworkEvent in) { IpConnectivityLogClass.NetworkEvent networkEvent = new IpConnectivityLogClass.NetworkEvent(); diff --git a/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java b/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java index f2445fa36006..24217e6eef0b 100644 --- a/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java +++ b/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java @@ -32,12 +32,15 @@ import android.text.format.DateUtils; import android.util.ArrayMap; import android.util.Base64; import android.util.Log; + import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.RingBuffer; import com.android.internal.util.TokenBucket; +import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent; + import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; @@ -112,6 +115,9 @@ final public class IpConnectivityMetrics extends SystemService { private final ToIntFunction<Context> mCapacityGetter; + @VisibleForTesting + final DefaultNetworkMetrics mDefaultNetworkMetrics = new DefaultNetworkMetrics(); + public IpConnectivityMetrics(Context ctx, ToIntFunction<Context> capacityGetter) { super(ctx); mCapacityGetter = capacityGetter; @@ -135,6 +141,8 @@ final public class IpConnectivityMetrics extends SystemService { publishBinderService(SERVICE_NAME, impl); publishBinderService(mNetdListener.SERVICE_NAME, mNetdListener); + + LocalServices.addService(Logger.class, new LoggerImpl()); } } @@ -188,6 +196,8 @@ final public class IpConnectivityMetrics extends SystemService { final List<IpConnectivityEvent> protoEvents = IpConnectivityEventBuilder.toProto(events); + mDefaultNetworkMetrics.flushEvents(protoEvents); + if (mNetdListener != null) { mNetdListener.flushStatistics(protoEvents); } @@ -228,6 +238,7 @@ final public class IpConnectivityMetrics extends SystemService { if (mNetdListener != null) { mNetdListener.listAsProtos(pw); } + mDefaultNetworkMetrics.listEventsAsProto(pw); return; } @@ -237,6 +248,7 @@ final public class IpConnectivityMetrics extends SystemService { if (mNetdListener != null) { mNetdListener.list(pw); } + mDefaultNetworkMetrics.listEvents(pw); } /** @@ -254,6 +266,7 @@ final public class IpConnectivityMetrics extends SystemService { if (mNetdListener != null) { mNetdListener.list(pw); } + mDefaultNetworkMetrics.listEvents(pw); } private void cmdStats(FileDescriptor fd, PrintWriter pw, String[] args) { @@ -366,4 +379,15 @@ final public class IpConnectivityMetrics extends SystemService { map.put(ApfProgramEvent.class, new TokenBucket((int)DateUtils.MINUTE_IN_MILLIS, 50)); return map; } + + /** Direct non-Binder interface for event producer clients within the system servers. */ + public interface Logger { + DefaultNetworkMetrics defaultNetworkMetrics(); + } + + private class LoggerImpl implements Logger { + public DefaultNetworkMetrics defaultNetworkMetrics() { + return mDefaultNetworkMetrics; + } + } } diff --git a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java index 6206dfcd6622..05c6e69c1895 100644 --- a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java +++ b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java @@ -27,6 +27,7 @@ import android.net.metrics.ConnectStats; import android.net.metrics.DnsEvent; import android.net.metrics.INetdEventListener; import android.net.metrics.IpConnectivityLog; +import android.net.metrics.NetworkMetrics; import android.net.metrics.WakeupEvent; import android.net.metrics.WakeupStats; import android.os.RemoteException; @@ -34,6 +35,7 @@ import android.text.format.DateUtils; import android.util.Log; import android.util.ArrayMap; import android.util.SparseArray; + import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.BitUtils; @@ -41,10 +43,11 @@ import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.RingBuffer; import com.android.internal.util.TokenBucket; import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent; + import java.io.PrintWriter; +import java.util.ArrayList; import java.util.List; -import java.util.function.Function; -import java.util.function.IntFunction; +import java.util.StringJoiner; /** * Implementation of the INetdEventListener interface. @@ -57,13 +60,13 @@ public class NetdEventListenerService extends INetdEventListener.Stub { private static final boolean DBG = false; private static final boolean VDBG = false; - private static final int INITIAL_DNS_BATCH_SIZE = 100; - // Rate limit connect latency logging to 1 measurement per 15 seconds (5760 / day) with maximum // bursts of 5000 measurements. private static final int CONNECT_LATENCY_BURST_LIMIT = 5000; private static final int CONNECT_LATENCY_FILL_RATE = 15 * (int) DateUtils.SECOND_IN_MILLIS; - private static final int CONNECT_LATENCY_MAXIMUM_RECORDS = 20000; + + private static final long METRICS_SNAPSHOT_SPAN_MS = 5 * DateUtils.MINUTE_IN_MILLIS; + private static final int METRICS_SNAPSHOT_BUFFER_SIZE = 48; // 4 hours @VisibleForTesting static final int WAKEUP_EVENT_BUFFER_LENGTH = 1024; @@ -72,11 +75,15 @@ public class NetdEventListenerService extends INetdEventListener.Stub { @VisibleForTesting static final String WAKEUP_EVENT_IFACE_PREFIX = "iface:"; - // Sparse arrays of DNS and connect events, grouped by net id. + // Array of aggregated DNS and connect events sent by netd, grouped by net id. + @GuardedBy("this") + private final SparseArray<NetworkMetrics> mNetworkMetrics = new SparseArray<>(); + @GuardedBy("this") - private final SparseArray<DnsEvent> mDnsEvents = new SparseArray<>(); + private final RingBuffer<NetworkMetricsSnapshot> mNetworkMetricsSnapshots = + new RingBuffer<>(NetworkMetricsSnapshot.class, METRICS_SNAPSHOT_BUFFER_SIZE); @GuardedBy("this") - private final SparseArray<ConnectStats> mConnectEvents = new SparseArray<>(); + private long mLastSnapshot = 0; // Array of aggregated wakeup event stats, grouped by interface name. @GuardedBy("this") @@ -84,7 +91,7 @@ public class NetdEventListenerService extends INetdEventListener.Stub { // Ring buffer array for storing packet wake up events sent by Netd. @GuardedBy("this") private final RingBuffer<WakeupEvent> mWakeupEvents = - new RingBuffer(WakeupEvent.class, WAKEUP_EVENT_BUFFER_LENGTH); + new RingBuffer<>(WakeupEvent.class, WAKEUP_EVENT_BUFFER_LENGTH); private final ConnectivityManager mCm; @@ -116,6 +123,41 @@ public class NetdEventListenerService extends INetdEventListener.Stub { mCm = cm; } + private static long projectSnapshotTime(long timeMs) { + return (timeMs / METRICS_SNAPSHOT_SPAN_MS) * METRICS_SNAPSHOT_SPAN_MS; + } + + private NetworkMetrics getMetricsForNetwork(long timeMs, int netId) { + collectPendingMetricsSnapshot(timeMs); + NetworkMetrics metrics = mNetworkMetrics.get(netId); + if (metrics == null) { + // TODO: allow to change transport for a given netid. + metrics = new NetworkMetrics(netId, getTransports(netId), mConnectTb); + mNetworkMetrics.put(netId, metrics); + } + return metrics; + } + + private NetworkMetricsSnapshot[] getNetworkMetricsSnapshots() { + collectPendingMetricsSnapshot(System.currentTimeMillis()); + return mNetworkMetricsSnapshots.toArray(); + } + + private void collectPendingMetricsSnapshot(long timeMs) { + // Detects time differences larger than the snapshot collection period. + // This is robust against clock jumps and long inactivity periods. + if (Math.abs(timeMs - mLastSnapshot) <= METRICS_SNAPSHOT_SPAN_MS) { + return; + } + mLastSnapshot = projectSnapshotTime(timeMs); + NetworkMetricsSnapshot snapshot = + NetworkMetricsSnapshot.collect(mLastSnapshot, mNetworkMetrics); + if (snapshot.stats.isEmpty()) { + return; + } + mNetworkMetricsSnapshots.append(snapshot); + } + @Override // Called concurrently by multiple binder threads. // This method must not block or perform long-running operations. @@ -124,15 +166,10 @@ public class NetdEventListenerService extends INetdEventListener.Stub { throws RemoteException { maybeVerboseLog("onDnsEvent(%d, %d, %d, %dms)", netId, eventType, returnCode, latencyMs); - DnsEvent dnsEvent = mDnsEvents.get(netId); - if (dnsEvent == null) { - dnsEvent = makeDnsEvent(netId); - mDnsEvents.put(netId, dnsEvent); - } - dnsEvent.addResult((byte) eventType, (byte) returnCode, latencyMs); + long timestamp = System.currentTimeMillis(); + getMetricsForNetwork(timestamp, netId).addDnsResult(eventType, returnCode, latencyMs); if (mNetdEventCallback != null) { - long timestamp = System.currentTimeMillis(); mNetdEventCallback.onDnsEvent(hostname, ipAddresses, ipAddressesCount, timestamp, uid); } } @@ -144,15 +181,11 @@ public class NetdEventListenerService extends INetdEventListener.Stub { int port, int uid) throws RemoteException { maybeVerboseLog("onConnectEvent(%d, %d, %dms)", netId, error, latencyMs); - ConnectStats connectStats = mConnectEvents.get(netId); - if (connectStats == null) { - connectStats = makeConnectStats(netId); - mConnectEvents.put(netId, connectStats); - } - connectStats.addEvent(error, latencyMs, ipAddr); + long timestamp = System.currentTimeMillis(); + getMetricsForNetwork(timestamp, netId).addConnectResult(error, latencyMs, ipAddr); if (mNetdEventCallback != null) { - mNetdEventCallback.onConnectEvent(ipAddr, port, System.currentTimeMillis(), uid); + mNetdEventCallback.onConnectEvent(ipAddr, port, timestamp, uid); } } @@ -189,11 +222,24 @@ public class NetdEventListenerService extends INetdEventListener.Stub { } public synchronized void flushStatistics(List<IpConnectivityEvent> events) { - flushProtos(events, mConnectEvents, IpConnectivityEventBuilder::toProto); - flushProtos(events, mDnsEvents, IpConnectivityEventBuilder::toProto); + for (int i = 0; i < mNetworkMetrics.size(); i++) { + ConnectStats stats = mNetworkMetrics.valueAt(i).connectMetrics; + if (stats.eventCount == 0) { + continue; + } + events.add(IpConnectivityEventBuilder.toProto(stats)); + } + for (int i = 0; i < mNetworkMetrics.size(); i++) { + DnsEvent ev = mNetworkMetrics.valueAt(i).dnsMetrics; + if (ev.eventCount == 0) { + continue; + } + events.add(IpConnectivityEventBuilder.toProto(ev)); + } for (int i = 0; i < mWakeupStats.size(); i++) { events.add(IpConnectivityEventBuilder.toProto(mWakeupStats.valueAt(i))); } + mNetworkMetrics.clear(); mWakeupStats.clear(); } @@ -206,8 +252,15 @@ public class NetdEventListenerService extends INetdEventListener.Stub { } public synchronized void list(PrintWriter pw) { - listEvents(pw, mConnectEvents, (x) -> x, "\n"); - listEvents(pw, mDnsEvents, (x) -> x, "\n"); + for (int i = 0; i < mNetworkMetrics.size(); i++) { + pw.println(mNetworkMetrics.valueAt(i).connectMetrics); + } + for (int i = 0; i < mNetworkMetrics.size(); i++) { + pw.println(mNetworkMetrics.valueAt(i).dnsMetrics); + } + for (NetworkMetricsSnapshot s : getNetworkMetricsSnapshots()) { + pw.println(s); + } for (int i = 0; i < mWakeupStats.size(); i++) { pw.println(mWakeupStats.valueAt(i)); } @@ -217,41 +270,17 @@ public class NetdEventListenerService extends INetdEventListener.Stub { } public synchronized void listAsProtos(PrintWriter pw) { - listEvents(pw, mConnectEvents, IpConnectivityEventBuilder::toProto, ""); - listEvents(pw, mDnsEvents, IpConnectivityEventBuilder::toProto, ""); - for (int i = 0; i < mWakeupStats.size(); i++) { - pw.print(IpConnectivityEventBuilder.toProto(mWakeupStats.valueAt(i))); + for (int i = 0; i < mNetworkMetrics.size(); i++) { + pw.print(IpConnectivityEventBuilder.toProto(mNetworkMetrics.valueAt(i).connectMetrics)); } - } - - private static <T> void flushProtos(List<IpConnectivityEvent> out, SparseArray<T> in, - Function<T, IpConnectivityEvent> mapper) { - for (int i = 0; i < in.size(); i++) { - out.add(mapper.apply(in.valueAt(i))); + for (int i = 0; i < mNetworkMetrics.size(); i++) { + pw.print(IpConnectivityEventBuilder.toProto(mNetworkMetrics.valueAt(i).dnsMetrics)); } - in.clear(); - } - - private static <T> void listEvents( - PrintWriter pw, SparseArray<T> events, Function<T, Object> mapper, String separator) { - // Proto derived Classes have toString method that adds a \n at the end. - // Let the caller control that by passing in the line separator explicitly. - for (int i = 0; i < events.size(); i++) { - pw.print(mapper.apply(events.valueAt(i))); - pw.print(separator); + for (int i = 0; i < mWakeupStats.size(); i++) { + pw.print(IpConnectivityEventBuilder.toProto(mWakeupStats.valueAt(i))); } } - private ConnectStats makeConnectStats(int netId) { - long transports = getTransports(netId); - return new ConnectStats(netId, transports, mConnectTb, CONNECT_LATENCY_MAXIMUM_RECORDS); - } - - private DnsEvent makeDnsEvent(int netId) { - long transports = getTransports(netId); - return new DnsEvent(netId, transports, INITIAL_DNS_BATCH_SIZE); - } - private long getTransports(int netId) { // TODO: directly query ConnectivityService instead of going through Binder interface. NetworkCapabilities nc = mCm.getNetworkCapabilities(new Network(netId)); @@ -268,4 +297,32 @@ public class NetdEventListenerService extends INetdEventListener.Stub { private static void maybeVerboseLog(String s, Object... args) { if (VDBG) Log.d(TAG, String.format(s, args)); } + + /** Helper class for buffering summaries of NetworkMetrics at regular time intervals */ + static class NetworkMetricsSnapshot { + + public long timeMs; + public List<NetworkMetrics.Summary> stats = new ArrayList<>(); + + static NetworkMetricsSnapshot collect(long timeMs, SparseArray<NetworkMetrics> networkMetrics) { + NetworkMetricsSnapshot snapshot = new NetworkMetricsSnapshot(); + snapshot.timeMs = timeMs; + for (int i = 0; i < networkMetrics.size(); i++) { + NetworkMetrics.Summary s = networkMetrics.valueAt(i).getPendingStats(); + if (s != null) { + snapshot.stats.add(s); + } + } + return snapshot; + } + + @Override + public String toString() { + StringJoiner j = new StringJoiner(", "); + for (NetworkMetrics.Summary s : stats) { + j.add(s.toString()); + } + return String.format("%tT.%tL: %s", timeMs, timeMs, j.toString()); + } + } } diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java index d7cd81ff3c5f..59870cb97fc9 100644 --- a/services/core/java/com/android/server/connectivity/Tethering.java +++ b/services/core/java/com/android/server/connectivity/Tethering.java @@ -28,6 +28,7 @@ import static android.net.wifi.WifiManager.IFACE_IP_MODE_LOCAL_ONLY; import static android.net.wifi.WifiManager.IFACE_IP_MODE_TETHERED; import static android.net.wifi.WifiManager.IFACE_IP_MODE_UNSPECIFIED; import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLED; +import static android.telephony.CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED; import static com.android.server.ConnectivityService.SHORT_ARG; import android.app.Notification; @@ -60,6 +61,7 @@ import android.net.NetworkUtils; import android.net.RouteInfo; import android.net.util.PrefixUtils; import android.net.util.SharedLog; +import android.net.util.VersionedBroadcastListener; import android.net.wifi.WifiManager; import android.os.Binder; import android.os.Bundle; @@ -68,6 +70,7 @@ import android.os.INetworkManagementService; import android.os.Looper; import android.os.Message; import android.os.Parcel; +import android.os.PersistableBundle; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.UserHandle; @@ -184,6 +187,8 @@ public class Tethering extends BaseNetworkObserver { // TODO: Figure out how to merge this and other downstream-tracking objects // into a single coherent structure. private final HashSet<TetherInterfaceStateMachine> mForwardedDownstreams; + private final VersionedBroadcastListener mCarrierConfigChange; + // TODO: Delete SimChangeListener; it's obsolete. private final SimChangeListener mSimChange; private volatile TetheringConfiguration mConfig; @@ -224,11 +229,26 @@ public class Tethering extends BaseNetworkObserver { mUpstreamNetworkMonitor = new UpstreamNetworkMonitor( mContext, mTetherMasterSM, mLog, TetherMasterSM.EVENT_UPSTREAM_CALLBACK); mForwardedDownstreams = new HashSet<>(); + + IntentFilter filter = new IntentFilter(); + filter.addAction(ACTION_CARRIER_CONFIG_CHANGED); + mCarrierConfigChange = new VersionedBroadcastListener( + "CarrierConfigChangeListener", mContext, smHandler, filter, + (Intent ignored) -> { + mLog.log("OBSERVED carrier config change"); + reevaluateSimCardProvisioning(); + }); + // TODO: Remove SimChangeListener altogether. For now, we retain it + // for logging purposes in case we need to debug something that might + // be related to changing signals from ACTION_SIM_STATE_CHANGED to + // ACTION_CARRIER_CONFIG_CHANGED. mSimChange = new SimChangeListener( - mContext, smHandler, () -> reevaluateSimCardProvisioning()); + mContext, smHandler, () -> { + mLog.log("OBSERVED SIM card change"); + }); mStateReceiver = new StateReceiver(); - IntentFilter filter = new IntentFilter(); + filter = new IntentFilter(); filter.addAction(UsbManager.ACTION_USB_STATE); filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); filter.addAction(WifiManager.WIFI_AP_STATE_CHANGED_ACTION); @@ -364,18 +384,30 @@ public class Tethering extends BaseNetworkObserver { return false; } + if (carrierConfigAffirmsEntitlementCheckNotRequired()) { + return false; + } + return (provisionApp.length == 2); + } + + // The logic here is aimed solely at confirming that a CarrierConfig exists + // and affirms that entitlement checks are not required. + // + // TODO: find a better way to express this, or alter the checking process + // entirely so that this is more intuitive. + private boolean carrierConfigAffirmsEntitlementCheckNotRequired() { // Check carrier config for entitlement checks final CarrierConfigManager configManager = (CarrierConfigManager) mContext .getSystemService(Context.CARRIER_CONFIG_SERVICE); - if (configManager != null && configManager.getConfig() != null) { - // we do have a CarrierConfigManager and it has a config. - boolean isEntitlementCheckRequired = configManager.getConfig().getBoolean( - CarrierConfigManager.KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL); - if (!isEntitlementCheckRequired) { - return false; - } - } - return (provisionApp.length == 2); + if (configManager == null) return false; + + final PersistableBundle carrierConfig = configManager.getConfig(); + if (carrierConfig == null) return false; + + // A CarrierConfigManager was found and it has a config. + final boolean isEntitlementCheckRequired = carrierConfig.getBoolean( + CarrierConfigManager.KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL); + return !isEntitlementCheckRequired; } // Used by the SIM card change observation code. @@ -818,6 +850,7 @@ public class Tethering extends BaseNetworkObserver { } else if (action.equals(WifiManager.WIFI_AP_STATE_CHANGED_ACTION)) { handleWifiApAction(intent); } else if (action.equals(Intent.ACTION_CONFIGURATION_CHANGED)) { + mLog.log("OBSERVED configuration changed"); updateConfiguration(); } } @@ -1192,6 +1225,7 @@ public class Tethering extends BaseNetworkObserver { private void reevaluateSimCardProvisioning() { if (!hasMobileHotspotProvisionApp()) return; + if (carrierConfigAffirmsEntitlementCheckNotRequired()) return; ArrayList<Integer> tethered = new ArrayList<>(); synchronized (mPublicSync) { @@ -1559,6 +1593,7 @@ public class Tethering extends BaseNetworkObserver { return; } + mCarrierConfigChange.startListening(); mSimChange.startListening(); mUpstreamNetworkMonitor.start(); @@ -1576,6 +1611,7 @@ public class Tethering extends BaseNetworkObserver { mOffload.stop(); mUpstreamNetworkMonitor.stop(); mSimChange.stopListening(); + mCarrierConfigChange.stopListening(); notifyDownstreamsOfNewUpstreamIface(null); handleNewUpstreamNetworkState(null); } diff --git a/services/core/java/com/android/server/connectivity/tethering/SimChangeListener.java b/services/core/java/com/android/server/connectivity/tethering/SimChangeListener.java index 3e60f9f6c97a..33c9355ae64b 100644 --- a/services/core/java/com/android/server/connectivity/tethering/SimChangeListener.java +++ b/services/core/java/com/android/server/connectivity/tethering/SimChangeListener.java @@ -23,12 +23,15 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.net.util.VersionedBroadcastListener; +import android.net.util.VersionedBroadcastListener.IntentCallback; import android.os.Handler; import android.util.Log; import com.android.internal.telephony.TelephonyIntents; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; /** @@ -37,88 +40,40 @@ import java.util.concurrent.atomic.AtomicInteger; * * @hide */ -public class SimChangeListener { +public class SimChangeListener extends VersionedBroadcastListener { private static final String TAG = SimChangeListener.class.getSimpleName(); private static final boolean DBG = false; - private final Context mContext; - private final Handler mTarget; - private final AtomicInteger mSimBcastGenerationNumber; - private final Runnable mCallback; - private BroadcastReceiver mBroadcastReceiver; - public SimChangeListener(Context ctx, Handler handler, Runnable onSimCardLoadedCallback) { - mContext = ctx; - mTarget = handler; - mCallback = onSimCardLoadedCallback; - mSimBcastGenerationNumber = new AtomicInteger(0); - } - - public int generationNumber() { - return mSimBcastGenerationNumber.get(); + super(TAG, ctx, handler, makeIntentFilter(), makeCallback(onSimCardLoadedCallback)); } - public void startListening() { - if (DBG) Log.d(TAG, "startListening for SIM changes"); - - if (mBroadcastReceiver != null) return; - - mBroadcastReceiver = new SimChangeBroadcastReceiver( - mSimBcastGenerationNumber.incrementAndGet()); + private static IntentFilter makeIntentFilter() { final IntentFilter filter = new IntentFilter(); filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED); - - mContext.registerReceiver(mBroadcastReceiver, filter, null, mTarget); + return filter; } - public void stopListening() { - if (DBG) Log.d(TAG, "stopListening for SIM changes"); - - if (mBroadcastReceiver == null) return; - - mSimBcastGenerationNumber.incrementAndGet(); - mContext.unregisterReceiver(mBroadcastReceiver); - mBroadcastReceiver = null; - } - - private boolean isSimCardLoaded(String state) { - return INTENT_VALUE_ICC_LOADED.equals(state); - } - - private class SimChangeBroadcastReceiver extends BroadcastReceiver { - // used to verify this receiver is still current - final private int mGenerationNumber; - - // used to check the sim state transition from non-loaded to loaded - private boolean mSimNotLoadedSeen = false; - - public SimChangeBroadcastReceiver(int generationNumber) { - mGenerationNumber = generationNumber; - } - - @Override - public void onReceive(Context context, Intent intent) { - final int currentGenerationNumber = mSimBcastGenerationNumber.get(); - - if (DBG) { - Log.d(TAG, "simchange mGenerationNumber=" + mGenerationNumber + - ", current generationNumber=" + currentGenerationNumber); - } - if (mGenerationNumber != currentGenerationNumber) return; - - final String state = intent.getStringExtra(INTENT_KEY_ICC_STATE); - Log.d(TAG, "got Sim changed to state " + state + ", mSimNotLoadedSeen=" + - mSimNotLoadedSeen); - - if (!isSimCardLoaded(state)) { - mSimNotLoadedSeen = true; - return; - } - - if (mSimNotLoadedSeen) { - mSimNotLoadedSeen = false; - mCallback.run(); + private static Consumer<Intent> makeCallback(Runnable onSimCardLoadedCallback) { + return new Consumer<Intent>() { + private boolean mSimNotLoadedSeen = false; + + @Override + public void accept(Intent intent) { + final String state = intent.getStringExtra(INTENT_KEY_ICC_STATE); + Log.d(TAG, "got Sim changed to state " + state + ", mSimNotLoadedSeen=" + + mSimNotLoadedSeen); + + if (!INTENT_VALUE_ICC_LOADED.equals(state)) { + mSimNotLoadedSeen = true; + return; + } + + if (mSimNotLoadedSeen) { + mSimNotLoadedSeen = false; + onSimCardLoadedCallback.run(); + } } - } + }; } } diff --git a/services/core/java/com/android/server/job/JobServiceContext.java b/services/core/java/com/android/server/job/JobServiceContext.java index d3fd3a992a31..ae01c433fc8d 100644 --- a/services/core/java/com/android/server/job/JobServiceContext.java +++ b/services/core/java/com/android/server/job/JobServiceContext.java @@ -216,7 +216,7 @@ public final class JobServiceContext implements ServiceConnection { final JobInfo ji = job.getJob(); mParams = new JobParameters(mRunningCallback, job.getJobId(), ji.getExtras(), ji.getTransientExtras(), ji.getClipData(), ji.getClipGrantFlags(), - isDeadlineExpired, triggeredUris, triggeredAuthorities); + isDeadlineExpired, triggeredUris, triggeredAuthorities, job.network); mExecutionStartTimeElapsed = SystemClock.elapsedRealtime(); // Once we'e begun executing a job, we by definition no longer care whether diff --git a/services/core/java/com/android/server/job/controllers/ConnectivityController.java b/services/core/java/com/android/server/job/controllers/ConnectivityController.java index 78367fe97a54..c928c07be983 100644 --- a/services/core/java/com/android/server/job/controllers/ConnectivityController.java +++ b/services/core/java/com/android/server/job/controllers/ConnectivityController.java @@ -125,6 +125,11 @@ public final class ConnectivityController extends StateController implements changed |= jobStatus.setUnmeteredConstraintSatisfied(unmetered); changed |= jobStatus.setNotRoamingConstraintSatisfied(notRoaming); + // Pass along the evaluated network for job to use; prevents race + // conditions as default routes change over time, and opens the door to + // using non-default routes. + jobStatus.network = network; + // Track system-uid connected/validated as a general reportable proxy for the // overall state of connectivity constraint satisfiability. if (jobUid == Process.SYSTEM_UID) { diff --git a/services/core/java/com/android/server/job/controllers/JobStatus.java b/services/core/java/com/android/server/job/controllers/JobStatus.java index 23caa8cfa701..1a27c0afb5ea 100644 --- a/services/core/java/com/android/server/job/controllers/JobStatus.java +++ b/services/core/java/com/android/server/job/controllers/JobStatus.java @@ -22,6 +22,7 @@ import android.app.job.JobInfo; import android.app.job.JobWorkItem; import android.content.ClipData; import android.content.ComponentName; +import android.net.Network; import android.net.Uri; import android.os.RemoteException; import android.os.SystemClock; @@ -167,6 +168,7 @@ public final class JobStatus { // These are filled in by controllers when preparing for execution. public ArraySet<Uri> changedUris; public ArraySet<String> changedAuthorities; + public Network network; public int lastEvaluatedPriority; @@ -1101,6 +1103,9 @@ public final class JobStatus { } } } + if (network != null) { + pw.print(prefix); pw.print("Network: "); pw.println(network); + } if (pendingWork != null && pendingWork.size() > 0) { pw.print(prefix); pw.println("Pending work:"); for (int i = 0; i < pendingWork.size(); i++) { diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java index e41c17df8ca1..4cf35bc4accb 100644 --- a/services/core/java/com/android/server/location/GnssLocationProvider.java +++ b/services/core/java/com/android/server/location/GnssLocationProvider.java @@ -669,9 +669,11 @@ public class GnssLocationProvider implements LocationProviderInterface { for (String item : configValues) { if (DEBUG) Log.d(TAG, "GpsParamsResource: " + item); // We need to support "KEY =", but not "=VALUE". - String[] split = item.split("="); - if (split.length == 2) { - properties.setProperty(split[0].trim().toUpperCase(), split[1]); + int index = item.indexOf("="); + if (index > 0 && index + 1 < item.length()) { + String key = item.substring(0, index); + String value = item.substring(index + 1); + properties.setProperty(key.trim().toUpperCase(), value); } else { Log.w(TAG, "malformed contents: " + item); } diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java index 6253857d1aa4..03f662a49762 100644 --- a/services/core/java/com/android/server/pm/OtaDexoptService.java +++ b/services/core/java/com/android/server/pm/OtaDexoptService.java @@ -310,7 +310,7 @@ public class OtaDexoptService extends IOtaDexopt.Stub { collectingInstaller, mPackageManagerService.mInstallLock, mContext); String[] libraryDependencies = pkg.usesLibraryFiles; - if (pkg.isSystemApp()) { + if (pkg.isSystem()) { // For system apps, we want to avoid classpaths checks. libraryDependencies = NO_LIBRARIES; } diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java index cf0ffbb1c2e7..e7b4abb28a4f 100644 --- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java +++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java @@ -110,7 +110,7 @@ public class PackageDexOptimizer { } // We do not dexopt a priv-app package when pm.dexopt.priv-apps is false. - if (pkg.isPrivilegedApp()) { + if (pkg.isPrivileged()) { return SystemProperties.getBoolean("pm.dexopt.priv-apps", true); } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 391deb746e5c..acec2cb0cc79 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -397,7 +397,7 @@ public class PackageManagerService extends IPackageManager.Stub static final boolean DEBUG_UPGRADE = false; static final boolean DEBUG_DOMAIN_VERIFICATION = false; private static final boolean DEBUG_BACKUP = false; - private static final boolean DEBUG_INSTALL = false; + public static final boolean DEBUG_INSTALL = false; public static final boolean DEBUG_REMOVE = false; private static final boolean DEBUG_BROADCASTS = false; private static final boolean DEBUG_SHOW_INFO = false; @@ -522,7 +522,7 @@ public class PackageManagerService extends IPackageManager.Stub */ private static final int DEFAULT_VERIFICATION_RESPONSE = PackageManager.VERIFICATION_ALLOW; - static final String PLATFORM_PACKAGE_NAME = "android"; + public static final String PLATFORM_PACKAGE_NAME = "android"; static final String DEFAULT_CONTAINER_PACKAGE = "com.android.defcontainer"; @@ -542,18 +542,6 @@ public class PackageManagerService extends IPackageManager.Stub private static final String VENDOR_OVERLAY_DIR = "/vendor/overlay"; - /** Permission grant: not grant the permission. */ - private static final int GRANT_DENIED = 1; - - /** Permission grant: grant the permission as an install permission. */ - private static final int GRANT_INSTALL = 2; - - /** Permission grant: grant the permission as a runtime one. */ - private static final int GRANT_RUNTIME = 3; - - /** Permission grant: grant as runtime a permission that was granted as an install time one. */ - private static final int GRANT_UPGRADE = 4; - /** Canonical intent used to identify what counts as a "web browser" app */ private static final Intent sBrowserIntent; static { @@ -753,9 +741,6 @@ public class PackageManagerService extends IPackageManager.Stub PackageManagerInternal.ExternalSourcesPolicy mExternalSourcesPolicy; - // System configuration read by SystemConfig. - final int[] mGlobalGids; - final SparseArray<ArraySet<String>> mSystemPermissions; @GuardedBy("mAvailableFeatures") final ArrayMap<String, FeatureInfo> mAvailableFeatures; @@ -938,10 +923,6 @@ public class PackageManagerService extends IPackageManager.Stub final ArrayMap<ComponentName, PackageParser.Instrumentation> mInstrumentation = new ArrayMap<ComponentName, PackageParser.Instrumentation>(); - // Mapping from permission names to info about them. - final ArrayMap<String, PackageParser.PermissionGroup> mPermissionGroups = - new ArrayMap<String, PackageParser.PermissionGroup>(); - // Packages whose data we have transfered into another package, thus // should no longer exist. final ArraySet<String> mTransferedPackages = new ArraySet<String>(); @@ -1016,8 +997,6 @@ public class PackageManagerService extends IPackageManager.Stub private File mCacheDir; - private ArraySet<String> mPrivappPermissionsViolations; - private Future<?> mPrepareAppDataFuture; private static class IFVerificationParams { @@ -1405,8 +1384,6 @@ public class PackageManagerService extends IPackageManager.Stub final @NonNull String mServicesSystemSharedLibraryPackageName; final @NonNull String mSharedSystemSharedLibraryPackageName; - final boolean mPermissionReviewRequired; - private final PackageUsage mPackageUsage = new PackageUsage(); private final CompilerStats mCompilerStats = new CompilerStats(); @@ -1928,9 +1905,11 @@ public class PackageManagerService extends IPackageManager.Stub } } @Override - public void onPermissionUpdated(int userId) { + public void onPermissionUpdated(int[] updatedUserIds, boolean sync) { synchronized (mPackages) { - mSettings.writeRuntimePermissionsForUserLPr(userId, false); + for (int userId : updatedUserIds) { + mSettings.writeRuntimePermissionsForUserLPr(userId, sync); + } } } @Override @@ -2363,9 +2342,6 @@ public class PackageManagerService extends IPackageManager.Stub mContext = context; - mPermissionReviewRequired = context.getResources().getBoolean( - R.bool.config_permissionReviewRequired); - mFactoryTest = factoryTest; mOnlyCore = onlyCore; mMetrics = new DisplayMetrics(); @@ -2434,8 +2410,6 @@ public class PackageManagerService extends IPackageManager.Stub Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "get system config"); SystemConfig systemConfig = SystemConfig.getInstance(); - mGlobalGids = systemConfig.getGlobalGids(); - mSystemPermissions = systemConfig.getSystemPermissions(); mAvailableFeatures = systemConfig.getAvailableFeatures(); Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); @@ -2872,13 +2846,14 @@ public class PackageManagerService extends IPackageManager.Stub // cases get permissions that the user didn't initially explicitly // allow... it would be nice to have some better way to handle // this situation. - int updateFlags = UPDATE_PERMISSIONS_ALL; - if (ver.sdkVersion != mSdkVersion) { + final boolean sdkUpdated = (ver.sdkVersion != mSdkVersion); + if (sdkUpdated) { Slog.i(TAG, "Platform changed from " + ver.sdkVersion + " to " + mSdkVersion + "; regranting permissions for internal storage"); - updateFlags |= UPDATE_PERMISSIONS_REPLACE_PKG | UPDATE_PERMISSIONS_REPLACE_ALL; } - updatePermissionsLocked(null, null, StorageManager.UUID_PRIVATE_INTERNAL, updateFlags); + mPermissionManager.updateAllPermissions( + StorageManager.UUID_PRIVATE_INTERNAL, sdkUpdated, mPackages.values(), + mPermissionCallback); ver.sdkVersion = mSdkVersion; // If this is the first boot or an update from pre-M, and it is a normal @@ -3597,7 +3572,7 @@ public class PackageManagerService extends IPackageManager.Stub for (String packageName : packages) { PackageParser.Package pkg = mPackages.get(packageName); if (pkg != null) { - if (!pkg.isSystemApp()) { + if (!pkg.isSystem()) { Slog.w(TAG, "Non-system app '" + packageName + "' in sysconfig <app-link>"); continue; } @@ -4228,44 +4203,22 @@ public class PackageManagerService extends IPackageManager.Stub @Override public @Nullable ParceledListSlice<PermissionInfo> queryPermissionsByGroup(String groupName, int flags) { - // TODO Move this to PermissionManager when mPermissionGroups is moved there - synchronized (mPackages) { - if (groupName != null && !mPermissionGroups.containsKey(groupName)) { - // This is thrown as NameNotFoundException - return null; - } - } - return new ParceledListSlice<>( - mPermissionManager.getPermissionInfoByGroup(groupName, flags, getCallingUid())); + final List<PermissionInfo> permissionList = + mPermissionManager.getPermissionInfoByGroup(groupName, flags, getCallingUid()); + return (permissionList == null) ? null : new ParceledListSlice<>(permissionList); } @Override - public PermissionGroupInfo getPermissionGroupInfo(String name, int flags) { - if (getInstantAppPackageName(Binder.getCallingUid()) != null) { - return null; - } - // reader - synchronized (mPackages) { - return PackageParser.generatePermissionGroupInfo( - mPermissionGroups.get(name), flags); - } + public PermissionGroupInfo getPermissionGroupInfo(String groupName, int flags) { + return mPermissionManager.getPermissionGroupInfo(groupName, flags, getCallingUid()); } @Override public @NonNull ParceledListSlice<PermissionGroupInfo> getAllPermissionGroups(int flags) { - if (getInstantAppPackageName(Binder.getCallingUid()) != null) { - return ParceledListSlice.emptyList(); - } - // reader - synchronized (mPackages) { - final int N = mPermissionGroups.size(); - ArrayList<PermissionGroupInfo> out - = new ArrayList<PermissionGroupInfo>(N); - for (PackageParser.PermissionGroup pg : mPermissionGroups.values()) { - out.add(PackageParser.generatePermissionGroupInfo(pg, flags)); - } - return new ParceledListSlice<>(out); - } + final List<PermissionGroupInfo> permissionList = + mPermissionManager.getAllPermissionGroups(flags, getCallingUid()); + return (permissionList == null) + ? ParceledListSlice.emptyList() : new ParceledListSlice<>(permissionList); } private ApplicationInfo generateApplicationInfoFromSettingsLPw(String packageName, int flags, @@ -5138,59 +5091,7 @@ public class PackageManagerService extends IPackageManager.Stub @Override public int checkUidPermission(String permName, int uid) { - final int callingUid = Binder.getCallingUid(); - final int callingUserId = UserHandle.getUserId(callingUid); - final boolean isCallerInstantApp = getInstantAppPackageName(callingUid) != null; - final boolean isUidInstantApp = getInstantAppPackageName(uid) != null; - final int userId = UserHandle.getUserId(uid); - if (!sUserManager.exists(userId)) { - return PackageManager.PERMISSION_DENIED; - } - - synchronized (mPackages) { - Object obj = mSettings.getUserIdLPr(UserHandle.getAppId(uid)); - if (obj != null) { - if (obj instanceof SharedUserSetting) { - if (isCallerInstantApp) { - return PackageManager.PERMISSION_DENIED; - } - } else if (obj instanceof PackageSetting) { - final PackageSetting ps = (PackageSetting) obj; - if (filterAppAccessLPr(ps, callingUid, callingUserId)) { - return PackageManager.PERMISSION_DENIED; - } - } - final SettingBase settingBase = (SettingBase) obj; - final PermissionsState permissionsState = settingBase.getPermissionsState(); - if (permissionsState.hasPermission(permName, userId)) { - if (isUidInstantApp) { - if (mSettings.mPermissions.isPermissionInstant(permName)) { - return PackageManager.PERMISSION_GRANTED; - } - } else { - return PackageManager.PERMISSION_GRANTED; - } - } - // Special case: ACCESS_FINE_LOCATION permission includes ACCESS_COARSE_LOCATION - if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName) && permissionsState - .hasPermission(Manifest.permission.ACCESS_FINE_LOCATION, userId)) { - return PackageManager.PERMISSION_GRANTED; - } - } else { - ArraySet<String> perms = mSystemPermissions.get(uid); - if (perms != null) { - if (perms.contains(permName)) { - return PackageManager.PERMISSION_GRANTED; - } - if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName) && perms - .contains(Manifest.permission.ACCESS_FINE_LOCATION)) { - return PackageManager.PERMISSION_GRANTED; - } - } - } - } - - return PackageManager.PERMISSION_DENIED; + return mPermissionManager.checkUidPermission(permName, uid, getCallingUid()); } @Override @@ -5352,7 +5253,9 @@ public class PackageManagerService extends IPackageManager.Stub } synchronized (mPackages) { - updatePermissionsLPw(null, null, UPDATE_PERMISSIONS_ALL); + mPermissionManager.updateAllPermissions( + StorageManager.UUID_PRIVATE_INTERNAL, false, mPackages.values(), + mPermissionCallback); for (int userId : UserManagerService.getInstance().getUserIds()) { final int packageCount = mPackages.size(); for (int i = 0; i < packageCount; i++) { @@ -5369,7 +5272,8 @@ public class PackageManagerService extends IPackageManager.Stub @Override public int getPermissionFlags(String permName, String packageName, int userId) { - return mPermissionManager.getPermissionFlags(permName, packageName, getCallingUid(), userId); + return mPermissionManager.getPermissionFlags( + permName, packageName, getCallingUid(), userId); } @Override @@ -9914,7 +9818,7 @@ public class PackageManagerService extends IPackageManager.Stub // it is better for the user to reinstall than to be in an limbo // state. Also libs disappearing under an app should never happen // - just in case. - if (!pkg.isSystemApp() || pkg.isUpdatedSystemApp()) { + if (!pkg.isSystem() || pkg.isUpdatedSystemApp()) { final int flags = pkg.isUpdatedSystemApp() ? PackageManager.DELETE_KEEP_DATA : 0; deletePackageLIF(pkg.packageName, null, true, sUserManager.getUserIds(), @@ -10063,7 +9967,7 @@ public class PackageManagerService extends IPackageManager.Stub assertPackageIsValid(pkg, policyFlags, scanFlags); if (Build.IS_DEBUGGABLE && - pkg.isPrivilegedApp() && + pkg.isPrivileged() && !SystemProperties.getBoolean("pm.dexopt.priv-apps", true)) { PackageManagerServiceUtils.logPackageHasUncompressedCode(pkg); } @@ -11156,54 +11060,15 @@ public class PackageManagerService extends IPackageManager.Stub if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, " Activities: " + r); } - N = pkg.permissionGroups.size(); - r = null; - for (i=0; i<N; i++) { - PackageParser.PermissionGroup pg = pkg.permissionGroups.get(i); - PackageParser.PermissionGroup cur = mPermissionGroups.get(pg.info.name); - final String curPackageName = cur == null ? null : cur.info.packageName; - // Dont allow ephemeral apps to define new permission groups. - if ((scanFlags & SCAN_AS_INSTANT_APP) != 0) { - Slog.w(TAG, "Permission group " + pg.info.name + " from package " - + pg.info.packageName - + " ignored: instant apps cannot define new permission groups."); - continue; - } - final boolean isPackageUpdate = pg.info.packageName.equals(curPackageName); - if (cur == null || isPackageUpdate) { - mPermissionGroups.put(pg.info.name, pg); - if (chatty) { - if (r == null) { - r = new StringBuilder(256); - } else { - r.append(' '); - } - if (isPackageUpdate) { - r.append("UPD:"); - } - r.append(pg.info.name); - } - } else { - Slog.w(TAG, "Permission group " + pg.info.name + " from package " - + pg.info.packageName + " ignored: original from " - + cur.info.packageName); - if (chatty) { - if (r == null) { - r = new StringBuilder(256); - } else { - r.append(' '); - } - r.append("DUP:"); - r.append(pg.info.name); - } - } - } - if (r != null) { - if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, " Permission Groups: " + r); + // Don't allow ephemeral applications to define new permissions groups. + if ((scanFlags & SCAN_AS_INSTANT_APP) != 0) { + Slog.w(TAG, "Permission groups from package " + pkg.packageName + + " ignored: instant apps cannot define new permission groups."); + } else { + mPermissionManager.addAllPermissionGroups(pkg, chatty); } - - // Dont allow ephemeral apps to define new permissions. + // Don't allow ephemeral applications to define new permissions. if ((scanFlags & SCAN_AS_INSTANT_APP) != 0) { Slog.w(TAG, "Permissions from package " + pkg.packageName + " ignored: instant apps cannot define new permissions."); @@ -11995,611 +11860,6 @@ public class PackageManagerService extends IPackageManager.Stub } } - public static final int UPDATE_PERMISSIONS_ALL = 1<<0; - public static final int UPDATE_PERMISSIONS_REPLACE_PKG = 1<<1; - public static final int UPDATE_PERMISSIONS_REPLACE_ALL = 1<<2; - - private void updatePermissionsLPw(PackageParser.Package pkg, int flags) { - // Update the parent permissions - updatePermissionsLPw(pkg.packageName, pkg, flags); - // Update the child permissions - final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0; - for (int i = 0; i < childCount; i++) { - PackageParser.Package childPkg = pkg.childPackages.get(i); - updatePermissionsLPw(childPkg.packageName, childPkg, flags); - } - } - - private void updatePermissionsLPw(String changingPkg, PackageParser.Package pkgInfo, - int flags) { - final String volumeUuid = (pkgInfo != null) ? getVolumeUuidForPackage(pkgInfo) : null; - updatePermissionsLocked(changingPkg, pkgInfo, volumeUuid, flags); - } - - private void updatePermissionsLocked(String changingPkg, - PackageParser.Package pkgInfo, String replaceVolumeUuid, int flags) { - // TODO: Most of the methods exposing BasePermission internals [source package name, - // etc..] shouldn't be needed. Instead, when we've parsed a permission that doesn't - // have package settings, we should make note of it elsewhere [map between - // source package name and BasePermission] and cycle through that here. Then we - // define a single method on BasePermission that takes a PackageSetting, changing - // package name and a package. - // NOTE: With this approach, we also don't need to tree trees differently than - // normal permissions. Today, we need two separate loops because these BasePermission - // objects are stored separately. - // Make sure there are no dangling permission trees. - flags = mPermissionManager.updatePermissionTrees(changingPkg, pkgInfo, flags); - - // Make sure all dynamic permissions have been assigned to a package, - // and make sure there are no dangling permissions. - flags = mPermissionManager.updatePermissions(changingPkg, pkgInfo, flags); - - Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "grantPermissions"); - // Now update the permissions for all packages, in particular - // replace the granted permissions of the system packages. - if ((flags&UPDATE_PERMISSIONS_ALL) != 0) { - for (PackageParser.Package pkg : mPackages.values()) { - if (pkg != pkgInfo) { - // Only replace for packages on requested volume - final String volumeUuid = getVolumeUuidForPackage(pkg); - final boolean replace = ((flags & UPDATE_PERMISSIONS_REPLACE_ALL) != 0) - && Objects.equals(replaceVolumeUuid, volumeUuid); - grantPermissionsLPw(pkg, replace, changingPkg); - } - } - } - - if (pkgInfo != null) { - // Only replace for packages on requested volume - final String volumeUuid = getVolumeUuidForPackage(pkgInfo); - final boolean replace = ((flags & UPDATE_PERMISSIONS_REPLACE_PKG) != 0) - && Objects.equals(replaceVolumeUuid, volumeUuid); - grantPermissionsLPw(pkgInfo, replace, changingPkg); - } - Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); - } - - private void grantPermissionsLPw(PackageParser.Package pkg, boolean replace, - String packageOfInterest) { - // IMPORTANT: There are two types of permissions: install and runtime. - // Install time permissions are granted when the app is installed to - // all device users and users added in the future. Runtime permissions - // are granted at runtime explicitly to specific users. Normal and signature - // protected permissions are install time permissions. Dangerous permissions - // are install permissions if the app's target SDK is Lollipop MR1 or older, - // otherwise they are runtime permissions. This function does not manage - // runtime permissions except for the case an app targeting Lollipop MR1 - // being upgraded to target a newer SDK, in which case dangerous permissions - // are transformed from install time to runtime ones. - - final PackageSetting ps = (PackageSetting) pkg.mExtras; - if (ps == null) { - return; - } - - PermissionsState permissionsState = ps.getPermissionsState(); - PermissionsState origPermissions = permissionsState; - - final int[] currentUserIds = UserManagerService.getInstance().getUserIds(); - - boolean runtimePermissionsRevoked = false; - int[] changedRuntimePermissionUserIds = EMPTY_INT_ARRAY; - - boolean changedInstallPermission = false; - - if (replace) { - ps.installPermissionsFixed = false; - if (!ps.isSharedUser()) { - origPermissions = new PermissionsState(permissionsState); - permissionsState.reset(); - } else { - // We need to know only about runtime permission changes since the - // calling code always writes the install permissions state but - // the runtime ones are written only if changed. The only cases of - // changed runtime permissions here are promotion of an install to - // runtime and revocation of a runtime from a shared user. - changedRuntimePermissionUserIds = - mPermissionManager.revokeUnusedSharedUserPermissions( - ps.sharedUser, UserManagerService.getInstance().getUserIds()); - if (!ArrayUtils.isEmpty(changedRuntimePermissionUserIds)) { - runtimePermissionsRevoked = true; - } - } - } - - permissionsState.setGlobalGids(mGlobalGids); - - final int N = pkg.requestedPermissions.size(); - for (int i=0; i<N; i++) { - final String name = pkg.requestedPermissions.get(i); - final BasePermission bp = (BasePermission) mPermissionManager.getPermissionTEMP(name); - final boolean appSupportsRuntimePermissions = pkg.applicationInfo.targetSdkVersion - >= Build.VERSION_CODES.M; - - if (DEBUG_INSTALL) { - Log.i(TAG, "Package " + pkg.packageName + " checking " + name + ": " + bp); - } - - if (bp == null || bp.getSourcePackageSetting() == null) { - if (packageOfInterest == null || packageOfInterest.equals(pkg.packageName)) { - if (DEBUG_PERMISSIONS) { - Slog.i(TAG, "Unknown permission " + name - + " in package " + pkg.packageName); - } - } - continue; - } - - - // Limit ephemeral apps to ephemeral allowed permissions. - if (pkg.applicationInfo.isInstantApp() && !bp.isInstant()) { - if (DEBUG_PERMISSIONS) { - Log.i(TAG, "Denying non-ephemeral permission " + bp.getName() + " for package " - + pkg.packageName); - } - continue; - } - - if (bp.isRuntimeOnly() && !appSupportsRuntimePermissions) { - if (DEBUG_PERMISSIONS) { - Log.i(TAG, "Denying runtime-only permission " + bp.getName() + " for package " - + pkg.packageName); - } - continue; - } - - final String perm = bp.getName(); - boolean allowedSig = false; - int grant = GRANT_DENIED; - - // Keep track of app op permissions. - if (bp.isAppOp()) { - mSettings.addAppOpPackage(perm, pkg.packageName); - } - - if (bp.isNormal()) { - // For all apps normal permissions are install time ones. - grant = GRANT_INSTALL; - } else if (bp.isRuntime()) { - // If a permission review is required for legacy apps we represent - // their permissions as always granted runtime ones since we need - // to keep the review required permission flag per user while an - // install permission's state is shared across all users. - if (!appSupportsRuntimePermissions && !mPermissionReviewRequired) { - // For legacy apps dangerous permissions are install time ones. - grant = GRANT_INSTALL; - } else if (origPermissions.hasInstallPermission(bp.getName())) { - // For legacy apps that became modern, install becomes runtime. - grant = GRANT_UPGRADE; - } else if (mPromoteSystemApps - && isSystemApp(ps) - && mExistingSystemPackages.contains(ps.name)) { - // For legacy system apps, install becomes runtime. - // We cannot check hasInstallPermission() for system apps since those - // permissions were granted implicitly and not persisted pre-M. - grant = GRANT_UPGRADE; - } else { - // For modern apps keep runtime permissions unchanged. - grant = GRANT_RUNTIME; - } - } else if (bp.isSignature()) { - // For all apps signature permissions are install time ones. - allowedSig = grantSignaturePermission(perm, pkg, bp, origPermissions); - if (allowedSig) { - grant = GRANT_INSTALL; - } - } - - if (DEBUG_PERMISSIONS) { - Slog.i(TAG, "Granting permission " + perm + " to package " + pkg.packageName); - } - - if (grant != GRANT_DENIED) { - if (!isSystemApp(ps) && ps.installPermissionsFixed) { - // If this is an existing, non-system package, then - // we can't add any new permissions to it. - if (!allowedSig && !origPermissions.hasInstallPermission(perm)) { - // Except... if this is a permission that was added - // to the platform (note: need to only do this when - // updating the platform). - if (!isNewPlatformPermissionForPackage(perm, pkg)) { - grant = GRANT_DENIED; - } - } - } - - switch (grant) { - case GRANT_INSTALL: { - // Revoke this as runtime permission to handle the case of - // a runtime permission being downgraded to an install one. - // Also in permission review mode we keep dangerous permissions - // for legacy apps - for (int userId : UserManagerService.getInstance().getUserIds()) { - if (origPermissions.getRuntimePermissionState( - perm, userId) != null) { - // Revoke the runtime permission and clear the flags. - origPermissions.revokeRuntimePermission(bp, userId); - origPermissions.updatePermissionFlags(bp, userId, - PackageManager.MASK_PERMISSION_FLAGS, 0); - // If we revoked a permission permission, we have to write. - changedRuntimePermissionUserIds = ArrayUtils.appendInt( - changedRuntimePermissionUserIds, userId); - } - } - // Grant an install permission. - if (permissionsState.grantInstallPermission(bp) != - PermissionsState.PERMISSION_OPERATION_FAILURE) { - changedInstallPermission = true; - } - } break; - - case GRANT_RUNTIME: { - // Grant previously granted runtime permissions. - for (int userId : UserManagerService.getInstance().getUserIds()) { - PermissionState permissionState = origPermissions - .getRuntimePermissionState(perm, userId); - int flags = permissionState != null - ? permissionState.getFlags() : 0; - if (origPermissions.hasRuntimePermission(perm, userId)) { - // Don't propagate the permission in a permission review mode if - // the former was revoked, i.e. marked to not propagate on upgrade. - // Note that in a permission review mode install permissions are - // represented as constantly granted runtime ones since we need to - // keep a per user state associated with the permission. Also the - // revoke on upgrade flag is no longer applicable and is reset. - final boolean revokeOnUpgrade = (flags & PackageManager - .FLAG_PERMISSION_REVOKE_ON_UPGRADE) != 0; - if (revokeOnUpgrade) { - flags &= ~PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE; - // Since we changed the flags, we have to write. - changedRuntimePermissionUserIds = ArrayUtils.appendInt( - changedRuntimePermissionUserIds, userId); - } - if (!mPermissionReviewRequired || !revokeOnUpgrade) { - if (permissionsState.grantRuntimePermission(bp, userId) == - PermissionsState.PERMISSION_OPERATION_FAILURE) { - // If we cannot put the permission as it was, - // we have to write. - changedRuntimePermissionUserIds = ArrayUtils.appendInt( - changedRuntimePermissionUserIds, userId); - } - } - - // If the app supports runtime permissions no need for a review. - if (mPermissionReviewRequired - && appSupportsRuntimePermissions - && (flags & PackageManager - .FLAG_PERMISSION_REVIEW_REQUIRED) != 0) { - flags &= ~PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED; - // Since we changed the flags, we have to write. - changedRuntimePermissionUserIds = ArrayUtils.appendInt( - changedRuntimePermissionUserIds, userId); - } - } else if (mPermissionReviewRequired - && !appSupportsRuntimePermissions) { - // For legacy apps that need a permission review, every new - // runtime permission is granted but it is pending a review. - // We also need to review only platform defined runtime - // permissions as these are the only ones the platform knows - // how to disable the API to simulate revocation as legacy - // apps don't expect to run with revoked permissions. - if (PLATFORM_PACKAGE_NAME.equals(bp.getSourcePackageName())) { - if ((flags & FLAG_PERMISSION_REVIEW_REQUIRED) == 0) { - flags |= FLAG_PERMISSION_REVIEW_REQUIRED; - // We changed the flags, hence have to write. - changedRuntimePermissionUserIds = ArrayUtils.appendInt( - changedRuntimePermissionUserIds, userId); - } - } - if (permissionsState.grantRuntimePermission(bp, userId) - != PermissionsState.PERMISSION_OPERATION_FAILURE) { - // We changed the permission, hence have to write. - changedRuntimePermissionUserIds = ArrayUtils.appendInt( - changedRuntimePermissionUserIds, userId); - } - } - // Propagate the permission flags. - permissionsState.updatePermissionFlags(bp, userId, flags, flags); - } - } break; - - case GRANT_UPGRADE: { - // Grant runtime permissions for a previously held install permission. - PermissionState permissionState = origPermissions - .getInstallPermissionState(perm); - final int flags = permissionState != null ? permissionState.getFlags() : 0; - - if (origPermissions.revokeInstallPermission(bp) - != PermissionsState.PERMISSION_OPERATION_FAILURE) { - // We will be transferring the permission flags, so clear them. - origPermissions.updatePermissionFlags(bp, UserHandle.USER_ALL, - PackageManager.MASK_PERMISSION_FLAGS, 0); - changedInstallPermission = true; - } - - // If the permission is not to be promoted to runtime we ignore it and - // also its other flags as they are not applicable to install permissions. - if ((flags & PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE) == 0) { - for (int userId : currentUserIds) { - if (permissionsState.grantRuntimePermission(bp, userId) != - PermissionsState.PERMISSION_OPERATION_FAILURE) { - // Transfer the permission flags. - permissionsState.updatePermissionFlags(bp, userId, - flags, flags); - // If we granted the permission, we have to write. - changedRuntimePermissionUserIds = ArrayUtils.appendInt( - changedRuntimePermissionUserIds, userId); - } - } - } - } break; - - default: { - if (packageOfInterest == null - || packageOfInterest.equals(pkg.packageName)) { - if (DEBUG_PERMISSIONS) { - Slog.i(TAG, "Not granting permission " + perm - + " to package " + pkg.packageName - + " because it was previously installed without"); - } - } - } break; - } - } else { - if (permissionsState.revokeInstallPermission(bp) != - PermissionsState.PERMISSION_OPERATION_FAILURE) { - // Also drop the permission flags. - permissionsState.updatePermissionFlags(bp, UserHandle.USER_ALL, - PackageManager.MASK_PERMISSION_FLAGS, 0); - changedInstallPermission = true; - Slog.i(TAG, "Un-granting permission " + perm - + " from package " + pkg.packageName - + " (protectionLevel=" + bp.getProtectionLevel() - + " flags=0x" + Integer.toHexString(pkg.applicationInfo.flags) - + ")"); - } else if (bp.isAppOp()) { - // Don't print warning for app op permissions, since it is fine for them - // not to be granted, there is a UI for the user to decide. - if (DEBUG_PERMISSIONS - && (packageOfInterest == null - || packageOfInterest.equals(pkg.packageName))) { - Slog.i(TAG, "Not granting permission " + perm - + " to package " + pkg.packageName - + " (protectionLevel=" + bp.getProtectionLevel() - + " flags=0x" + Integer.toHexString(pkg.applicationInfo.flags) - + ")"); - } - } - } - } - - if ((changedInstallPermission || replace) && !ps.installPermissionsFixed && - !isSystemApp(ps) || isUpdatedSystemApp(ps)){ - // This is the first that we have heard about this package, so the - // permissions we have now selected are fixed until explicitly - // changed. - ps.installPermissionsFixed = true; - } - - // Persist the runtime permissions state for users with changes. If permissions - // were revoked because no app in the shared user declares them we have to - // write synchronously to avoid losing runtime permissions state. - for (int userId : changedRuntimePermissionUserIds) { - mSettings.writeRuntimePermissionsForUserLPr(userId, runtimePermissionsRevoked); - } - } - - private boolean isNewPlatformPermissionForPackage(String perm, PackageParser.Package pkg) { - boolean allowed = false; - final int NP = PackageParser.NEW_PERMISSIONS.length; - for (int ip=0; ip<NP; ip++) { - final PackageParser.NewPermissionInfo npi - = PackageParser.NEW_PERMISSIONS[ip]; - if (npi.name.equals(perm) - && pkg.applicationInfo.targetSdkVersion < npi.sdkVersion) { - allowed = true; - Log.i(TAG, "Auto-granting " + perm + " to old pkg " - + pkg.packageName); - break; - } - } - return allowed; - } - - /** - * Determines whether a package is whitelisted for a particular privapp permission. - * - * <p>Does NOT check whether the package is a privapp, just whether it's whitelisted. - * - * <p>This handles parent/child apps. - */ - private boolean hasPrivappWhitelistEntry(String perm, PackageParser.Package pkg) { - ArraySet<String> wlPermissions = SystemConfig.getInstance() - .getPrivAppPermissions(pkg.packageName); - // Let's check if this package is whitelisted... - boolean whitelisted = wlPermissions != null && wlPermissions.contains(perm); - // If it's not, we'll also tail-recurse to the parent. - return whitelisted || - pkg.parentPackage != null && hasPrivappWhitelistEntry(perm, pkg.parentPackage); - } - - private boolean grantSignaturePermission(String perm, PackageParser.Package pkg, - BasePermission bp, PermissionsState origPermissions) { - boolean oemPermission = bp.isOEM(); - boolean privilegedPermission = bp.isPrivileged(); - boolean privappPermissionsDisable = - RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_DISABLE; - boolean platformPermission = PLATFORM_PACKAGE_NAME.equals(bp.getSourcePackageName()); - boolean platformPackage = PLATFORM_PACKAGE_NAME.equals(pkg.packageName); - if (!privappPermissionsDisable && privilegedPermission && pkg.isPrivilegedApp() - && !platformPackage && platformPermission) { - if (!hasPrivappWhitelistEntry(perm, pkg)) { - Slog.w(TAG, "Privileged permission " + perm + " for package " - + pkg.packageName + " - not in privapp-permissions whitelist"); - // Only report violations for apps on system image - if (!mSystemReady && !pkg.isUpdatedSystemApp()) { - // it's only a reportable violation if the permission isn't explicitly denied - final ArraySet<String> deniedPermissions = SystemConfig.getInstance() - .getPrivAppDenyPermissions(pkg.packageName); - final boolean permissionViolation = - deniedPermissions == null || !deniedPermissions.contains(perm); - if (permissionViolation - && RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE) { - if (mPrivappPermissionsViolations == null) { - mPrivappPermissionsViolations = new ArraySet<>(); - } - mPrivappPermissionsViolations.add(pkg.packageName + ": " + perm); - } else { - return false; - } - } - if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE) { - return false; - } - } - } - boolean allowed = (compareSignatures( - bp.getSourcePackageSetting().signatures.mSignatures, pkg.mSignatures) - == PackageManager.SIGNATURE_MATCH) - || (compareSignatures(mPlatformPackage.mSignatures, pkg.mSignatures) - == PackageManager.SIGNATURE_MATCH); - if (!allowed && (privilegedPermission || oemPermission)) { - if (isSystemApp(pkg)) { - // For updated system applications, a privileged/oem permission - // is granted only if it had been defined by the original application. - if (pkg.isUpdatedSystemApp()) { - final PackageSetting sysPs = mSettings - .getDisabledSystemPkgLPr(pkg.packageName); - if (sysPs != null && sysPs.getPermissionsState().hasInstallPermission(perm)) { - // If the original was granted this permission, we take - // that grant decision as read and propagate it to the - // update. - if ((privilegedPermission && sysPs.isPrivileged()) - || (oemPermission && sysPs.isOem() - && canGrantOemPermission(sysPs, perm))) { - allowed = true; - } - } else { - // The system apk may have been updated with an older - // version of the one on the data partition, but which - // granted a new system permission that it didn't have - // before. In this case we do want to allow the app to - // now get the new permission if the ancestral apk is - // privileged to get it. - if (sysPs != null && sysPs.pkg != null - && isPackageRequestingPermission(sysPs.pkg, perm) - && ((privilegedPermission && sysPs.isPrivileged()) - || (oemPermission && sysPs.isOem() - && canGrantOemPermission(sysPs, perm)))) { - allowed = true; - } - // Also if a privileged parent package on the system image or any of - // its children requested a privileged/oem permission, the updated child - // packages can also get the permission. - if (pkg.parentPackage != null) { - final PackageSetting disabledSysParentPs = mSettings - .getDisabledSystemPkgLPr(pkg.parentPackage.packageName); - final PackageParser.Package disabledSysParentPkg = - (disabledSysParentPs == null || disabledSysParentPs.pkg == null) - ? null : disabledSysParentPs.pkg; - if (disabledSysParentPkg != null - && ((privilegedPermission && disabledSysParentPs.isPrivileged()) - || (oemPermission && disabledSysParentPs.isOem()))) { - if (isPackageRequestingPermission(disabledSysParentPkg, perm) - && canGrantOemPermission(disabledSysParentPs, perm)) { - allowed = true; - } else if (disabledSysParentPkg.childPackages != null) { - final int count = disabledSysParentPkg.childPackages.size(); - for (int i = 0; i < count; i++) { - final PackageParser.Package disabledSysChildPkg = - disabledSysParentPkg.childPackages.get(i); - final PackageSetting disabledSysChildPs = - mSettings.getDisabledSystemPkgLPr( - disabledSysChildPkg.packageName); - if (isPackageRequestingPermission(disabledSysChildPkg, perm) - && canGrantOemPermission( - disabledSysChildPs, perm)) { - allowed = true; - break; - } - } - } - } - } - } - } else { - allowed = (privilegedPermission && isPrivilegedApp(pkg)) - || (oemPermission && isOemApp(pkg) - && canGrantOemPermission( - mSettings.getPackageLPr(pkg.packageName), perm)); - } - } - } - if (!allowed) { - if (!allowed - && bp.isPre23() - && pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) { - // If this was a previously normal/dangerous permission that got moved - // to a system permission as part of the runtime permission redesign, then - // we still want to blindly grant it to old apps. - allowed = true; - } - if (!allowed && bp.isInstaller() - && pkg.packageName.equals(mRequiredInstallerPackage)) { - // If this permission is to be granted to the system installer and - // this app is an installer, then it gets the permission. - allowed = true; - } - if (!allowed && bp.isVerifier() - && pkg.packageName.equals(mRequiredVerifierPackage)) { - // If this permission is to be granted to the system verifier and - // this app is a verifier, then it gets the permission. - allowed = true; - } - if (!allowed && bp.isPreInstalled() - && isSystemApp(pkg)) { - // Any pre-installed system app is allowed to get this permission. - allowed = true; - } - if (!allowed && bp.isDevelopment()) { - // For development permissions, a development permission - // is granted only if it was already granted. - allowed = origPermissions.hasInstallPermission(perm); - } - if (!allowed && bp.isSetup() - && pkg.packageName.equals(mSetupWizardPackage)) { - // If this permission is to be granted to the system setup wizard and - // this app is a setup wizard, then it gets the permission. - allowed = true; - } - } - return allowed; - } - - private static boolean canGrantOemPermission(PackageSetting ps, String permission) { - if (!ps.isOem()) { - return false; - } - // all oem permissions must explicitly be granted or denied - final Boolean granted = - SystemConfig.getInstance().getOemPermissions(ps.name).get(permission); - if (granted == null) { - throw new IllegalStateException("OEM permission" + permission + " requested by package " - + ps.name + " must be explicitly declared granted or not"); - } - return Boolean.TRUE == granted; - } - - private boolean isPackageRequestingPermission(PackageParser.Package pkg, String permission) { - final int permCount = pkg.requestedPermissions.size(); - for (int j = 0; j < permCount; j++) { - String requestedPermission = pkg.requestedPermissions.get(j); - if (permission.equals(requestedPermission)) { - return true; - } - } - return false; - } final class ActivityIntentResolver extends IntentResolver<PackageParser.ActivityIntentInfo, ResolveInfo> { @@ -16491,7 +15751,7 @@ public class PackageManagerService extends IPackageManager.Stub } // don't allow a system upgrade unless the upgrade hash matches - if (oldPackage.restrictUpdateHash != null && oldPackage.isSystemApp()) { + if (oldPackage.restrictUpdateHash != null && oldPackage.isSystem()) { byte[] digestBytes = null; try { final MessageDigest digest = MessageDigest.getInstance("SHA-512"); @@ -16758,7 +16018,9 @@ public class PackageManagerService extends IPackageManager.Stub setInstallerPackageNameLPw(deletedPackage, installerPackageName); // Update permissions for restored package - updatePermissionsLPw(deletedPackage, UPDATE_PERMISSIONS_ALL); + mPermissionManager.updatePermissions( + deletedPackage.packageName, deletedPackage, false, mPackages.values(), + mPermissionCallback); mSettings.writeLPr(); } @@ -16900,7 +16162,9 @@ public class PackageManagerService extends IPackageManager.Stub setInstallerPackageNameLPw(deletedPackage, installerPackageName); // Update permissions for restored package - updatePermissionsLPw(deletedPackage, UPDATE_PERMISSIONS_ALL); + mPermissionManager.updatePermissions( + deletedPackage.packageName, deletedPackage, false, mPackages.values(), + mPermissionCallback); mSettings.writeLPr(); } @@ -17013,12 +16277,12 @@ public class PackageManagerService extends IPackageManager.Stub } } - private void updateSettingsInternalLI(PackageParser.Package newPackage, + private void updateSettingsInternalLI(PackageParser.Package pkg, String installerPackageName, int[] allUsers, int[] installedForUsers, PackageInstalledInfo res, UserHandle user, int installReason) { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "updateSettings"); - String pkgName = newPackage.packageName; + String pkgName = pkg.packageName; synchronized (mPackages) { //write settings. the installStatus will be incomplete at this stage. //note that the new package setting would have already been @@ -17030,18 +16294,18 @@ public class PackageManagerService extends IPackageManager.Stub Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } - if (DEBUG_INSTALL) Slog.d(TAG, "New package installed in " + newPackage.codePath); + if (DEBUG_INSTALL) Slog.d(TAG, "New package installed in " + pkg.codePath); synchronized (mPackages) { - updatePermissionsLPw(newPackage.packageName, newPackage, - UPDATE_PERMISSIONS_REPLACE_PKG | (newPackage.permissions.size() > 0 - ? UPDATE_PERMISSIONS_ALL : 0)); +// NOTE: This changes slightly to include UPDATE_PERMISSIONS_ALL regardless of the size of pkg.permissions + mPermissionManager.updatePermissions(pkg.packageName, pkg, true, mPackages.values(), + mPermissionCallback); // For system-bundled packages, we assume that installing an upgraded version // of the package implies that the user actually wants to run that new code, // so we enable the package. PackageSetting ps = mSettings.mPackages.get(pkgName); final int userId = user.getIdentifier(); if (ps != null) { - if (isSystemApp(newPackage)) { + if (isSystemApp(pkg)) { if (DEBUG_INSTALL) { Slog.d(TAG, "Implicitly enabling system package on upgrade: " + pkgName); } @@ -17101,8 +16365,8 @@ public class PackageManagerService extends IPackageManager.Stub mSettings.writeKernelMappingLPr(ps); } res.name = pkgName; - res.uid = newPackage.applicationInfo.uid; - res.pkg = newPackage; + res.uid = pkg.applicationInfo.uid; + res.pkg = pkg; mSettings.setInstallStatus(pkgName, PackageSettingBase.PKG_INSTALL_COMPLETE); mSettings.setInstallerPackageName(pkgName, installerPackageName); res.setReturnCode(PackageManager.INSTALL_SUCCEEDED); @@ -17801,18 +17065,6 @@ public class PackageManagerService extends IPackageManager.Stub return installFlags; } - private String getVolumeUuidForPackage(PackageParser.Package pkg) { - if (isExternal(pkg)) { - if (TextUtils.isEmpty(pkg.volumeUuid)) { - return StorageManager.UUID_PRIMARY_PHYSICAL; - } else { - return pkg.volumeUuid; - } - } else { - return StorageManager.UUID_PRIVATE_INTERNAL; - } - } - private VersionInfo getSettingsVersionForPackage(PackageParser.Package pkg) { if (isExternal(pkg)) { if (TextUtils.isEmpty(pkg.volumeUuid)) { @@ -18457,7 +17709,8 @@ public class PackageManagerService extends IPackageManager.Stub if (outInfo != null) { outInfo.removedAppId = removedAppId; } - updatePermissionsLPw(deletedPs.name, null, 0); + mPermissionManager.updatePermissions( + deletedPs.name, null, false, mPackages.values(), mPermissionCallback); if (deletedPs.sharedUser != null) { // Remove permissions associated with package. Since runtime // permissions are per user we have to kill the removed package @@ -18660,21 +17913,21 @@ public class PackageManagerService extends IPackageManager.Stub parseFlags |= PackageParser.PARSE_IS_OEM; } - final PackageParser.Package newPkg = + final PackageParser.Package pkg = scanPackageTracedLI(codePath, parseFlags, 0 /*scanFlags*/, 0 /*currentTime*/, null); try { // update shared libraries for the newly re-installed system package - updateSharedLibrariesLPr(newPkg, null); + updateSharedLibrariesLPr(pkg, null); } catch (PackageManagerException e) { Slog.e(TAG, "updateAllSharedLibrariesLPw failed: " + e.getMessage()); } - prepareAppDataAfterInstallLIF(newPkg); + prepareAppDataAfterInstallLIF(pkg); // writer synchronized (mPackages) { - PackageSetting ps = mSettings.mPackages.get(newPkg.packageName); + PackageSetting ps = mSettings.mPackages.get(pkg.packageName); // Propagate the permissions state as we do not want to drop on the floor // runtime permissions. The update permissions method below will take @@ -18682,8 +17935,8 @@ public class PackageManagerService extends IPackageManager.Stub if (origPermissionState != null) { ps.getPermissionsState().copyFrom(origPermissionState); } - updatePermissionsLPw(newPkg.packageName, newPkg, - UPDATE_PERMISSIONS_ALL | UPDATE_PERMISSIONS_REPLACE_PKG); + mPermissionManager.updatePermissions(pkg.packageName, pkg, true, mPackages.values(), + mPermissionCallback); final boolean applyUserRestrictions = (allUserHandles != null) && (origUserHandles != null); @@ -18716,7 +17969,7 @@ public class PackageManagerService extends IPackageManager.Stub mSettings.writeLPr(); } } - return newPkg; + return pkg; } private boolean deleteInstalledPackageLIF(PackageSetting ps, @@ -19333,7 +18586,7 @@ public class PackageManagerService extends IPackageManager.Stub // If permission review is enabled and this is a legacy app, mark the // permission as requiring a review as this is the initial state. int flags = 0; - if (mPermissionReviewRequired + if (mSettings.mPermissions.mPermissionReviewRequired && ps.pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) { flags |= FLAG_PERMISSION_REVIEW_REQUIRED; } @@ -20692,7 +19945,7 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); // data partition and then replace the version on the system partition. final PackageParser.Package deletedPkg = pkgSetting.pkg; final boolean isSystemStub = deletedPkg.isStub - && deletedPkg.isSystemApp(); + && deletedPkg.isSystem(); if (isSystemStub && (newState == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT || newState == PackageManager.COMPONENT_ENABLED_STATE_ENABLED)) { @@ -20732,22 +19985,23 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); synchronized (mPackages) { disableSystemPackageLPw(deletedPkg, tmpPkg); } - final PackageParser.Package newPkg; + final PackageParser.Package pkg; try (PackageFreezer freezer = freezePackage(deletedPkg.packageName, "setEnabledSetting")) { final int parseFlags = mDefParseFlags | PackageParser.PARSE_CHATTY | PackageParser.PARSE_ENFORCE_CODE; - newPkg = scanPackageTracedLI(codePath, parseFlags, 0 /*scanFlags*/, + pkg = scanPackageTracedLI(codePath, parseFlags, 0 /*scanFlags*/, 0 /*currentTime*/, null /*user*/); - prepareAppDataAfterInstallLIF(newPkg); + prepareAppDataAfterInstallLIF(pkg); synchronized (mPackages) { try { - updateSharedLibrariesLPr(newPkg, null); + updateSharedLibrariesLPr(pkg, null); } catch (PackageManagerException e) { Slog.e(TAG, "updateAllSharedLibrariesLPw failed: ", e); } - updatePermissionsLPw(newPkg.packageName, newPkg, - UPDATE_PERMISSIONS_ALL | UPDATE_PERMISSIONS_REPLACE_PKG); + mPermissionManager.updatePermissions( + pkg.packageName, pkg, true, mPackages.values(), + mPermissionCallback); mSettings.writeLPr(); } } catch (PackageManagerException e) { @@ -20787,11 +20041,11 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); } return; } - clearAppDataLIF(newPkg, UserHandle.USER_ALL, FLAG_STORAGE_DE + clearAppDataLIF(pkg, UserHandle.USER_ALL, FLAG_STORAGE_DE | FLAG_STORAGE_CE | Installer.FLAG_CLEAR_CODE_CACHE_ONLY); - clearAppProfilesLIF(newPkg, UserHandle.USER_ALL); - mDexManager.notifyPackageUpdated(newPkg.packageName, - newPkg.baseCodePath, newPkg.splitCodePaths); + clearAppProfilesLIF(pkg, UserHandle.USER_ALL); + mDexManager.notifyPackageUpdated(pkg.packageName, + pkg.baseCodePath, pkg.splitCodePaths); } } if (newState == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT @@ -21102,8 +20356,9 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); // permissions, ensure permissions are updated. Beware of dragons if you // try optimizing this. synchronized (mPackages) { - updatePermissionsLocked(null, null, StorageManager.UUID_PRIVATE_INTERNAL, - UPDATE_PERMISSIONS_ALL); + mPermissionManager.updateAllPermissions( + StorageManager.UUID_PRIVATE_INTERNAL, false, mPackages.values(), + mPermissionCallback); } // Kick off any messages waiting for system ready @@ -21152,10 +20407,7 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); sUserManager.reconcileUsers(StorageManager.UUID_PRIVATE_INTERNAL); reconcileApps(StorageManager.UUID_PRIVATE_INTERNAL); - if (mPrivappPermissionsViolations != null) { - throw new IllegalStateException("Signature|privileged permissions not in " - + "privapp-permissions whitelist: " + mPrivappPermissionsViolations); - } + mPermissionManager.systemReady(); } public void waitForAppDataPrepared() { @@ -22140,13 +21392,13 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); } synchronized (mPackages) { - int updateFlags = UPDATE_PERMISSIONS_ALL; - if (ver.sdkVersion != mSdkVersion) { + final boolean sdkUpdated = (ver.sdkVersion != mSdkVersion); + if (sdkUpdated) { logCriticalInfo(Log.INFO, "Platform changed from " + ver.sdkVersion + " to " + mSdkVersion + "; regranting permissions for " + volumeUuid); - updateFlags |= UPDATE_PERMISSIONS_REPLACE_PKG | UPDATE_PERMISSIONS_REPLACE_ALL; } - updatePermissionsLocked(null, null, volumeUuid, updateFlags); + mPermissionManager.updateAllPermissions(volumeUuid, sdkUpdated, mPackages.values(), + mPermissionCallback); // Yay, everything is now upgraded ver.forceCurrent(); @@ -22594,7 +21846,7 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); * requested by the app. */ private boolean maybeMigrateAppDataLIF(PackageParser.Package pkg, int userId) { - if (pkg.isSystemApp() && !StorageManager.isFileEncryptedNativeOrEmulated() + if (pkg.isSystem() && !StorageManager.isFileEncryptedNativeOrEmulated() && PackageManager.APPLY_DEFAULT_TO_DEVICE_PROTECTED_STORAGE) { final int storageTarget = pkg.applicationInfo.isDefaultToDeviceProtectedStorage() ? StorageManager.FLAG_STORAGE_DE : StorageManager.FLAG_STORAGE_CE; @@ -23159,9 +22411,11 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); // permissions to keep per user flag state whether review is needed. // Hence, if a new user is added we have to propagate dangerous // permission grants for these legacy apps. - if (mPermissionReviewRequired) { - updatePermissionsLPw(null, null, UPDATE_PERMISSIONS_ALL - | UPDATE_PERMISSIONS_REPLACE_ALL); + if (mSettings.mPermissions.mPermissionReviewRequired) { +// NOTE: This adds UPDATE_PERMISSIONS_REPLACE_PKG + mPermissionManager.updateAllPermissions( + StorageManager.UUID_PRIVATE_INTERNAL, true, mPackages.values(), + mPermissionCallback); } } } @@ -23606,13 +22860,6 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); } @Override - public PackageParser.PermissionGroup getPermissionGroupTEMP(String groupName) { - synchronized (mPackages) { - return mPermissionGroups.get(groupName); - } - } - - @Override public boolean isInstantApp(String packageName, int userId) { return PackageManagerService.this.isInstantApp(packageName, userId); } @@ -23750,24 +22997,8 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); @Override public boolean isPermissionsReviewRequired(String packageName, int userId) { synchronized (mPackages) { - // If we do not support permission review, done. - if (!mPermissionReviewRequired) { - return false; - } - - PackageSetting packageSetting = mSettings.mPackages.get(packageName); - if (packageSetting == null) { - return false; - } - - // Permission review applies only to apps not supporting the new permission model. - if (packageSetting.pkg.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.M) { - return false; - } - - // Legacy apps have the permission and get user consent on launch. - PermissionsState permissionsState = packageSetting.getPermissionsState(); - return permissionsState.isPermissionReviewRequired(userId); + return mPermissionManager.isPermissionsReviewRequired( + mPackages.get(packageName), userId); } } @@ -23921,6 +23152,16 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); } @Override + public boolean isLegacySystemApp(Package pkg) { + synchronized (mPackages) { + final PackageSetting ps = (PackageSetting) pkg.mExtras; + return mPromoteSystemApps + && ps.isSystem() + && mExistingSystemPackages.contains(ps.name); + } + } + + @Override public List<PackageInfo> getOverlayPackages(int userId) { final ArrayList<PackageInfo> overlayPackages = new ArrayList<PackageInfo>(); synchronized (mPackages) { diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index 1fea003a9cc1..a2099e6080e5 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -16,14 +16,17 @@ package com.android.server.pm; +import android.accounts.IAccountManager; import android.app.ActivityManager; import android.content.ComponentName; +import android.content.Context; import android.content.IIntentReceiver; import android.content.IIntentSender; import android.content.Intent; import android.content.IntentSender; import android.content.pm.ApplicationInfo; import android.content.pm.FeatureInfo; +import android.content.pm.IPackageDataObserver; import android.content.pm.IPackageManager; import android.content.pm.InstrumentationInfo; import android.content.pm.PackageInfo; @@ -41,6 +44,7 @@ import android.content.pm.PackageInstaller.SessionInfo; import android.content.pm.PackageInstaller.SessionParams; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; +import android.content.pm.UserInfo; import android.content.pm.VersionedPackage; import android.content.res.AssetManager; import android.content.res.Resources; @@ -49,15 +53,23 @@ import android.os.Binder; import android.os.Build; import android.os.Bundle; import android.os.IBinder; +import android.os.IUserManager; +import android.os.Process; import android.os.RemoteException; +import android.os.ServiceManager; import android.os.ShellCommand; +import android.os.SystemClock; import android.os.SystemProperties; import android.os.UserHandle; +import android.os.UserManager; +import android.os.storage.StorageManager; import android.text.TextUtils; -import android.util.ArrayMap; +import android.text.format.DateUtils; import android.util.ArraySet; import android.util.PrintWriterPrinter; + import com.android.internal.content.PackageHelper; +import com.android.internal.util.ArrayUtils; import com.android.internal.util.SizedInputStream; import com.android.server.SystemConfig; @@ -81,6 +93,12 @@ import java.util.WeakHashMap; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.TimeUnit; +import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS; +import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK; +import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK; +import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER; +import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED; + class PackageManagerShellCommand extends ShellCommand { /** Path for streaming APK content */ private static final String STDIN_PATH = "-"; @@ -107,6 +125,20 @@ class PackageManagerShellCommand extends ShellCommand { final PrintWriter pw = getOutPrintWriter(); try { switch(cmd) { + case "path": + return runPath(); + case "dump": + return runDump(); + case "list": + return runList(); + case "resolve-activity": + return runResolveActivity(); + case "query-activities": + return runQueryIntentActivities(); + case "query-services": + return runQueryIntentServices(); + case "query-receivers": + return runQueryIntentReceivers(); case "install": return runInstall(); case "install-abandon": @@ -122,44 +154,99 @@ class PackageManagerShellCommand extends ShellCommand { return runInstallWrite(); case "install-existing": return runInstallExisting(); + case "set-install-location": + return runSetInstallLocation(); + case "get-install-location": + return runGetInstallLocation(); + case "move-package": + return runMovePackage(); + case "move-primary-storage": + return runMovePrimaryStorage(); case "compile": return runCompile(); case "reconcile-secondary-dex-files": return runreconcileSecondaryDexFiles(); + case "force-dex-opt": + return runForceDexOpt(); case "bg-dexopt-job": return runDexoptJob(); case "dump-profiles": return runDumpProfiles(); - case "list": - return runList(); case "uninstall": return runUninstall(); - case "resolve-activity": - return runResolveActivity(); - case "query-activities": - return runQueryIntentActivities(); - case "query-services": - return runQueryIntentServices(); - case "query-receivers": - return runQueryIntentReceivers(); + case "clear": + return runClear(); + case "enable": + return runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_ENABLED); + case "disable": + return runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_DISABLED); + case "disable-user": + return runSetEnabledSetting( + PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER); + case "disable-until-used": + return runSetEnabledSetting( + PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED); + case "default-state": + return runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT); + case "hide": + return runSetHiddenSetting(true); + case "unhide": + return runSetHiddenSetting(false); case "suspend": return runSuspend(true); case "unsuspend": return runSuspend(false); - case "set-home-activity": - return runSetHomeActivity(); + case "grant": + return runGrantRevokePermission(true); + case "revoke": + return runGrantRevokePermission(false); + case "reset-permissions": + return runResetPermissions(); + case "set-permission-enforced": + return runSetPermissionEnforced(); case "get-privapp-permissions": return runGetPrivappPermissions(); case "get-privapp-deny-permissions": return runGetPrivappDenyPermissions(); case "get-oem-permissions": return runGetOemPermissions(); + case "set-app-link": + return runSetAppLink(); + case "get-app-link": + return runGetAppLink(); + case "trim-caches": + return runTrimCaches(); + case "create-user": + return runCreateUser(); + case "remove-user": + return runRemoveUser(); + case "set-user-restriction": + return runSetUserRestriction(); + case "get-max-users": + return runGetMaxUsers(); + case "set-home-activity": + return runSetHomeActivity(); + case "set-installer": + return runSetInstaller(); case "get-instantapp-resolver": return runGetInstantAppResolver(); case "has-feature": return runHasFeature(); - default: + default: { + String nextArg = getNextArg(); + if (nextArg == null) { + if (cmd.equalsIgnoreCase("-l")) { + return runListPackages(false); + } else if (cmd.equalsIgnoreCase("-lf")) { + return runListPackages(true); + } + } else if (getNextArg() == null) { + if (cmd.equalsIgnoreCase("-p")) { + return displayPackageFilePath(nextArg, UserHandle.USER_SYSTEM); + } + } return handleDefaultCommands(cmd); + } } } catch (RemoteException e) { pw.println("Remote exception: " + e); @@ -195,346 +282,40 @@ class PackageManagerShellCommand extends ShellCommand { } } } - - private int runInstall() throws RemoteException { - final PrintWriter pw = getOutPrintWriter(); - final InstallParams params = makeInstallParams(); - final String inPath = getNextArg(); - - setParamsSize(params, inPath); - final int sessionId = doCreateSession(params.sessionParams, - params.installerPackageName, params.userId); - boolean abandonSession = true; - try { - if (inPath == null && params.sessionParams.sizeBytes == -1) { - pw.println("Error: must either specify a package size or an APK file"); - return 1; - } - if (doWriteSplit(sessionId, inPath, params.sessionParams.sizeBytes, "base.apk", - false /*logSuccess*/) != PackageInstaller.STATUS_SUCCESS) { - return 1; - } - if (doCommitSession(sessionId, false /*logSuccess*/) - != PackageInstaller.STATUS_SUCCESS) { - return 1; - } - abandonSession = false; - pw.println("Success"); - return 0; - } finally { - if (abandonSession) { - try { - doAbandonSession(sessionId, false /*logSuccess*/); - } catch (Exception ignore) { + /** + * Displays the package file for a package. + * @param pckg + */ + private int displayPackageFilePath(String pckg, int userId) throws RemoteException { + PackageInfo info = mInterface.getPackageInfo(pckg, 0, userId); + if (info != null && info.applicationInfo != null) { + final PrintWriter pw = getOutPrintWriter(); + pw.print("package:"); + pw.println(info.applicationInfo.sourceDir); + if (!ArrayUtils.isEmpty(info.applicationInfo.splitSourceDirs)) { + for (String splitSourceDir : info.applicationInfo.splitSourceDirs) { + pw.print("package:"); + pw.println(splitSourceDir); } } - } - } - - private int runSuspend(boolean suspendedState) { - final PrintWriter pw = getOutPrintWriter(); - int userId = UserHandle.USER_SYSTEM; - String opt; - while ((opt = getNextOption()) != null) { - switch (opt) { - case "--user": - userId = UserHandle.parseUserArg(getNextArgRequired()); - break; - default: - pw.println("Error: Unknown option: " + opt); - return 1; - } - } - - String packageName = getNextArg(); - if (packageName == null) { - pw.println("Error: package name not specified"); - return 1; - } - - try { - mInterface.setPackagesSuspendedAsUser(new String[]{packageName}, suspendedState, - userId); - pw.println("Package " + packageName + " new suspended state: " - + mInterface.isPackageSuspendedForUser(packageName, userId)); return 0; - } catch (RemoteException | IllegalArgumentException e) { - pw.println(e.toString()); - return 1; } + return 1; } - private int runInstallAbandon() throws RemoteException { - final int sessionId = Integer.parseInt(getNextArg()); - return doAbandonSession(sessionId, true /*logSuccess*/); - } - - private int runInstallCommit() throws RemoteException { - final int sessionId = Integer.parseInt(getNextArg()); - return doCommitSession(sessionId, true /*logSuccess*/); - } - - private int runInstallCreate() throws RemoteException { - final PrintWriter pw = getOutPrintWriter(); - final InstallParams installParams = makeInstallParams(); - final int sessionId = doCreateSession(installParams.sessionParams, - installParams.installerPackageName, installParams.userId); - - // NOTE: adb depends on parsing this string - pw.println("Success: created install session [" + sessionId + "]"); - return 0; - } - - private int runInstallWrite() throws RemoteException { - long sizeBytes = -1; - - String opt; - while ((opt = getNextOption()) != null) { - if (opt.equals("-S")) { - sizeBytes = Long.parseLong(getNextArg()); - } else { - throw new IllegalArgumentException("Unknown option: " + opt); - } - } - - final int sessionId = Integer.parseInt(getNextArg()); - final String splitName = getNextArg(); - final String path = getNextArg(); - return doWriteSplit(sessionId, path, sizeBytes, splitName, true /*logSuccess*/); - } - - private int runInstallRemove() throws RemoteException { - final PrintWriter pw = getOutPrintWriter(); - - final int sessionId = Integer.parseInt(getNextArg()); - - final String splitName = getNextArg(); - if (splitName == null) { - pw.println("Error: split name not specified"); - return 1; - } - return doRemoveSplit(sessionId, splitName, true /*logSuccess*/); - } - - private int runInstallExisting() throws RemoteException { - final PrintWriter pw = getOutPrintWriter(); + private int runPath() throws RemoteException { int userId = UserHandle.USER_SYSTEM; - int installFlags = 0; - String opt; - while ((opt = getNextOption()) != null) { - switch (opt) { - case "--user": - userId = UserHandle.parseUserArg(getNextArgRequired()); - break; - case "--ephemeral": - case "--instant": - installFlags |= PackageManager.INSTALL_INSTANT_APP; - installFlags &= ~PackageManager.INSTALL_FULL_APP; - break; - case "--full": - installFlags &= ~PackageManager.INSTALL_INSTANT_APP; - installFlags |= PackageManager.INSTALL_FULL_APP; - break; - default: - pw.println("Error: Unknown option: " + opt); - return 1; - } + String option = getNextOption(); + if (option != null && option.equals("--user")) { + userId = UserHandle.parseUserArg(getNextArgRequired()); } - final String packageName = getNextArg(); - if (packageName == null) { - pw.println("Error: package name not specified"); - return 1; - } - - try { - final int res = mInterface.installExistingPackageAsUser(packageName, userId, - installFlags, PackageManager.INSTALL_REASON_UNKNOWN); - if (res == PackageManager.INSTALL_FAILED_INVALID_URI) { - throw new NameNotFoundException("Package " + packageName + " doesn't exist"); - } - pw.println("Package " + packageName + " installed for user: " + userId); - return 0; - } catch (RemoteException | NameNotFoundException e) { - pw.println(e.toString()); - return 1; - } - } - - private int runCompile() throws RemoteException { - final PrintWriter pw = getOutPrintWriter(); - boolean checkProfiles = SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false); - boolean forceCompilation = false; - boolean allPackages = false; - boolean clearProfileData = false; - String compilerFilter = null; - String compilationReason = null; - String checkProfilesRaw = null; - boolean secondaryDex = false; - String split = null; - - String opt; - while ((opt = getNextOption()) != null) { - switch (opt) { - case "-a": - allPackages = true; - break; - case "-c": - clearProfileData = true; - break; - case "-f": - forceCompilation = true; - break; - case "-m": - compilerFilter = getNextArgRequired(); - break; - case "-r": - compilationReason = getNextArgRequired(); - break; - case "--check-prof": - checkProfilesRaw = getNextArgRequired(); - break; - case "--reset": - forceCompilation = true; - clearProfileData = true; - compilationReason = "install"; - break; - case "--secondary-dex": - secondaryDex = true; - break; - case "--split": - split = getNextArgRequired(); - break; - default: - pw.println("Error: Unknown option: " + opt); - return 1; - } - } - - if (checkProfilesRaw != null) { - if ("true".equals(checkProfilesRaw)) { - checkProfiles = true; - } else if ("false".equals(checkProfilesRaw)) { - checkProfiles = false; - } else { - pw.println("Invalid value for \"--check-prof\". Expected \"true\" or \"false\"."); - return 1; - } - } - - if (compilerFilter != null && compilationReason != null) { - pw.println("Cannot use compilation filter (\"-m\") and compilation reason (\"-r\") " + - "at the same time"); - return 1; - } - if (compilerFilter == null && compilationReason == null) { - pw.println("Cannot run without any of compilation filter (\"-m\") and compilation " + - "reason (\"-r\") at the same time"); - return 1; - } - - if (allPackages && split != null) { - pw.println("-a cannot be specified together with --split"); - return 1; - } - - if (secondaryDex && split != null) { - pw.println("--secondary-dex cannot be specified together with --split"); - return 1; - } - - String targetCompilerFilter; - if (compilerFilter != null) { - if (!DexFile.isValidCompilerFilter(compilerFilter)) { - pw.println("Error: \"" + compilerFilter + - "\" is not a valid compilation filter."); - return 1; - } - targetCompilerFilter = compilerFilter; - } else { - int reason = -1; - for (int i = 0; i < PackageManagerServiceCompilerMapping.REASON_STRINGS.length; i++) { - if (PackageManagerServiceCompilerMapping.REASON_STRINGS[i].equals( - compilationReason)) { - reason = i; - break; - } - } - if (reason == -1) { - pw.println("Error: Unknown compilation reason: " + compilationReason); - return 1; - } - targetCompilerFilter = - PackageManagerServiceCompilerMapping.getCompilerFilterForReason(reason); - } - - - List<String> packageNames = null; - if (allPackages) { - packageNames = mInterface.getAllPackages(); - } else { - String packageName = getNextArg(); - if (packageName == null) { - pw.println("Error: package name not specified"); - return 1; - } - packageNames = Collections.singletonList(packageName); - } - - List<String> failedPackages = new ArrayList<>(); - for (String packageName : packageNames) { - if (clearProfileData) { - mInterface.clearApplicationProfileData(packageName); - } - - boolean result = secondaryDex - ? mInterface.performDexOptSecondary(packageName, - targetCompilerFilter, forceCompilation) - : mInterface.performDexOptMode(packageName, - checkProfiles, targetCompilerFilter, forceCompilation, - true /* bootComplete */, split); - if (!result) { - failedPackages.add(packageName); - } - } - - if (failedPackages.isEmpty()) { - pw.println("Success"); - return 0; - } else if (failedPackages.size() == 1) { - pw.println("Failure: package " + failedPackages.get(0) + " could not be compiled"); - return 1; - } else { - pw.print("Failure: the following packages could not be compiled: "); - boolean is_first = true; - for (String packageName : failedPackages) { - if (is_first) { - is_first = false; - } else { - pw.print(", "); - } - pw.print(packageName); - } - pw.println(); + String pkg = getNextArgRequired(); + if (pkg == null) { + getErrPrintWriter().println("Error: no package specified"); return 1; } - } - - private int runreconcileSecondaryDexFiles() throws RemoteException { - String packageName = getNextArg(); - mInterface.reconcileSecondaryDexFiles(packageName); - return 0; - } - - private int runDexoptJob() throws RemoteException { - boolean result = mInterface.runBackgroundDexoptJob(); - return result ? 0 : -1; - } - - private int runDumpProfiles() throws RemoteException { - String packageName = getNextArg(); - mInterface.dumpProfiles(packageName); - return 0; + return displayPackageFilePath(pkg, userId); } private int runList() throws RemoteException { @@ -558,6 +339,11 @@ class PackageManagerShellCommand extends ShellCommand { return runListPermissionGroups(); case "permissions": return runListPermissions(); + case "users": + ServiceManager.getService("user").shellCommand( + getInFileDescriptor(), getOutFileDescriptor(), getErrFileDescriptor(), + new String[] { "list" }, getShellCallback(), adoptResultReceiver()); + return 0; } pw.println("Error: unknown list type '" + type + "'"); return -1; @@ -590,7 +376,7 @@ class PackageManagerShellCommand extends ShellCommand { pw.println(); } else { pw.println("reqGlEsVersion=0x" - + Integer.toHexString(fi.reqGlEsVersion)); + + Integer.toHexString(fi.reqGlEsVersion)); } } return 0; @@ -872,111 +658,6 @@ class PackageManagerShellCommand extends ShellCommand { return 0; } - private int runUninstall() throws RemoteException { - final PrintWriter pw = getOutPrintWriter(); - int flags = 0; - int userId = UserHandle.USER_ALL; - int versionCode = PackageManager.VERSION_CODE_HIGHEST; - - String opt; - while ((opt = getNextOption()) != null) { - switch (opt) { - case "-k": - flags |= PackageManager.DELETE_KEEP_DATA; - break; - case "--user": - userId = UserHandle.parseUserArg(getNextArgRequired()); - break; - case "--versionCode": - versionCode = Integer.parseInt(getNextArgRequired()); - break; - default: - pw.println("Error: Unknown option: " + opt); - return 1; - } - } - - final String packageName = getNextArg(); - if (packageName == null) { - pw.println("Error: package name not specified"); - return 1; - } - - // if a split is specified, just remove it and not the whole package - final String splitName = getNextArg(); - if (splitName != null) { - return runRemoveSplit(packageName, splitName); - } - - userId = translateUserId(userId, "runUninstall"); - if (userId == UserHandle.USER_ALL) { - userId = UserHandle.USER_SYSTEM; - flags |= PackageManager.DELETE_ALL_USERS; - } else { - final PackageInfo info = mInterface.getPackageInfo(packageName, - PackageManager.MATCH_STATIC_SHARED_LIBRARIES, userId); - if (info == null) { - pw.println("Failure [not installed for " + userId + "]"); - return 1; - } - final boolean isSystem = - (info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0; - // If we are being asked to delete a system app for just one - // user set flag so it disables rather than reverting to system - // version of the app. - if (isSystem) { - flags |= PackageManager.DELETE_SYSTEM_APP; - } - } - - final LocalIntentReceiver receiver = new LocalIntentReceiver(); - mInterface.getPackageInstaller().uninstall(new VersionedPackage(packageName, - versionCode), null /*callerPackageName*/, flags, - receiver.getIntentSender(), userId); - - final Intent result = receiver.getResult(); - final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS, - PackageInstaller.STATUS_FAILURE); - if (status == PackageInstaller.STATUS_SUCCESS) { - pw.println("Success"); - return 0; - } else { - pw.println("Failure [" - + result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) + "]"); - return 1; - } - } - - private int runRemoveSplit(String packageName, String splitName) throws RemoteException { - final PrintWriter pw = getOutPrintWriter(); - final SessionParams sessionParams = new SessionParams(SessionParams.MODE_INHERIT_EXISTING); - sessionParams.installFlags |= PackageManager.INSTALL_REPLACE_EXISTING; - sessionParams.appPackageName = packageName; - final int sessionId = - doCreateSession(sessionParams, null /*installerPackageName*/, UserHandle.USER_ALL); - boolean abandonSession = true; - try { - if (doRemoveSplit(sessionId, splitName, false /*logSuccess*/) - != PackageInstaller.STATUS_SUCCESS) { - return 1; - } - if (doCommitSession(sessionId, false /*logSuccess*/) - != PackageInstaller.STATUS_SUCCESS) { - return 1; - } - abandonSession = false; - pw.println("Success"); - return 0; - } finally { - if (abandonSession) { - try { - doAbandonSession(sessionId, false /*logSuccess*/); - } catch (Exception ignore) { - } - } - } - } - private Intent parseIntentAndUser() throws URISyntaxException { mTargetUser = UserHandle.USER_CURRENT; mBrief = false; @@ -1154,6 +835,1029 @@ class PackageManagerShellCommand extends ShellCommand { return 0; } + private int runInstall() throws RemoteException { + final PrintWriter pw = getOutPrintWriter(); + final InstallParams params = makeInstallParams(); + final String inPath = getNextArg(); + + setParamsSize(params, inPath); + final int sessionId = doCreateSession(params.sessionParams, + params.installerPackageName, params.userId); + boolean abandonSession = true; + try { + if (inPath == null && params.sessionParams.sizeBytes == -1) { + pw.println("Error: must either specify a package size or an APK file"); + return 1; + } + if (doWriteSplit(sessionId, inPath, params.sessionParams.sizeBytes, "base.apk", + false /*logSuccess*/) != PackageInstaller.STATUS_SUCCESS) { + return 1; + } + if (doCommitSession(sessionId, false /*logSuccess*/) + != PackageInstaller.STATUS_SUCCESS) { + return 1; + } + abandonSession = false; + pw.println("Success"); + return 0; + } finally { + if (abandonSession) { + try { + doAbandonSession(sessionId, false /*logSuccess*/); + } catch (Exception ignore) { + } + } + } + } + + private int runInstallAbandon() throws RemoteException { + final int sessionId = Integer.parseInt(getNextArg()); + return doAbandonSession(sessionId, true /*logSuccess*/); + } + + private int runInstallCommit() throws RemoteException { + final int sessionId = Integer.parseInt(getNextArg()); + return doCommitSession(sessionId, true /*logSuccess*/); + } + + private int runInstallCreate() throws RemoteException { + final PrintWriter pw = getOutPrintWriter(); + final InstallParams installParams = makeInstallParams(); + final int sessionId = doCreateSession(installParams.sessionParams, + installParams.installerPackageName, installParams.userId); + + // NOTE: adb depends on parsing this string + pw.println("Success: created install session [" + sessionId + "]"); + return 0; + } + + private int runInstallWrite() throws RemoteException { + long sizeBytes = -1; + + String opt; + while ((opt = getNextOption()) != null) { + if (opt.equals("-S")) { + sizeBytes = Long.parseLong(getNextArg()); + } else { + throw new IllegalArgumentException("Unknown option: " + opt); + } + } + + final int sessionId = Integer.parseInt(getNextArg()); + final String splitName = getNextArg(); + final String path = getNextArg(); + return doWriteSplit(sessionId, path, sizeBytes, splitName, true /*logSuccess*/); + } + + private int runInstallRemove() throws RemoteException { + final PrintWriter pw = getOutPrintWriter(); + + final int sessionId = Integer.parseInt(getNextArg()); + + final String splitName = getNextArg(); + if (splitName == null) { + pw.println("Error: split name not specified"); + return 1; + } + return doRemoveSplit(sessionId, splitName, true /*logSuccess*/); + } + + private int runInstallExisting() throws RemoteException { + final PrintWriter pw = getOutPrintWriter(); + int userId = UserHandle.USER_SYSTEM; + int installFlags = 0; + String opt; + while ((opt = getNextOption()) != null) { + switch (opt) { + case "--user": + userId = UserHandle.parseUserArg(getNextArgRequired()); + break; + case "--ephemeral": + case "--instant": + installFlags |= PackageManager.INSTALL_INSTANT_APP; + installFlags &= ~PackageManager.INSTALL_FULL_APP; + break; + case "--full": + installFlags &= ~PackageManager.INSTALL_INSTANT_APP; + installFlags |= PackageManager.INSTALL_FULL_APP; + break; + default: + pw.println("Error: Unknown option: " + opt); + return 1; + } + } + + final String packageName = getNextArg(); + if (packageName == null) { + pw.println("Error: package name not specified"); + return 1; + } + + try { + final int res = mInterface.installExistingPackageAsUser(packageName, userId, + installFlags, PackageManager.INSTALL_REASON_UNKNOWN); + if (res == PackageManager.INSTALL_FAILED_INVALID_URI) { + throw new NameNotFoundException("Package " + packageName + " doesn't exist"); + } + pw.println("Package " + packageName + " installed for user: " + userId); + return 0; + } catch (RemoteException | NameNotFoundException e) { + pw.println(e.toString()); + return 1; + } + } + + private int runSetInstallLocation() throws RemoteException { + int loc; + + String arg = getNextArg(); + if (arg == null) { + getErrPrintWriter().println("Error: no install location specified."); + return 1; + } + try { + loc = Integer.parseInt(arg); + } catch (NumberFormatException e) { + getErrPrintWriter().println("Error: install location has to be a number."); + return 1; + } + if (!mInterface.setInstallLocation(loc)) { + getErrPrintWriter().println("Error: install location has to be a number."); + return 1; + } + return 0; + } + + private int runGetInstallLocation() throws RemoteException { + int loc = mInterface.getInstallLocation(); + String locStr = "invalid"; + if (loc == PackageHelper.APP_INSTALL_AUTO) { + locStr = "auto"; + } else if (loc == PackageHelper.APP_INSTALL_INTERNAL) { + locStr = "internal"; + } else if (loc == PackageHelper.APP_INSTALL_EXTERNAL) { + locStr = "external"; + } + getOutPrintWriter().println(loc + "[" + locStr + "]"); + return 0; + } + + public int runMovePackage() throws RemoteException { + final String packageName = getNextArg(); + if (packageName == null) { + getErrPrintWriter().println("Error: package name not specified"); + return 1; + } + String volumeUuid = getNextArg(); + if ("internal".equals(volumeUuid)) { + volumeUuid = null; + } + + final int moveId = mInterface.movePackage(packageName, volumeUuid); + + int status = mInterface.getMoveStatus(moveId); + while (!PackageManager.isMoveStatusFinished(status)) { + SystemClock.sleep(DateUtils.SECOND_IN_MILLIS); + status = mInterface.getMoveStatus(moveId); + } + + if (status == PackageManager.MOVE_SUCCEEDED) { + getOutPrintWriter().println("Success"); + return 0; + } else { + getErrPrintWriter().println("Failure [" + status + "]"); + return 1; + } + } + + public int runMovePrimaryStorage() throws RemoteException { + String volumeUuid = getNextArg(); + if ("internal".equals(volumeUuid)) { + volumeUuid = null; + } + + final int moveId = mInterface.movePrimaryStorage(volumeUuid); + + int status = mInterface.getMoveStatus(moveId); + while (!PackageManager.isMoveStatusFinished(status)) { + SystemClock.sleep(DateUtils.SECOND_IN_MILLIS); + status = mInterface.getMoveStatus(moveId); + } + + if (status == PackageManager.MOVE_SUCCEEDED) { + getOutPrintWriter().println("Success"); + return 0; + } else { + getErrPrintWriter().println("Failure [" + status + "]"); + return 1; + } + } + + private int runCompile() throws RemoteException { + final PrintWriter pw = getOutPrintWriter(); + boolean checkProfiles = SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false); + boolean forceCompilation = false; + boolean allPackages = false; + boolean clearProfileData = false; + String compilerFilter = null; + String compilationReason = null; + String checkProfilesRaw = null; + boolean secondaryDex = false; + String split = null; + + String opt; + while ((opt = getNextOption()) != null) { + switch (opt) { + case "-a": + allPackages = true; + break; + case "-c": + clearProfileData = true; + break; + case "-f": + forceCompilation = true; + break; + case "-m": + compilerFilter = getNextArgRequired(); + break; + case "-r": + compilationReason = getNextArgRequired(); + break; + case "--check-prof": + checkProfilesRaw = getNextArgRequired(); + break; + case "--reset": + forceCompilation = true; + clearProfileData = true; + compilationReason = "install"; + break; + case "--secondary-dex": + secondaryDex = true; + break; + case "--split": + split = getNextArgRequired(); + break; + default: + pw.println("Error: Unknown option: " + opt); + return 1; + } + } + + if (checkProfilesRaw != null) { + if ("true".equals(checkProfilesRaw)) { + checkProfiles = true; + } else if ("false".equals(checkProfilesRaw)) { + checkProfiles = false; + } else { + pw.println("Invalid value for \"--check-prof\". Expected \"true\" or \"false\"."); + return 1; + } + } + + if (compilerFilter != null && compilationReason != null) { + pw.println("Cannot use compilation filter (\"-m\") and compilation reason (\"-r\") " + + "at the same time"); + return 1; + } + if (compilerFilter == null && compilationReason == null) { + pw.println("Cannot run without any of compilation filter (\"-m\") and compilation " + + "reason (\"-r\") at the same time"); + return 1; + } + + if (allPackages && split != null) { + pw.println("-a cannot be specified together with --split"); + return 1; + } + + if (secondaryDex && split != null) { + pw.println("--secondary-dex cannot be specified together with --split"); + return 1; + } + + String targetCompilerFilter; + if (compilerFilter != null) { + if (!DexFile.isValidCompilerFilter(compilerFilter)) { + pw.println("Error: \"" + compilerFilter + + "\" is not a valid compilation filter."); + return 1; + } + targetCompilerFilter = compilerFilter; + } else { + int reason = -1; + for (int i = 0; i < PackageManagerServiceCompilerMapping.REASON_STRINGS.length; i++) { + if (PackageManagerServiceCompilerMapping.REASON_STRINGS[i].equals( + compilationReason)) { + reason = i; + break; + } + } + if (reason == -1) { + pw.println("Error: Unknown compilation reason: " + compilationReason); + return 1; + } + targetCompilerFilter = + PackageManagerServiceCompilerMapping.getCompilerFilterForReason(reason); + } + + + List<String> packageNames = null; + if (allPackages) { + packageNames = mInterface.getAllPackages(); + } else { + String packageName = getNextArg(); + if (packageName == null) { + pw.println("Error: package name not specified"); + return 1; + } + packageNames = Collections.singletonList(packageName); + } + + List<String> failedPackages = new ArrayList<>(); + for (String packageName : packageNames) { + if (clearProfileData) { + mInterface.clearApplicationProfileData(packageName); + } + + boolean result = secondaryDex + ? mInterface.performDexOptSecondary(packageName, + targetCompilerFilter, forceCompilation) + : mInterface.performDexOptMode(packageName, + checkProfiles, targetCompilerFilter, forceCompilation, + true /* bootComplete */, split); + if (!result) { + failedPackages.add(packageName); + } + } + + if (failedPackages.isEmpty()) { + pw.println("Success"); + return 0; + } else if (failedPackages.size() == 1) { + pw.println("Failure: package " + failedPackages.get(0) + " could not be compiled"); + return 1; + } else { + pw.print("Failure: the following packages could not be compiled: "); + boolean is_first = true; + for (String packageName : failedPackages) { + if (is_first) { + is_first = false; + } else { + pw.print(", "); + } + pw.print(packageName); + } + pw.println(); + return 1; + } + } + + private int runreconcileSecondaryDexFiles() throws RemoteException { + String packageName = getNextArg(); + mInterface.reconcileSecondaryDexFiles(packageName); + return 0; + } + + public int runForceDexOpt() throws RemoteException { + mInterface.forceDexOpt(getNextArgRequired()); + return 0; + } + + private int runDexoptJob() throws RemoteException { + boolean result = mInterface.runBackgroundDexoptJob(); + return result ? 0 : -1; + } + + private int runDumpProfiles() throws RemoteException { + String packageName = getNextArg(); + mInterface.dumpProfiles(packageName); + return 0; + } + + private int runUninstall() throws RemoteException { + final PrintWriter pw = getOutPrintWriter(); + int flags = 0; + int userId = UserHandle.USER_ALL; + int versionCode = PackageManager.VERSION_CODE_HIGHEST; + + String opt; + while ((opt = getNextOption()) != null) { + switch (opt) { + case "-k": + flags |= PackageManager.DELETE_KEEP_DATA; + break; + case "--user": + userId = UserHandle.parseUserArg(getNextArgRequired()); + break; + case "--versionCode": + versionCode = Integer.parseInt(getNextArgRequired()); + break; + default: + pw.println("Error: Unknown option: " + opt); + return 1; + } + } + + final String packageName = getNextArg(); + if (packageName == null) { + pw.println("Error: package name not specified"); + return 1; + } + + // if a split is specified, just remove it and not the whole package + final String splitName = getNextArg(); + if (splitName != null) { + return runRemoveSplit(packageName, splitName); + } + + userId = translateUserId(userId, "runUninstall"); + if (userId == UserHandle.USER_ALL) { + userId = UserHandle.USER_SYSTEM; + flags |= PackageManager.DELETE_ALL_USERS; + } else { + final PackageInfo info = mInterface.getPackageInfo(packageName, + PackageManager.MATCH_STATIC_SHARED_LIBRARIES, userId); + if (info == null) { + pw.println("Failure [not installed for " + userId + "]"); + return 1; + } + final boolean isSystem = + (info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0; + // If we are being asked to delete a system app for just one + // user set flag so it disables rather than reverting to system + // version of the app. + if (isSystem) { + flags |= PackageManager.DELETE_SYSTEM_APP; + } + } + + final LocalIntentReceiver receiver = new LocalIntentReceiver(); + mInterface.getPackageInstaller().uninstall(new VersionedPackage(packageName, + versionCode), null /*callerPackageName*/, flags, + receiver.getIntentSender(), userId); + + final Intent result = receiver.getResult(); + final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS, + PackageInstaller.STATUS_FAILURE); + if (status == PackageInstaller.STATUS_SUCCESS) { + pw.println("Success"); + return 0; + } else { + pw.println("Failure [" + + result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) + "]"); + return 1; + } + } + + private int runRemoveSplit(String packageName, String splitName) throws RemoteException { + final PrintWriter pw = getOutPrintWriter(); + final SessionParams sessionParams = new SessionParams(SessionParams.MODE_INHERIT_EXISTING); + sessionParams.installFlags |= PackageManager.INSTALL_REPLACE_EXISTING; + sessionParams.appPackageName = packageName; + final int sessionId = + doCreateSession(sessionParams, null /*installerPackageName*/, UserHandle.USER_ALL); + boolean abandonSession = true; + try { + if (doRemoveSplit(sessionId, splitName, false /*logSuccess*/) + != PackageInstaller.STATUS_SUCCESS) { + return 1; + } + if (doCommitSession(sessionId, false /*logSuccess*/) + != PackageInstaller.STATUS_SUCCESS) { + return 1; + } + abandonSession = false; + pw.println("Success"); + return 0; + } finally { + if (abandonSession) { + try { + doAbandonSession(sessionId, false /*logSuccess*/); + } catch (Exception ignore) { + } + } + } + } + + static class ClearDataObserver extends IPackageDataObserver.Stub { + boolean finished; + boolean result; + + @Override + public void onRemoveCompleted(String packageName, boolean succeeded) throws RemoteException { + synchronized (this) { + finished = true; + result = succeeded; + notifyAll(); + } + } + } + + private int runClear() throws RemoteException { + int userId = UserHandle.USER_SYSTEM; + String option = getNextOption(); + if (option != null && option.equals("--user")) { + userId = UserHandle.parseUserArg(getNextArgRequired()); + } + + String pkg = getNextArg(); + if (pkg == null) { + getErrPrintWriter().println("Error: no package specified"); + return 1; + } + + ClearDataObserver obs = new ClearDataObserver(); + ActivityManager.getService().clearApplicationUserData(pkg, obs, userId); + synchronized (obs) { + while (!obs.finished) { + try { + obs.wait(); + } catch (InterruptedException e) { + } + } + } + + if (obs.result) { + getOutPrintWriter().println("Success"); + return 0; + } else { + getErrPrintWriter().println("Failed"); + return 1; + } + } + + private static String enabledSettingToString(int state) { + switch (state) { + case PackageManager.COMPONENT_ENABLED_STATE_DEFAULT: + return "default"; + case PackageManager.COMPONENT_ENABLED_STATE_ENABLED: + return "enabled"; + case PackageManager.COMPONENT_ENABLED_STATE_DISABLED: + return "disabled"; + case PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER: + return "disabled-user"; + case PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED: + return "disabled-until-used"; + } + return "unknown"; + } + + private int runSetEnabledSetting(int state) throws RemoteException { + int userId = UserHandle.USER_SYSTEM; + String option = getNextOption(); + if (option != null && option.equals("--user")) { + userId = UserHandle.parseUserArg(getNextArgRequired()); + } + + String pkg = getNextArg(); + if (pkg == null) { + getErrPrintWriter().println("Error: no package or component specified"); + return 1; + } + ComponentName cn = ComponentName.unflattenFromString(pkg); + if (cn == null) { + mInterface.setApplicationEnabledSetting(pkg, state, 0, userId, + "shell:" + android.os.Process.myUid()); + getOutPrintWriter().println("Package " + pkg + " new state: " + + enabledSettingToString( + mInterface.getApplicationEnabledSetting(pkg, userId))); + return 0; + } else { + mInterface.setComponentEnabledSetting(cn, state, 0, userId); + getOutPrintWriter().println("Component " + cn.toShortString() + " new state: " + + enabledSettingToString( + mInterface.getComponentEnabledSetting(cn, userId))); + return 0; + } + } + + private int runSetHiddenSetting(boolean state) throws RemoteException { + int userId = UserHandle.USER_SYSTEM; + String option = getNextOption(); + if (option != null && option.equals("--user")) { + userId = UserHandle.parseUserArg(getNextArgRequired()); + } + + String pkg = getNextArg(); + if (pkg == null) { + getErrPrintWriter().println("Error: no package or component specified"); + return 1; + } + mInterface.setApplicationHiddenSettingAsUser(pkg, state, userId); + getOutPrintWriter().println("Package " + pkg + " new hidden state: " + + mInterface.getApplicationHiddenSettingAsUser(pkg, userId)); + return 0; + } + + private int runSuspend(boolean suspendedState) { + final PrintWriter pw = getOutPrintWriter(); + int userId = UserHandle.USER_SYSTEM; + String opt; + while ((opt = getNextOption()) != null) { + switch (opt) { + case "--user": + userId = UserHandle.parseUserArg(getNextArgRequired()); + break; + default: + pw.println("Error: Unknown option: " + opt); + return 1; + } + } + + String packageName = getNextArg(); + if (packageName == null) { + pw.println("Error: package name not specified"); + return 1; + } + + try { + mInterface.setPackagesSuspendedAsUser(new String[]{packageName}, suspendedState, + userId); + pw.println("Package " + packageName + " new suspended state: " + + mInterface.isPackageSuspendedForUser(packageName, userId)); + return 0; + } catch (RemoteException | IllegalArgumentException e) { + pw.println(e.toString()); + return 1; + } + } + + private int runGrantRevokePermission(boolean grant) throws RemoteException { + int userId = UserHandle.USER_SYSTEM; + + String opt = null; + while ((opt = getNextOption()) != null) { + if (opt.equals("--user")) { + userId = UserHandle.parseUserArg(getNextArgRequired()); + } + } + + String pkg = getNextArg(); + if (pkg == null) { + getErrPrintWriter().println("Error: no package specified"); + return 1; + } + String perm = getNextArg(); + if (perm == null) { + getErrPrintWriter().println("Error: no permission specified"); + return 1; + } + + if (grant) { + mInterface.grantRuntimePermission(pkg, perm, userId); + } else { + mInterface.revokeRuntimePermission(pkg, perm, userId); + } + return 0; + } + + private int runResetPermissions() throws RemoteException { + mInterface.resetRuntimePermissions(); + return 0; + } + + private int runSetPermissionEnforced() throws RemoteException { + final String permission = getNextArg(); + if (permission == null) { + getErrPrintWriter().println("Error: no permission specified"); + return 1; + } + final String enforcedRaw = getNextArg(); + if (enforcedRaw == null) { + getErrPrintWriter().println("Error: no enforcement specified"); + return 1; + } + mInterface.setPermissionEnforced(permission, Boolean.parseBoolean(enforcedRaw)); + return 0; + } + + private int runGetPrivappPermissions() { + final String pkg = getNextArg(); + if (pkg == null) { + getErrPrintWriter().println("Error: no package specified."); + return 1; + } + ArraySet<String> privAppPermissions = SystemConfig.getInstance().getPrivAppPermissions(pkg); + getOutPrintWriter().println(privAppPermissions == null + ? "{}" : privAppPermissions.toString()); + return 0; + } + + private int runGetPrivappDenyPermissions() { + final String pkg = getNextArg(); + if (pkg == null) { + getErrPrintWriter().println("Error: no package specified."); + return 1; + } + ArraySet<String> privAppDenyPermissions = + SystemConfig.getInstance().getPrivAppDenyPermissions(pkg); + getOutPrintWriter().println(privAppDenyPermissions == null + ? "{}" : privAppDenyPermissions.toString()); + return 0; + } + + private int runGetOemPermissions() { + final String pkg = getNextArg(); + if (pkg == null) { + getErrPrintWriter().println("Error: no package specified."); + return 1; + } + final Map<String, Boolean> oemPermissions = SystemConfig.getInstance() + .getOemPermissions(pkg); + if (oemPermissions == null || oemPermissions.isEmpty()) { + getOutPrintWriter().println("{}"); + } else { + oemPermissions.forEach((permission, granted) -> + getOutPrintWriter().println(permission + " granted:" + granted) + ); + } + return 0; + } + + private String linkStateToString(int state) { + switch (state) { + case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED: return "undefined"; + case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK: return "ask"; + case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS: return "always"; + case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER: return "never"; + case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK : return "always ask"; + } + return "Unknown link state: " + state; + } + + // pm set-app-link [--user USER_ID] PACKAGE {always|ask|always-ask|never|undefined} + private int runSetAppLink() throws RemoteException { + int userId = UserHandle.USER_SYSTEM; + + String opt; + while ((opt = getNextOption()) != null) { + if (opt.equals("--user")) { + userId = UserHandle.parseUserArg(getNextArgRequired()); + } else { + getErrPrintWriter().println("Error: unknown option: " + opt); + return 1; + } + } + + // Package name to act on; required + final String pkg = getNextArg(); + if (pkg == null) { + getErrPrintWriter().println("Error: no package specified."); + return 1; + } + + // State to apply; {always|ask|never|undefined}, required + final String modeString = getNextArg(); + if (modeString == null) { + getErrPrintWriter().println("Error: no app link state specified."); + return 1; + } + + final int newMode; + switch (modeString.toLowerCase()) { + case "undefined": + newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED; + break; + + case "always": + newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS; + break; + + case "ask": + newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK; + break; + + case "always-ask": + newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK; + break; + + case "never": + newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER; + break; + + default: + getErrPrintWriter().println("Error: unknown app link state '" + modeString + "'"); + return 1; + } + + final PackageInfo info = mInterface.getPackageInfo(pkg, 0, userId); + if (info == null) { + getErrPrintWriter().println("Error: package " + pkg + " not found."); + return 1; + } + + if ((info.applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS) == 0) { + getErrPrintWriter().println("Error: package " + pkg + " does not handle web links."); + return 1; + } + + if (!mInterface.updateIntentVerificationStatus(pkg, newMode, userId)) { + getErrPrintWriter().println("Error: unable to update app link status for " + pkg); + return 1; + } + + return 0; + } + + // pm get-app-link [--user USER_ID] PACKAGE + private int runGetAppLink() throws RemoteException { + int userId = UserHandle.USER_SYSTEM; + + String opt; + while ((opt = getNextOption()) != null) { + if (opt.equals("--user")) { + userId = UserHandle.parseUserArg(getNextArgRequired()); + } else { + getErrPrintWriter().println("Error: unknown option: " + opt); + return 1; + } + } + + // Package name to act on; required + final String pkg = getNextArg(); + if (pkg == null) { + getErrPrintWriter().println("Error: no package specified."); + return 1; + } + + final PackageInfo info = mInterface.getPackageInfo(pkg, 0, userId); + if (info == null) { + getErrPrintWriter().println("Error: package " + pkg + " not found."); + return 1; + } + + if ((info.applicationInfo.privateFlags + & ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS) == 0) { + getErrPrintWriter().println("Error: package " + pkg + " does not handle web links."); + return 1; + } + + getOutPrintWriter().println(linkStateToString( + mInterface.getIntentVerificationStatus(pkg, userId))); + + return 0; + } + + private int runTrimCaches() throws RemoteException { + String size = getNextArg(); + if (size == null) { + getErrPrintWriter().println("Error: no size specified"); + return 1; + } + long multiplier = 1; + int len = size.length(); + char c = size.charAt(len - 1); + if (c < '0' || c > '9') { + if (c == 'K' || c == 'k') { + multiplier = 1024L; + } else if (c == 'M' || c == 'm') { + multiplier = 1024L*1024L; + } else if (c == 'G' || c == 'g') { + multiplier = 1024L*1024L*1024L; + } else { + getErrPrintWriter().println("Invalid suffix: " + c); + return 1; + } + size = size.substring(0, len-1); + } + long sizeVal; + try { + sizeVal = Long.parseLong(size) * multiplier; + } catch (NumberFormatException e) { + getErrPrintWriter().println("Error: expected number at: " + size); + return 1; + } + String volumeUuid = getNextArg(); + if ("internal".equals(volumeUuid)) { + volumeUuid = null; + } + ClearDataObserver obs = new ClearDataObserver(); + mInterface.freeStorageAndNotify(volumeUuid, sizeVal, + StorageManager.FLAG_ALLOCATE_DEFY_ALL_RESERVED, obs); + synchronized (obs) { + while (!obs.finished) { + try { + obs.wait(); + } catch (InterruptedException e) { + } + } + } + return 0; + } + + private static boolean isNumber(String s) { + try { + Integer.parseInt(s); + } catch (NumberFormatException nfe) { + return false; + } + return true; + } + + public int runCreateUser() throws RemoteException { + String name; + int userId = -1; + int flags = 0; + String opt; + while ((opt = getNextOption()) != null) { + if ("--profileOf".equals(opt)) { + userId = UserHandle.parseUserArg(getNextArgRequired()); + } else if ("--managed".equals(opt)) { + flags |= UserInfo.FLAG_MANAGED_PROFILE; + } else if ("--restricted".equals(opt)) { + flags |= UserInfo.FLAG_RESTRICTED; + } else if ("--ephemeral".equals(opt)) { + flags |= UserInfo.FLAG_EPHEMERAL; + } else if ("--guest".equals(opt)) { + flags |= UserInfo.FLAG_GUEST; + } else if ("--demo".equals(opt)) { + flags |= UserInfo.FLAG_DEMO; + } else { + getErrPrintWriter().println("Error: unknown option " + opt); + return 1; + } + } + String arg = getNextArg(); + if (arg == null) { + getErrPrintWriter().println("Error: no user name specified."); + return 1; + } + name = arg; + UserInfo info; + IUserManager um = IUserManager.Stub.asInterface( + ServiceManager.getService(Context.USER_SERVICE)); + IAccountManager accm = IAccountManager.Stub.asInterface( + ServiceManager.getService(Context.ACCOUNT_SERVICE)); + if ((flags & UserInfo.FLAG_RESTRICTED) != 0) { + // In non-split user mode, userId can only be SYSTEM + int parentUserId = userId >= 0 ? userId : UserHandle.USER_SYSTEM; + info = um.createRestrictedProfile(name, parentUserId); + accm.addSharedAccountsFromParentUser(parentUserId, userId, + (Process.myUid() == Process.ROOT_UID) ? "root" : "com.android.shell"); + } else if (userId < 0) { + info = um.createUser(name, flags); + } else { + info = um.createProfileForUser(name, flags, userId, null); + } + + if (info != null) { + getOutPrintWriter().println("Success: created user id " + info.id); + return 0; + } else { + getErrPrintWriter().println("Error: couldn't create User."); + return 1; + } + } + + public int runRemoveUser() throws RemoteException { + int userId; + String arg = getNextArg(); + if (arg == null) { + getErrPrintWriter().println("Error: no user id specified."); + return 1; + } + userId = UserHandle.parseUserArg(arg); + IUserManager um = IUserManager.Stub.asInterface( + ServiceManager.getService(Context.USER_SERVICE)); + if (um.removeUser(userId)) { + getOutPrintWriter().println("Success: removed user"); + return 0; + } else { + getErrPrintWriter().println("Error: couldn't remove user id " + userId); + return 1; + } + } + + public int runSetUserRestriction() throws RemoteException { + int userId = UserHandle.USER_SYSTEM; + String opt = getNextOption(); + if (opt != null && "--user".equals(opt)) { + userId = UserHandle.parseUserArg(getNextArgRequired()); + } + + String restriction = getNextArg(); + String arg = getNextArg(); + boolean value; + if ("1".equals(arg)) { + value = true; + } else if ("0".equals(arg)) { + value = false; + } else { + getErrPrintWriter().println("Error: valid value not specified"); + return 1; + } + IUserManager um = IUserManager.Stub.asInterface( + ServiceManager.getService(Context.USER_SERVICE)); + um.setUserRestriction(restriction, value, userId); + return 0; + } + + public int runGetMaxUsers() { + getOutPrintWriter().println("Maximum supported users: " + + UserManager.getMaxSupportedUsers()); + return 0; + } + private static class InstallParams { SessionParams sessionParams; String installerPackageName; @@ -1287,46 +1991,17 @@ class PackageManagerShellCommand extends ShellCommand { } } - private int runGetPrivappPermissions() { - final String pkg = getNextArg(); - if (pkg == null) { - System.err.println("Error: no package specified."); - return 1; - } - ArraySet<String> privAppPermissions = SystemConfig.getInstance().getPrivAppPermissions(pkg); - getOutPrintWriter().println(privAppPermissions == null - ? "{}" : privAppPermissions.toString()); - return 0; - } + private int runSetInstaller() throws RemoteException { + final String targetPackage = getNextArg(); + final String installerPackageName = getNextArg(); - private int runGetPrivappDenyPermissions() { - final String pkg = getNextArg(); - if (pkg == null) { - System.err.println("Error: no package specified."); + if (targetPackage == null || installerPackageName == null) { + getErrPrintWriter().println("Must provide both target and installer package names"); return 1; } - ArraySet<String> privAppDenyPermissions = - SystemConfig.getInstance().getPrivAppDenyPermissions(pkg); - getOutPrintWriter().println(privAppDenyPermissions == null - ? "{}" : privAppDenyPermissions.toString()); - return 0; - } - private int runGetOemPermissions() { - final String pkg = getNextArg(); - if (pkg == null) { - System.err.println("Error: no package specified."); - return 1; - } - final Map<String, Boolean> oemPermissions = SystemConfig.getInstance() - .getOemPermissions(pkg); - if (oemPermissions == null || oemPermissions.isEmpty()) { - getOutPrintWriter().println("{}"); - } else { - oemPermissions.forEach((permission, granted) -> - getOutPrintWriter().println(permission + " granted:" + granted) - ); - } + mInterface.setInstallerPackageName(targetPackage, installerPackageName); + getOutPrintWriter().println("Success"); return 0; } @@ -1367,6 +2042,16 @@ class PackageManagerShellCommand extends ShellCommand { } } + private int runDump() { + String pkg = getNextArg(); + if (pkg == null) { + getErrPrintWriter().println("Error: no package specified"); + return 1; + } + ActivityManager.dumpPackageStateStatic(getOutFileDescriptor(), pkg); + return 0; + } + private static String checkAbiArgument(String abi) { if (TextUtils.isEmpty(abi)) { throw new IllegalArgumentException("Missing ABI argument"); @@ -1663,52 +2348,31 @@ class PackageManagerShellCommand extends ShellCommand { pw.println(" help"); pw.println(" Print this help text."); pw.println(""); - pw.println(" compile [-m MODE | -r REASON] [-f] [-c] [--split SPLIT_NAME]"); - pw.println(" [--reset] [--check-prof (true | false)] (-a | TARGET-PACKAGE)"); - pw.println(" Trigger compilation of TARGET-PACKAGE or all packages if \"-a\"."); - pw.println(" Options:"); - pw.println(" -a: compile all packages"); - pw.println(" -c: clear profile data before compiling"); - pw.println(" -f: force compilation even if not needed"); - pw.println(" -m: select compilation mode"); - pw.println(" MODE is one of the dex2oat compiler filters:"); - pw.println(" assume-verified"); - pw.println(" extract"); - pw.println(" verify"); - pw.println(" quicken"); - pw.println(" space-profile"); - pw.println(" space"); - pw.println(" speed-profile"); - pw.println(" speed"); - pw.println(" everything"); - pw.println(" -r: select compilation reason"); - pw.println(" REASON is one of:"); - for (int i = 0; i < PackageManagerServiceCompilerMapping.REASON_STRINGS.length; i++) { - pw.println(" " + PackageManagerServiceCompilerMapping.REASON_STRINGS[i]); - } - pw.println(" --reset: restore package to its post-install state"); - pw.println(" --check-prof (true | false): look at profiles when doing dexopt?"); - pw.println(" --secondary-dex: compile app secondary dex files"); - pw.println(" --split SPLIT: compile only the given split name"); - pw.println(" bg-dexopt-job"); - pw.println(" Execute the background optimizations immediately."); - pw.println(" Note that the command only runs the background optimizer logic. It may"); - pw.println(" overlap with the actual job but the job scheduler will not be able to"); - pw.println(" cancel it. It will also run even if the device is not in the idle"); - pw.println(" maintenance mode."); + pw.println(" path [--user USER_ID] PACKAGE"); + pw.println(" Print the path to the .apk of the given PACKAGE."); + pw.println(""); + pw.println(" dump PACKAGE"); + pw.println(" Print various system state associated with the given PACKAGE."); + pw.println(""); pw.println(" list features"); pw.println(" Prints all features of the system."); + pw.println(""); + pw.println(" has-feature FEATURE_NAME [version]"); + pw.println(" Prints true and returns exit status 0 when system has a FEATURE_NAME,"); + pw.println(" otherwise prints false and returns exit status 1"); + pw.println(""); pw.println(" list instrumentation [-f] [TARGET-PACKAGE]"); pw.println(" Prints all test packages; optionally only those targeting TARGET-PACKAGE"); pw.println(" Options:"); pw.println(" -f: dump the name of the .apk file containing the test package"); + pw.println(""); pw.println(" list libraries"); pw.println(" Prints all system libraries."); - pw.println(" list packages [-f] [-d] [-e] [-s] [-3] [-i] [-l] [-u] [-U] " - + "[--uid UID] [--user USER_ID] [FILTER]"); + pw.println(""); + pw.println(" list packages [-f] [-d] [-e] [-s] [-3] [-i] [-l] [-u] [-U] "); + pw.println(" [--uid UID] [--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(" the text in FILTER. Options are:"); pw.println(" -f: see their associated file"); pw.println(" -d: filter to only show disabled packages"); pw.println(" -e: filter to only show enabled packages"); @@ -1720,42 +2384,216 @@ class PackageManagerShellCommand extends ShellCommand { pw.println(" -u: also include uninstalled packages"); pw.println(" --uid UID: filter to only show packages with the given UID"); pw.println(" --user USER_ID: only list packages belonging to the given user"); - pw.println(" reconcile-secondary-dex-files TARGET-PACKAGE"); - pw.println(" Reconciles the package secondary dex files with the generated oat files."); + pw.println(""); pw.println(" list permission-groups"); pw.println(" Prints all known permission groups."); + pw.println(""); 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(" Prints all known permissions; optionally only those in GROUP. Options are:"); 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(" dump-profiles TARGET-PACKAGE"); - pw.println(" Dumps method/class profile files to"); - pw.println(" /data/misc/profman/TARGET-PACKAGE.txt"); + pw.println(""); pw.println(" resolve-activity [--brief] [--components] [--user USER_ID] INTENT"); - pw.println(" Prints the activity that resolves to the given Intent."); + pw.println(" Prints the activity that resolves to the given INTENT."); + pw.println(""); pw.println(" query-activities [--brief] [--components] [--user USER_ID] INTENT"); - pw.println(" Prints all activities that can handle the given Intent."); + pw.println(" Prints all activities that can handle the given INTENT."); + pw.println(""); pw.println(" query-services [--brief] [--components] [--user USER_ID] INTENT"); - pw.println(" Prints all services that can handle the given Intent."); + pw.println(" Prints all services that can handle the given INTENT."); + pw.println(""); pw.println(" query-receivers [--brief] [--components] [--user USER_ID] INTENT"); - pw.println(" Prints all broadcast receivers that can handle the given Intent."); + pw.println(" Prints all broadcast receivers that can handle the given INTENT."); + pw.println(""); + pw.println(" install [-lrtsfdg] [-i PACKAGE] [--user USER_ID|all|current]"); + pw.println(" [-p INHERIT_PACKAGE] [--install-location 0/1/2]"); + pw.println(" [--originating-uri URI] [---referrer URI]"); + pw.println(" [--abi ABI_NAME] [--force-sdk]"); + pw.println(" [--preload] [--instantapp] [--full] [--dont-kill]"); + pw.println(" [--force-uuid internal|UUID] [--pkg PACKAGE] [-S BYTES] [PATH|-]"); + pw.println(" Install an application. Must provide the apk data to install, either as a"); + pw.println(" file path or '-' to read from stdin. Options are:"); + pw.println(" -l: forward lock application"); + pw.println(" -r: allow replacement of existing application"); + pw.println(" -t: allow test packages"); + pw.println(" -i: specify package name of installer owning the app"); + pw.println(" -s: install application on sdcard"); + pw.println(" -f: install application on internal flash"); + pw.println(" -d: allow version code downgrade (debuggable packages only)"); + pw.println(" -p: partial application install (new split on top of existing pkg)"); + pw.println(" -g: grant all runtime permissions"); + pw.println(" -S: size in bytes of package, required for stdin"); + pw.println(" --user: install under the given user."); + pw.println(" --dont-kill: installing a new feature split, don't kill running app"); + pw.println(" --originating-uri: set URI where app was downloaded from"); + pw.println(" --referrer: set URI that instigated the install of the app"); + pw.println(" --pkg: specify expected package name of app being installed"); + pw.println(" --abi: override the default ABI of the platform"); + pw.println(" --instantapp: cause the app to be installed as an ephemeral install app"); + pw.println(" --full: cause the app to be installed as a non-ephemeral full app"); + pw.println(" --install-location: force the install location:"); + pw.println(" 0=auto, 1=internal only, 2=prefer external"); + pw.println(" --force-uuid: force install on to disk volume with given UUID"); + pw.println(" --force-sdk: allow install even when existing app targets platform"); + pw.println(" codename but new one targets a final API level"); + pw.println(""); + pw.println(" install-create [-lrtsfdg] [-i PACKAGE] [--user USER_ID|all|current]"); + pw.println(" [-p INHERIT_PACKAGE] [--install-location 0/1/2]"); + pw.println(" [--originating-uri URI] [---referrer URI]"); + pw.println(" [--abi ABI_NAME] [--force-sdk]"); + pw.println(" [--preload] [--instantapp] [--full] [--dont-kill]"); + pw.println(" [--force-uuid internal|UUID] [--pkg PACKAGE] [-S BYTES]"); + pw.println(" Like \"install\", but starts an install session. Use \"install-write\""); + pw.println(" to push data into the session, and \"install-commit\" to finish."); + pw.println(""); + pw.println(" install-write [-S BYTES] SESSION_ID SPLIT_NAME [PATH|-]"); + pw.println(" Write an apk into the given install session. If the path is '-', data"); + pw.println(" will be read from stdin. Options are:"); + pw.println(" -S: size in bytes of package, required for stdin"); + pw.println(""); + pw.println(" install-commit SESSION_ID"); + pw.println(" Commit the given active install session, installing the app."); + pw.println(""); + pw.println(" install-abandon SESSION_ID"); + pw.println(" Delete the given active install session."); + pw.println(""); + pw.println(" set-install-location LOCATION"); + pw.println(" Changes the default install location. NOTE this is only intended for debugging;"); + pw.println(" using this can cause applications to break and other undersireable behavior."); + pw.println(" LOCATION is one of:"); + pw.println(" 0 [auto]: Let system decide the best location"); + pw.println(" 1 [internal]: Install on internal device storage"); + pw.println(" 2 [external]: Install on external media"); + pw.println(""); + pw.println(" get-install-location"); + pw.println(" Returns the current install location: 0, 1 or 2 as per set-install-location."); + pw.println(""); + pw.println(" move-package PACKAGE [internal|UUID]"); + pw.println(""); + pw.println(" move-primary-storage [internal|UUID]"); + pw.println(""); + pw.println(" pm uninstall [-k] [--user USER_ID] [--versionCode VERSION_CODE] PACKAGE [SPLIT]"); + pw.println(" Remove the given package name from the system. May remove an entire app"); + pw.println(" if no SPLIT name is specified, otherwise will remove only the split of the"); + pw.println(" given app. Options are:"); + pw.println(" -k: keep the data and cache directories around after package removal."); + pw.println(" --user: remove the app from the given user."); + pw.println(" --versionCode: only uninstall if the app has the given version code."); + pw.println(""); + pw.println(" clear [--user USER_ID] PACKAGE"); + pw.println(" Deletes all data associated with a package."); + pw.println(""); + pw.println(" enable [--user USER_ID] PACKAGE_OR_COMPONENT"); + pw.println(" disable [--user USER_ID] PACKAGE_OR_COMPONENT"); + pw.println(" disable-user [--user USER_ID] PACKAGE_OR_COMPONENT"); + pw.println(" disable-until-used [--user USER_ID] PACKAGE_OR_COMPONENT"); + pw.println(" default-state [--user USER_ID] PACKAGE_OR_COMPONENT"); + pw.println(" These commands change the enabled state of a given package or"); + pw.println(" component (written as \"package/class\")."); + pw.println(""); + pw.println(" hide [--user USER_ID] PACKAGE_OR_COMPONENT"); + pw.println(" unhide [--user USER_ID] PACKAGE_OR_COMPONENT"); + pw.println(""); pw.println(" suspend [--user USER_ID] TARGET-PACKAGE"); pw.println(" Suspends the specified package (as user)."); + pw.println(""); pw.println(" unsuspend [--user USER_ID] TARGET-PACKAGE"); pw.println(" Unsuspends the specified package (as user)."); - pw.println(" set-home-activity [--user USER_ID] TARGET-COMPONENT"); - pw.println(" set the default home activity (aka launcher)."); - pw.println(" has-feature FEATURE_NAME [version]"); - pw.println(" prints true and returns exit status 0 when system has a FEATURE_NAME,"); - pw.println(" otherwise prints false and returns exit status 1"); - pw.println(" get-privileged-permissions TARGET-PACKAGE"); - pw.println(" prints all privileged permissions for a package."); + pw.println(""); + pw.println(" grant [--user USER_ID] PACKAGE PERMISSION"); + pw.println(" revoke [--user USER_ID] PACKAGE PERMISSION"); + pw.println(" These commands either grant or revoke permissions to apps. The permissions"); + pw.println(" must be declared as used in the app's manifest, be runtime permissions"); + pw.println(" (protection level dangerous), and the app targeting SDK greater than Lollipop MR1."); + pw.println(""); + pw.println(" reset-permissions"); + pw.println(" Revert all runtime permissions to their default state."); + pw.println(""); + pw.println(" set-permission-enforced PERMISSION [true|false]"); + pw.println(""); + pw.println(" get-privapp-permissions TARGET-PACKAGE"); + pw.println(" Prints all privileged permissions for a package."); + pw.println(""); + pw.println(" get-privapp-deny-permissions TARGET-PACKAGE"); + pw.println(" Prints all privileged permissions that are denied for a package."); + pw.println(""); pw.println(" get-oem-permissions TARGET-PACKAGE"); - pw.println(" prints all OEM permissions for a package."); + pw.println(" Prints all OEM permissions for a package."); + pw.println(""); + pw.println(" set-app-link [--user USER_ID] PACKAGE {always|ask|never|undefined}"); + pw.println(" get-app-link [--user USER_ID] PACKAGE"); + pw.println(""); + pw.println(" trim-caches DESIRED_FREE_SPACE [internal|UUID]"); + pw.println(" Trim cache files to reach the given free space."); + pw.println(""); + pw.println(" create-user [--profileOf USER_ID] [--managed] [--restricted] [--ephemeral]"); + pw.println(" [--guest] USER_NAME"); + pw.println(" Create a new user with the given USER_NAME, printing the new user identifier"); + pw.println(" of the user."); + pw.println(""); + pw.println(" remove-user USER_ID"); + pw.println(" Remove the user with the given USER_IDENTIFIER, deleting all data"); + pw.println(" associated with that user"); + pw.println(""); + pw.println(" set-user-restriction [--user USER_ID] RESTRICTION VALUE"); + pw.println(""); + pw.println(" get-max-users"); + pw.println(""); + pw.println(" compile [-m MODE | -r REASON] [-f] [-c] [--split SPLIT_NAME]"); + pw.println(" [--reset] [--check-prof (true | false)] (-a | TARGET-PACKAGE)"); + pw.println(" Trigger compilation of TARGET-PACKAGE or all packages if \"-a\". Options are:"); + pw.println(" -a: compile all packages"); + pw.println(" -c: clear profile data before compiling"); + pw.println(" -f: force compilation even if not needed"); + pw.println(" -m: select compilation mode"); + pw.println(" MODE is one of the dex2oat compiler filters:"); + pw.println(" assume-verified"); + pw.println(" extract"); + pw.println(" verify"); + pw.println(" quicken"); + pw.println(" space-profile"); + pw.println(" space"); + pw.println(" speed-profile"); + pw.println(" speed"); + pw.println(" everything"); + pw.println(" -r: select compilation reason"); + pw.println(" REASON is one of:"); + for (int i = 0; i < PackageManagerServiceCompilerMapping.REASON_STRINGS.length; i++) { + pw.println(" " + PackageManagerServiceCompilerMapping.REASON_STRINGS[i]); + } + pw.println(" --reset: restore package to its post-install state"); + pw.println(" --check-prof (true | false): look at profiles when doing dexopt?"); + pw.println(" --secondary-dex: compile app secondary dex files"); + pw.println(" --split SPLIT: compile only the given split name"); + pw.println(""); + pw.println(" force-dex-opt PACKAGE"); + pw.println(" Force immediate execution of dex opt for the given PACKAGE."); + pw.println(""); + pw.println(" bg-dexopt-job"); + pw.println(" Execute the background optimizations immediately."); + pw.println(" Note that the command only runs the background optimizer logic. It may"); + pw.println(" overlap with the actual job but the job scheduler will not be able to"); + pw.println(" cancel it. It will also run even if the device is not in the idle"); + pw.println(" maintenance mode."); + pw.println(""); + pw.println(" reconcile-secondary-dex-files TARGET-PACKAGE"); + pw.println(" Reconciles the package secondary dex files with the generated oat files."); + pw.println(""); + pw.println(" dump-profiles TARGET-PACKAGE"); + pw.println(" Dumps method/class profile files to"); + pw.println(" /data/misc/profman/TARGET-PACKAGE.txt"); + pw.println(""); + pw.println(" set-home-activity [--user USER_ID] TARGET-COMPONENT"); + pw.println(" Set the default home activity (aka launcher)."); + pw.println(""); + pw.println(" set-installer PACKAGE INSTALLER"); + pw.println(" Set installer package name"); + pw.println(""); + pw.println(" get-instantapp-resolver"); + pw.println(" Return the name of the component that is the current instant app installer."); pw.println(); Intent.printIntentArgsHelp(pw , ""); } diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java index 83cb2db2e0d7..3b414e9a0dc5 100644 --- a/services/core/java/com/android/server/pm/PackageSetting.java +++ b/services/core/java/com/android/server/pm/PackageSetting.java @@ -86,6 +86,10 @@ public final class PackageSetting extends PackageSettingBase { return sharedUserId; } + public SharedUserSetting getSharedUser() { + return sharedUser; + } + @Override public String toString() { return "PackageSetting{" @@ -120,6 +124,14 @@ public final class PackageSetting extends PackageSettingBase { return appId; } + public void setInstallPermissionsFixed(boolean fixed) { + installPermissionsFixed = fixed; + } + + public boolean areInstallPermissionsFixed() { + return installPermissionsFixed; + } + public boolean isPrivileged() { return (pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0; } @@ -136,6 +148,10 @@ public final class PackageSetting extends PackageSettingBase { return (pkgFlags & ApplicationInfo.FLAG_SYSTEM) != 0; } + public boolean isUpdatedSystem() { + return (pkgFlags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0; + } + @Override public boolean isSharedUser() { return sharedUser != null; diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java index e19e83fcee83..a83876848910 100644 --- a/services/core/java/com/android/server/pm/PackageSettingBase.java +++ b/services/core/java/com/android/server/pm/PackageSettingBase.java @@ -24,6 +24,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.IntentFilterVerificationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageUserState; +import android.content.pm.Signature; import android.service.pm.PackageProto; import android.util.ArraySet; import android.util.SparseArray; @@ -57,7 +58,7 @@ public abstract class PackageSettingBase extends SettingBase { static final int PKG_INSTALL_COMPLETE = 1; static final int PKG_INSTALL_INCOMPLETE = 0; - final String name; + public final String name; final String realName; String parentPackageName; @@ -231,6 +232,11 @@ public abstract class PackageSettingBase extends SettingBase { public boolean isSharedUser() { return false; } + + public Signature[] getSignatures() { + return signatures.mSignatures; + } + /** * Makes a shallow copy of the given package settings. * diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 191b43a66d51..7077fd166987 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -544,7 +544,7 @@ public final class Settings { } final PackageSetting dp = mDisabledSysPackages.get(name); // always make sure the system package code and resource paths dont change - if (dp == null && p.pkg != null && p.pkg.isSystemApp() && !p.pkg.isUpdatedSystemApp()) { + if (dp == null && p.pkg != null && p.pkg.isSystem() && !p.pkg.isUpdatedSystemApp()) { if((p.pkg != null) && (p.pkg.applicationInfo != null)) { p.pkg.applicationInfo.flags |= ApplicationInfo.FLAG_UPDATED_SYSTEM_APP; } diff --git a/services/core/java/com/android/server/pm/UserDataPreparer.java b/services/core/java/com/android/server/pm/UserDataPreparer.java index b8b00af448eb..bfe09b8ecbce 100644 --- a/services/core/java/com/android/server/pm/UserDataPreparer.java +++ b/services/core/java/com/android/server/pm/UserDataPreparer.java @@ -132,11 +132,9 @@ class UserDataPreparer { if ((flags & StorageManager.FLAG_STORAGE_DE) != 0) { FileUtils.deleteContentsAndDir(getUserSystemDirectory(userId)); FileUtils.deleteContentsAndDir(getDataSystemDeDirectory(userId)); - FileUtils.deleteContentsAndDir(getDataMiscDeDirectory(userId)); } if ((flags & StorageManager.FLAG_STORAGE_CE) != 0) { FileUtils.deleteContentsAndDir(getDataSystemCeDirectory(userId)); - FileUtils.deleteContentsAndDir(getDataMiscCeDirectory(userId)); } } diff --git a/services/core/java/com/android/server/pm/permission/BasePermission.java b/services/core/java/com/android/server/pm/permission/BasePermission.java index 71d3202db853..8c86db64471d 100644 --- a/services/core/java/com/android/server/pm/permission/BasePermission.java +++ b/services/core/java/com/android/server/pm/permission/BasePermission.java @@ -32,6 +32,7 @@ import android.annotation.Nullable; import android.content.pm.PackageParser; import android.content.pm.PackageParser.Permission; import android.content.pm.PermissionInfo; +import android.content.pm.Signature; import android.os.UserHandle; import android.util.Log; import android.util.Slog; @@ -129,6 +130,9 @@ public final class BasePermission { public PackageSettingBase getSourcePackageSetting() { return sourcePackageSetting; } + public Signature[] getSourceSignatures() { + return sourcePackageSetting.getSignatures(); + } public int getType() { return type; } @@ -277,8 +281,8 @@ public final class BasePermission { // Allow system apps to redefine non-system permissions if (bp != null && !Objects.equals(bp.sourcePackageName, p.info.packageName)) { final boolean currentOwnerIsSystem = (bp.perm != null - && bp.perm.owner.isSystemApp()); - if (p.owner.isSystemApp()) { + && bp.perm.owner.isSystem()); + if (p.owner.isSystem()) { if (bp.type == BasePermission.TYPE_BUILTIN && bp.perm == null) { // It's a built-in permission and no owner, take ownership now bp.sourcePackageSetting = pkgSetting; diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java index 161efd38c0f9..533b261998ae 100644 --- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java +++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java @@ -985,7 +985,7 @@ public final class DefaultPermissionGrantPolicy { private PackageParser.Package getSystemPackage(String packageName) { PackageParser.Package pkg = getPackage(packageName); - if (pkg != null && pkg.isSystemApp()) { + if (pkg != null && pkg.isSystem()) { return !isSysComponentOrPersistentPlatformSignedPrivApp(pkg) ? pkg : null; } return null; @@ -1094,7 +1094,7 @@ public final class DefaultPermissionGrantPolicy { if (UserHandle.getAppId(pkg.applicationInfo.uid) < FIRST_APPLICATION_UID) { return true; } - if (!pkg.isPrivilegedApp()) { + if (!pkg.isPrivileged()) { return false; } final PackageParser.Package disabledPkg = diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerInternal.java b/services/core/java/com/android/server/pm/permission/PermissionManagerInternal.java index 8aac52ae0df7..60c118b58409 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerInternal.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerInternal.java @@ -19,6 +19,7 @@ package com.android.server.pm.permission; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.pm.PackageParser; +import android.content.pm.PermissionGroupInfo; import android.content.pm.PermissionInfo; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageManager.PermissionInfoFlags; @@ -57,7 +58,7 @@ public abstract class PermissionManagerInternal { } public void onInstallPermissionRevoked() { } - public void onPermissionUpdated(int userId) { + public void onPermissionUpdated(int[] updatedUserIds, boolean sync) { } public void onPermissionRemoved() { } @@ -65,6 +66,10 @@ public abstract class PermissionManagerInternal { } } + public abstract void systemReady(); + + public abstract boolean isPermissionsReviewRequired(PackageParser.Package pkg, int userId); + public abstract void grantRuntimePermission( @NonNull String permName, @NonNull String packageName, boolean overridePolicy, int callingUid, int userId, @Nullable PermissionCallback callback); @@ -78,9 +83,12 @@ public abstract class PermissionManagerInternal { public abstract void revokeRuntimePermission(@NonNull String permName, @NonNull String packageName, boolean overridePolicy, int callingUid, int userId, @Nullable PermissionCallback callback); - public abstract int[] revokeUnusedSharedUserPermissions(@NonNull SharedUserSetting suSetting, - @NonNull int[] allUserIds); + public abstract void updatePermissions(@Nullable String packageName, + @Nullable PackageParser.Package pkg, boolean replaceGrant, + @NonNull Collection<PackageParser.Package> allPacakges, PermissionCallback callback); + public abstract void updateAllPermissions(@Nullable String volumeUuid, boolean sdkUpdated, + @NonNull Collection<PackageParser.Package> allPacakges, PermissionCallback callback); /** * Add all permissions in the given package. @@ -89,22 +97,28 @@ public abstract class PermissionManagerInternal { * the permission settings. */ public abstract void addAllPermissions(@NonNull PackageParser.Package pkg, boolean chatty); + public abstract void addAllPermissionGroups(@NonNull PackageParser.Package pkg, boolean chatty); public abstract void removeAllPermissions(@NonNull PackageParser.Package pkg, boolean chatty); public abstract boolean addDynamicPermission(@NonNull PermissionInfo info, boolean async, int callingUid, @Nullable PermissionCallback callback); public abstract void removeDynamicPermission(@NonNull String permName, int callingUid, @Nullable PermissionCallback callback); - public abstract int updatePermissions(@Nullable String changingPkg, - @Nullable PackageParser.Package pkgInfo, int flags); - public abstract int updatePermissionTrees(@Nullable String changingPkg, - @Nullable PackageParser.Package pkgInfo, int flags); - public abstract @Nullable String[] getAppOpPermissionPackages(@NonNull String permName); public abstract int getPermissionFlags(@NonNull String permName, @NonNull String packageName, int callingUid, int userId); /** + * Retrieve all of the information we know about a particular group of permissions. + */ + public abstract @Nullable PermissionGroupInfo getPermissionGroupInfo( + @NonNull String groupName, int flags, int callingUid); + /** + * Retrieve all of the known permission groups in the system. + */ + public abstract @Nullable List<PermissionGroupInfo> getAllPermissionGroups(int flags, + int callingUid); + /** * Retrieve all of the information we know about a particular permission. */ public abstract @Nullable PermissionInfo getPermissionInfo(@NonNull String permName, @@ -132,6 +146,7 @@ public abstract class PermissionManagerInternal { public abstract int checkPermission(@NonNull String permName, @NonNull String packageName, int callingUid, int userId); + public abstract int checkUidPermission(String permName, int uid, int callingUid); /** * Enforces the request is from the system or an app that has INTERACT_ACROSS_USERS @@ -147,8 +162,5 @@ public abstract class PermissionManagerInternal { public abstract @NonNull DefaultPermissionGrantPolicy getDefaultPermissionGrantPolicy(); /** HACK HACK methods to allow for partial migration of data to the PermissionManager class */ - public abstract Iterator<BasePermission> getPermissionIteratorTEMP(); public abstract @Nullable BasePermission getPermissionTEMP(@NonNull String permName); - public abstract void putPermissionTEMP(@NonNull String permName, - @NonNull BasePermission permission); }
\ No newline at end of file diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index d2d857caa240..9e4940908bda 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -18,6 +18,13 @@ package com.android.server.pm.permission; import static android.Manifest.permission.READ_EXTERNAL_STORAGE; import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE; +import static android.content.pm.PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED; +import static com.android.server.pm.PackageManagerService.DEBUG_INSTALL; +import static com.android.server.pm.PackageManagerService.DEBUG_PACKAGE_SCANNING; +import static com.android.server.pm.PackageManagerService.DEBUG_PERMISSIONS; +import static com.android.server.pm.PackageManagerService.DEBUG_REMOVE; +import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; +import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; import android.Manifest; import android.annotation.NonNull; @@ -27,7 +34,7 @@ import android.content.Context; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.PackageParser; -import android.content.pm.ParceledListSlice; +import android.content.pm.PermissionGroupInfo; import android.content.pm.PermissionInfo; import android.content.pm.PackageParser.Package; import android.os.Binder; @@ -35,18 +42,24 @@ import android.os.Build; import android.os.Handler; import android.os.HandlerThread; import android.os.Process; +import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; import android.os.UserManagerInternal; +import android.os.storage.StorageManager; import android.os.storage.StorageManagerInternal; +import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; import android.util.Slog; +import android.util.SparseArray; import com.android.internal.R; +import com.android.internal.annotations.GuardedBy; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.internal.os.RoSystemProperties; import com.android.internal.util.ArrayUtils; import com.android.server.FgThread; import com.android.server.LocalServices; @@ -58,6 +71,7 @@ import com.android.server.pm.PackageManagerServiceUtils; import com.android.server.pm.PackageSetting; import com.android.server.pm.ProcessLoggingHandler; import com.android.server.pm.SharedUserSetting; +import com.android.server.pm.UserManagerService; import com.android.server.pm.permission.DefaultPermissionGrantPolicy.DefaultPermissionGrantedCallback; import com.android.server.pm.permission.PermissionManagerInternal.PermissionCallback; import com.android.server.pm.permission.PermissionsState.PermissionState; @@ -69,6 +83,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.List; +import java.util.Objects; import java.util.Set; /** @@ -107,8 +122,19 @@ public class PermissionManagerService { Manifest.permission.READ_PHONE_NUMBERS, Manifest.permission.ANSWER_PHONE_CALLS); - /** Cap the size of permission trees that 3rd party apps can define */ - private static final int MAX_PERMISSION_TREE_FOOTPRINT = 32768; // characters of text + /** Permission grant: not grant the permission. */ + private static final int GRANT_DENIED = 1; + /** Permission grant: grant the permission as an install permission. */ + private static final int GRANT_INSTALL = 2; + /** Permission grant: grant the permission as a runtime one. */ + private static final int GRANT_RUNTIME = 3; + /** Permission grant: grant as runtime a permission that was granted as an install time one. */ + private static final int GRANT_UPGRADE = 4; + + /** Cap the size of permission trees that 3rd party apps can define; in characters of text */ + private static final int MAX_PERMISSION_TREE_FOOTPRINT = 32768; + /** Empty array to avoid allocations */ + private static final int[] EMPTY_INT_ARRAY = new int[0]; /** Lock to protect internal data access */ private final Object mLock; @@ -122,13 +148,29 @@ public class PermissionManagerService { /** Default permission policy to provide proper behaviour out-of-the-box */ private final DefaultPermissionGrantPolicy mDefaultPermissionGrantPolicy; - /** Internal storage for permissions and related settings */ - private final PermissionSettings mSettings; + /** + * Built-in permissions. Read from system configuration files. Mapping is from + * UID to permission name. + */ + private final SparseArray<ArraySet<String>> mSystemPermissions; + + /** Built-in group IDs given to all packages. Read from system configuration files. */ + private final int[] mGlobalGids; private final HandlerThread mHandlerThread; private final Handler mHandler; private final Context mContext; + /** Internal storage for permissions and related settings */ + @GuardedBy("mLock") + private final PermissionSettings mSettings; + + @GuardedBy("mLock") + private ArraySet<String> mPrivappPermissionsViolations; + + @GuardedBy("mLock") + private boolean mSystemReady; + PermissionManagerService(Context context, @Nullable DefaultPermissionGrantedCallback defaultGrantCallback, @NonNull Object externalLock) { @@ -146,6 +188,9 @@ public class PermissionManagerService { mDefaultPermissionGrantPolicy = new DefaultPermissionGrantPolicy( context, mHandlerThread.getLooper(), defaultGrantCallback, this); + SystemConfig systemConfig = SystemConfig.getInstance(); + mSystemPermissions = systemConfig.getSystemPermissions(); + mGlobalGids = systemConfig.getGlobalGids(); // propagate permission configuration final ArrayMap<String, SystemConfig.PermissionEntry> permConfig = @@ -230,14 +275,108 @@ public class PermissionManagerService { return PackageManager.PERMISSION_DENIED; } - private PermissionInfo getPermissionInfo(String name, String packageName, int flags, + private int checkUidPermission(String permName, int uid, int callingUid) { + final int callingUserId = UserHandle.getUserId(callingUid); + final boolean isCallerInstantApp = + mPackageManagerInt.getInstantAppPackageName(callingUid) != null; + final boolean isUidInstantApp = + mPackageManagerInt.getInstantAppPackageName(uid) != null; + final int userId = UserHandle.getUserId(uid); + if (!mUserManagerInt.exists(userId)) { + return PackageManager.PERMISSION_DENIED; + } + + final String[] packages = mContext.getPackageManager().getPackagesForUid(uid); + if (packages != null && packages.length > 0) { +Slog.e(TAG, "TODD: Packages: " + Arrays.toString(packages)); + PackageParser.Package pkg = null; + for (String packageName : packages) { + pkg = mPackageManagerInt.getPackage(packageName); + if (pkg != null) { +Slog.e(TAG, "TODD: Using packge: " + packageName); + break; + } else { +Slog.e(TAG, "TODD: Missing packge: " + packageName); + } + } + if (pkg == null) { +Slog.e(TAG, "TODD: No package not found; UID: " + uid); + return PackageManager.PERMISSION_DENIED; + } + if (pkg.mSharedUserId != null) { + if (isCallerInstantApp) { + return PackageManager.PERMISSION_DENIED; + } + } else { + if (mPackageManagerInt.filterAppAccess(pkg, callingUid, callingUserId)) { + return PackageManager.PERMISSION_DENIED; + } + } + final PermissionsState permissionsState = + ((PackageSetting) pkg.mExtras).getPermissionsState(); + if (permissionsState.hasPermission(permName, userId)) { + if (isUidInstantApp) { + if (mSettings.isPermissionInstant(permName)) { + return PackageManager.PERMISSION_GRANTED; + } + } else { + return PackageManager.PERMISSION_GRANTED; + } + } + // Special case: ACCESS_FINE_LOCATION permission includes ACCESS_COARSE_LOCATION + if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName) && permissionsState + .hasPermission(Manifest.permission.ACCESS_FINE_LOCATION, userId)) { + return PackageManager.PERMISSION_GRANTED; + } + } else { + ArraySet<String> perms = mSystemPermissions.get(uid); + if (perms != null) { + if (perms.contains(permName)) { + return PackageManager.PERMISSION_GRANTED; + } + if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName) && perms + .contains(Manifest.permission.ACCESS_FINE_LOCATION)) { + return PackageManager.PERMISSION_GRANTED; + } + } + } + return PackageManager.PERMISSION_DENIED; + } + + private PermissionGroupInfo getPermissionGroupInfo(String groupName, int flags, + int callingUid) { + if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) { + return null; + } + synchronized (mLock) { + return PackageParser.generatePermissionGroupInfo( + mSettings.mPermissionGroups.get(groupName), flags); + } + } + + private List<PermissionGroupInfo> getAllPermissionGroups(int flags, int callingUid) { + if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) { + return null; + } + synchronized (mLock) { + final int N = mSettings.mPermissionGroups.size(); + final ArrayList<PermissionGroupInfo> out + = new ArrayList<PermissionGroupInfo>(N); + for (PackageParser.PermissionGroup pg : mSettings.mPermissionGroups.values()) { + out.add(PackageParser.generatePermissionGroupInfo(pg, flags)); + } + return out; + } + } + + private PermissionInfo getPermissionInfo(String permName, String packageName, int flags, int callingUid) { if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) { return null; } // reader synchronized (mLock) { - final BasePermission bp = mSettings.getPermissionLocked(name); + final BasePermission bp = mSettings.getPermissionLocked(permName); if (bp == null) { return null; } @@ -252,14 +391,10 @@ public class PermissionManagerService { if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) { return null; } - // reader synchronized (mLock) { - // TODO Uncomment when mPermissionGroups moves to this class -// if (groupName != null && !mPermissionGroups.containsKey(groupName)) { -// // This is thrown as NameNotFoundException -// return null; -// } - + if (groupName != null && !mSettings.mPermissionGroups.containsKey(groupName)) { + return null; + } final ArrayList<PermissionInfo> out = new ArrayList<PermissionInfo>(10); for (BasePermission bp : mSettings.mPermissions.values()) { final PermissionInfo pi = bp.generatePermissionInfo(groupName, flags); @@ -314,21 +449,21 @@ public class PermissionManagerService { // Assume by default that we did not install this permission into the system. p.info.flags &= ~PermissionInfo.FLAG_INSTALLED; - // Now that permission groups have a special meaning, we ignore permission - // groups for legacy apps to prevent unexpected behavior. In particular, - // permissions for one app being granted to someone just because they happen - // to be in a group defined by another app (before this had no implications). - if (pkg.applicationInfo.targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) { - p.group = mPackageManagerInt.getPermissionGroupTEMP(p.info.group); - // Warn for a permission in an unknown group. - if (PackageManagerService.DEBUG_PERMISSIONS - && p.info.group != null && p.group == null) { - Slog.i(TAG, "Permission " + p.info.name + " from package " - + p.info.packageName + " in an unknown group " + p.info.group); + synchronized (PermissionManagerService.this.mLock) { + // Now that permission groups have a special meaning, we ignore permission + // groups for legacy apps to prevent unexpected behavior. In particular, + // permissions for one app being granted to someone just because they happen + // to be in a group defined by another app (before this had no implications). + if (pkg.applicationInfo.targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) { + p.group = mSettings.mPermissionGroups.get(p.info.group); + // Warn for a permission in an unknown group. + if (DEBUG_PERMISSIONS + && p.info.group != null && p.group == null) { + Slog.i(TAG, "Permission " + p.info.name + " from package " + + p.info.packageName + " in an unknown group " + p.info.group); + } } - } - synchronized (PermissionManagerService.this.mLock) { if (p.tree) { final BasePermission bp = BasePermission.createOrUpdate( mSettings.getPermissionTreeLocked(p.info.name), p, pkg, @@ -344,6 +479,48 @@ public class PermissionManagerService { } } + private void addAllPermissionGroups(PackageParser.Package pkg, boolean chatty) { + final int N = pkg.permissionGroups.size(); + StringBuilder r = null; + for (int i=0; i<N; i++) { + final PackageParser.PermissionGroup pg = pkg.permissionGroups.get(i); + final PackageParser.PermissionGroup cur = mSettings.mPermissionGroups.get(pg.info.name); + final String curPackageName = (cur == null) ? null : cur.info.packageName; + final boolean isPackageUpdate = pg.info.packageName.equals(curPackageName); + if (cur == null || isPackageUpdate) { + mSettings.mPermissionGroups.put(pg.info.name, pg); + if (chatty && DEBUG_PACKAGE_SCANNING) { + if (r == null) { + r = new StringBuilder(256); + } else { + r.append(' '); + } + if (isPackageUpdate) { + r.append("UPD:"); + } + r.append(pg.info.name); + } + } else { + Slog.w(TAG, "Permission group " + pg.info.name + " from package " + + pg.info.packageName + " ignored: original from " + + cur.info.packageName); + if (chatty && DEBUG_PACKAGE_SCANNING) { + if (r == null) { + r = new StringBuilder(256); + } else { + r.append(' '); + } + r.append("DUP:"); + r.append(pg.info.name); + } + } + } + if (r != null && DEBUG_PACKAGE_SCANNING) { + Log.d(TAG, " Permission Groups: " + r); + } + + } + private void removeAllPermissions(PackageParser.Package pkg, boolean chatty) { synchronized (mLock) { int N = pkg.permissions.size(); @@ -356,7 +533,7 @@ public class PermissionManagerService { } if (bp != null && bp.isPermission(p)) { bp.setPermission(null); - if (PackageManagerService.DEBUG_REMOVE && chatty) { + if (DEBUG_REMOVE && chatty) { if (r == null) { r = new StringBuilder(256); } else { @@ -374,7 +551,7 @@ public class PermissionManagerService { } } if (r != null) { - if (PackageManagerService.DEBUG_REMOVE) Log.d(TAG, " Permissions: " + r); + if (DEBUG_REMOVE) Log.d(TAG, " Permissions: " + r); } N = pkg.requestedPermissions.size(); @@ -392,7 +569,7 @@ public class PermissionManagerService { } } if (r != null) { - if (PackageManagerService.DEBUG_REMOVE) Log.d(TAG, " Permissions: " + r); + if (DEBUG_REMOVE) Log.d(TAG, " Permissions: " + r); } } } @@ -455,6 +632,579 @@ public class PermissionManagerService { } } + private void grantPermissions(PackageParser.Package pkg, boolean replace, + String packageOfInterest, PermissionCallback callback) { + // IMPORTANT: There are two types of permissions: install and runtime. + // Install time permissions are granted when the app is installed to + // all device users and users added in the future. Runtime permissions + // are granted at runtime explicitly to specific users. Normal and signature + // protected permissions are install time permissions. Dangerous permissions + // are install permissions if the app's target SDK is Lollipop MR1 or older, + // otherwise they are runtime permissions. This function does not manage + // runtime permissions except for the case an app targeting Lollipop MR1 + // being upgraded to target a newer SDK, in which case dangerous permissions + // are transformed from install time to runtime ones. + + final PackageSetting ps = (PackageSetting) pkg.mExtras; + if (ps == null) { + return; + } + final boolean isLegacySystemApp = mPackageManagerInt.isLegacySystemApp(pkg); + + final PermissionsState permissionsState = ps.getPermissionsState(); + PermissionsState origPermissions = permissionsState; + + final int[] currentUserIds = UserManagerService.getInstance().getUserIds(); + + boolean runtimePermissionsRevoked = false; + int[] updatedUserIds = EMPTY_INT_ARRAY; + + boolean changedInstallPermission = false; + + if (replace) { + ps.setInstallPermissionsFixed(false); + if (!ps.isSharedUser()) { + origPermissions = new PermissionsState(permissionsState); + permissionsState.reset(); + } else { + // We need to know only about runtime permission changes since the + // calling code always writes the install permissions state but + // the runtime ones are written only if changed. The only cases of + // changed runtime permissions here are promotion of an install to + // runtime and revocation of a runtime from a shared user. + synchronized (mLock) { + updatedUserIds = revokeUnusedSharedUserPermissionsLocked( + ps.getSharedUser(), UserManagerService.getInstance().getUserIds()); + if (!ArrayUtils.isEmpty(updatedUserIds)) { + runtimePermissionsRevoked = true; + } + } + } + } + + permissionsState.setGlobalGids(mGlobalGids); + + synchronized (mLock) { + final int N = pkg.requestedPermissions.size(); + for (int i = 0; i < N; i++) { + final String permName = pkg.requestedPermissions.get(i); + final BasePermission bp = mSettings.getPermissionLocked(permName); + final boolean appSupportsRuntimePermissions = + pkg.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.M; + + if (DEBUG_INSTALL) { + Log.i(TAG, "Package " + pkg.packageName + " checking " + permName + ": " + bp); + } + + if (bp == null || bp.getSourcePackageSetting() == null) { + if (packageOfInterest == null || packageOfInterest.equals(pkg.packageName)) { + if (DEBUG_PERMISSIONS) { + Slog.i(TAG, "Unknown permission " + permName + + " in package " + pkg.packageName); + } + } + continue; + } + + // Limit ephemeral apps to ephemeral allowed permissions. + if (pkg.applicationInfo.isInstantApp() && !bp.isInstant()) { + if (DEBUG_PERMISSIONS) { + Log.i(TAG, "Denying non-ephemeral permission " + bp.getName() + + " for package " + pkg.packageName); + } + continue; + } + + if (bp.isRuntimeOnly() && !appSupportsRuntimePermissions) { + if (DEBUG_PERMISSIONS) { + Log.i(TAG, "Denying runtime-only permission " + bp.getName() + + " for package " + pkg.packageName); + } + continue; + } + + final String perm = bp.getName(); + boolean allowedSig = false; + int grant = GRANT_DENIED; + + // Keep track of app op permissions. + if (bp.isAppOp()) { + mSettings.addAppOpPackage(perm, pkg.packageName); + } + + if (bp.isNormal()) { + // For all apps normal permissions are install time ones. + grant = GRANT_INSTALL; + } else if (bp.isRuntime()) { + // If a permission review is required for legacy apps we represent + // their permissions as always granted runtime ones since we need + // to keep the review required permission flag per user while an + // install permission's state is shared across all users. + if (!appSupportsRuntimePermissions && !mSettings.mPermissionReviewRequired) { + // For legacy apps dangerous permissions are install time ones. + grant = GRANT_INSTALL; + } else if (origPermissions.hasInstallPermission(bp.getName())) { + // For legacy apps that became modern, install becomes runtime. + grant = GRANT_UPGRADE; + } else if (isLegacySystemApp) { + // For legacy system apps, install becomes runtime. + // We cannot check hasInstallPermission() for system apps since those + // permissions were granted implicitly and not persisted pre-M. + grant = GRANT_UPGRADE; + } else { + // For modern apps keep runtime permissions unchanged. + grant = GRANT_RUNTIME; + } + } else if (bp.isSignature()) { + // For all apps signature permissions are install time ones. + allowedSig = grantSignaturePermission(perm, pkg, bp, origPermissions); + if (allowedSig) { + grant = GRANT_INSTALL; + } + } + + if (DEBUG_PERMISSIONS) { + Slog.i(TAG, "Granting permission " + perm + " to package " + pkg.packageName); + } + + if (grant != GRANT_DENIED) { + if (!ps.isSystem() && ps.areInstallPermissionsFixed()) { + // If this is an existing, non-system package, then + // we can't add any new permissions to it. + if (!allowedSig && !origPermissions.hasInstallPermission(perm)) { + // Except... if this is a permission that was added + // to the platform (note: need to only do this when + // updating the platform). + if (!isNewPlatformPermissionForPackage(perm, pkg)) { + grant = GRANT_DENIED; + } + } + } + + switch (grant) { + case GRANT_INSTALL: { + // Revoke this as runtime permission to handle the case of + // a runtime permission being downgraded to an install one. + // Also in permission review mode we keep dangerous permissions + // for legacy apps + for (int userId : UserManagerService.getInstance().getUserIds()) { + if (origPermissions.getRuntimePermissionState( + perm, userId) != null) { + // Revoke the runtime permission and clear the flags. + origPermissions.revokeRuntimePermission(bp, userId); + origPermissions.updatePermissionFlags(bp, userId, + PackageManager.MASK_PERMISSION_FLAGS, 0); + // If we revoked a permission permission, we have to write. + updatedUserIds = ArrayUtils.appendInt( + updatedUserIds, userId); + } + } + // Grant an install permission. + if (permissionsState.grantInstallPermission(bp) != + PermissionsState.PERMISSION_OPERATION_FAILURE) { + changedInstallPermission = true; + } + } break; + + case GRANT_RUNTIME: { + // Grant previously granted runtime permissions. + for (int userId : UserManagerService.getInstance().getUserIds()) { + final PermissionState permissionState = origPermissions + .getRuntimePermissionState(perm, userId); + int flags = permissionState != null + ? permissionState.getFlags() : 0; + if (origPermissions.hasRuntimePermission(perm, userId)) { + // Don't propagate the permission in a permission review + // mode if the former was revoked, i.e. marked to not + // propagate on upgrade. Note that in a permission review + // mode install permissions are represented as constantly + // granted runtime ones since we need to keep a per user + // state associated with the permission. Also the revoke + // on upgrade flag is no longer applicable and is reset. + final boolean revokeOnUpgrade = (flags & PackageManager + .FLAG_PERMISSION_REVOKE_ON_UPGRADE) != 0; + if (revokeOnUpgrade) { + flags &= ~PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE; + // Since we changed the flags, we have to write. + updatedUserIds = ArrayUtils.appendInt( + updatedUserIds, userId); + } + if (!mSettings.mPermissionReviewRequired || !revokeOnUpgrade) { + if (permissionsState.grantRuntimePermission(bp, userId) == + PermissionsState.PERMISSION_OPERATION_FAILURE) { + // If we cannot put the permission as it was, + // we have to write. + updatedUserIds = ArrayUtils.appendInt( + updatedUserIds, userId); + } + } + + // If the app supports runtime permissions no need for a review. + if (mSettings.mPermissionReviewRequired + && appSupportsRuntimePermissions + && (flags & PackageManager + .FLAG_PERMISSION_REVIEW_REQUIRED) != 0) { + flags &= ~PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED; + // Since we changed the flags, we have to write. + updatedUserIds = ArrayUtils.appendInt( + updatedUserIds, userId); + } + } else if (mSettings.mPermissionReviewRequired + && !appSupportsRuntimePermissions) { + // For legacy apps that need a permission review, every new + // runtime permission is granted but it is pending a review. + // We also need to review only platform defined runtime + // permissions as these are the only ones the platform knows + // how to disable the API to simulate revocation as legacy + // apps don't expect to run with revoked permissions. + if (PLATFORM_PACKAGE_NAME.equals(bp.getSourcePackageName())) { + if ((flags & FLAG_PERMISSION_REVIEW_REQUIRED) == 0) { + flags |= FLAG_PERMISSION_REVIEW_REQUIRED; + // We changed the flags, hence have to write. + updatedUserIds = ArrayUtils.appendInt( + updatedUserIds, userId); + } + } + if (permissionsState.grantRuntimePermission(bp, userId) + != PermissionsState.PERMISSION_OPERATION_FAILURE) { + // We changed the permission, hence have to write. + updatedUserIds = ArrayUtils.appendInt( + updatedUserIds, userId); + } + } + // Propagate the permission flags. + permissionsState.updatePermissionFlags(bp, userId, flags, flags); + } + } break; + + case GRANT_UPGRADE: { + // Grant runtime permissions for a previously held install permission. + final PermissionState permissionState = origPermissions + .getInstallPermissionState(perm); + final int flags = + (permissionState != null) ? permissionState.getFlags() : 0; + + if (origPermissions.revokeInstallPermission(bp) + != PermissionsState.PERMISSION_OPERATION_FAILURE) { + // We will be transferring the permission flags, so clear them. + origPermissions.updatePermissionFlags(bp, UserHandle.USER_ALL, + PackageManager.MASK_PERMISSION_FLAGS, 0); + changedInstallPermission = true; + } + + // If the permission is not to be promoted to runtime we ignore it and + // also its other flags as they are not applicable to install permissions. + if ((flags & PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE) == 0) { + for (int userId : currentUserIds) { + if (permissionsState.grantRuntimePermission(bp, userId) != + PermissionsState.PERMISSION_OPERATION_FAILURE) { + // Transfer the permission flags. + permissionsState.updatePermissionFlags(bp, userId, + flags, flags); + // If we granted the permission, we have to write. + updatedUserIds = ArrayUtils.appendInt( + updatedUserIds, userId); + } + } + } + } break; + + default: { + if (packageOfInterest == null + || packageOfInterest.equals(pkg.packageName)) { + if (DEBUG_PERMISSIONS) { + Slog.i(TAG, "Not granting permission " + perm + + " to package " + pkg.packageName + + " because it was previously installed without"); + } + } + } break; + } + } else { + if (permissionsState.revokeInstallPermission(bp) != + PermissionsState.PERMISSION_OPERATION_FAILURE) { + // Also drop the permission flags. + permissionsState.updatePermissionFlags(bp, UserHandle.USER_ALL, + PackageManager.MASK_PERMISSION_FLAGS, 0); + changedInstallPermission = true; + Slog.i(TAG, "Un-granting permission " + perm + + " from package " + pkg.packageName + + " (protectionLevel=" + bp.getProtectionLevel() + + " flags=0x" + Integer.toHexString(pkg.applicationInfo.flags) + + ")"); + } else if (bp.isAppOp()) { + // Don't print warning for app op permissions, since it is fine for them + // not to be granted, there is a UI for the user to decide. + if (DEBUG_PERMISSIONS + && (packageOfInterest == null + || packageOfInterest.equals(pkg.packageName))) { + Slog.i(TAG, "Not granting permission " + perm + + " to package " + pkg.packageName + + " (protectionLevel=" + bp.getProtectionLevel() + + " flags=0x" + Integer.toHexString(pkg.applicationInfo.flags) + + ")"); + } + } + } + } + + if ((changedInstallPermission || replace) && !ps.areInstallPermissionsFixed() && + !ps.isSystem() || ps.isUpdatedSystem()) { + // This is the first that we have heard about this package, so the + // permissions we have now selected are fixed until explicitly + // changed. + ps.setInstallPermissionsFixed(true); + } + } + + // Persist the runtime permissions state for users with changes. If permissions + // were revoked because no app in the shared user declares them we have to + // write synchronously to avoid losing runtime permissions state. + if (callback != null) { + callback.onPermissionUpdated(updatedUserIds, runtimePermissionsRevoked); + } + } + + private boolean isNewPlatformPermissionForPackage(String perm, PackageParser.Package pkg) { + boolean allowed = false; + final int NP = PackageParser.NEW_PERMISSIONS.length; + for (int ip=0; ip<NP; ip++) { + final PackageParser.NewPermissionInfo npi + = PackageParser.NEW_PERMISSIONS[ip]; + if (npi.name.equals(perm) + && pkg.applicationInfo.targetSdkVersion < npi.sdkVersion) { + allowed = true; + Log.i(TAG, "Auto-granting " + perm + " to old pkg " + + pkg.packageName); + break; + } + } + return allowed; + } + + /** + * Determines whether a package is whitelisted for a particular privapp permission. + * + * <p>Does NOT check whether the package is a privapp, just whether it's whitelisted. + * + * <p>This handles parent/child apps. + */ + private boolean hasPrivappWhitelistEntry(String perm, PackageParser.Package pkg) { + ArraySet<String> wlPermissions = SystemConfig.getInstance() + .getPrivAppPermissions(pkg.packageName); + // Let's check if this package is whitelisted... + boolean whitelisted = wlPermissions != null && wlPermissions.contains(perm); + // If it's not, we'll also tail-recurse to the parent. + return whitelisted || + pkg.parentPackage != null && hasPrivappWhitelistEntry(perm, pkg.parentPackage); + } + + private boolean grantSignaturePermission(String perm, PackageParser.Package pkg, + BasePermission bp, PermissionsState origPermissions) { + boolean oemPermission = bp.isOEM(); + boolean privilegedPermission = bp.isPrivileged(); + boolean privappPermissionsDisable = + RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_DISABLE; + boolean platformPermission = PLATFORM_PACKAGE_NAME.equals(bp.getSourcePackageName()); + boolean platformPackage = PLATFORM_PACKAGE_NAME.equals(pkg.packageName); + if (!privappPermissionsDisable && privilegedPermission && pkg.isPrivileged() + && !platformPackage && platformPermission) { + if (!hasPrivappWhitelistEntry(perm, pkg)) { + Slog.w(TAG, "Privileged permission " + perm + " for package " + + pkg.packageName + " - not in privapp-permissions whitelist"); + // Only report violations for apps on system image + if (!mSystemReady && !pkg.isUpdatedSystemApp()) { + // it's only a reportable violation if the permission isn't explicitly denied + final ArraySet<String> deniedPermissions = SystemConfig.getInstance() + .getPrivAppDenyPermissions(pkg.packageName); + final boolean permissionViolation = + deniedPermissions == null || !deniedPermissions.contains(perm); + if (permissionViolation + && RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE) { + if (mPrivappPermissionsViolations == null) { + mPrivappPermissionsViolations = new ArraySet<>(); + } + mPrivappPermissionsViolations.add(pkg.packageName + ": " + perm); + } else { + return false; + } + } + if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE) { + return false; + } + } + } + final String systemPackageName = mPackageManagerInt.getKnownPackageName( + PackageManagerInternal.PACKAGE_SYSTEM, UserHandle.USER_SYSTEM); + final PackageParser.Package systemPackage = + mPackageManagerInt.getPackage(systemPackageName); + boolean allowed = (PackageManagerService.compareSignatures( + bp.getSourceSignatures(), pkg.mSignatures) + == PackageManager.SIGNATURE_MATCH) + || (PackageManagerService.compareSignatures( + systemPackage.mSignatures, pkg.mSignatures) + == PackageManager.SIGNATURE_MATCH); + if (!allowed && (privilegedPermission || oemPermission)) { + if (pkg.isSystem()) { + // For updated system applications, a privileged/oem permission + // is granted only if it had been defined by the original application. + if (pkg.isUpdatedSystemApp()) { + final PackageParser.Package disabledPkg = + mPackageManagerInt.getDisabledPackage(pkg.packageName); + final PackageSetting disabledPs = + (disabledPkg != null) ? (PackageSetting) disabledPkg.mExtras : null; + if (disabledPs != null + && disabledPs.getPermissionsState().hasInstallPermission(perm)) { + // If the original was granted this permission, we take + // that grant decision as read and propagate it to the + // update. + if ((privilegedPermission && disabledPs.isPrivileged()) + || (oemPermission && disabledPs.isOem() + && canGrantOemPermission(disabledPs, perm))) { + allowed = true; + } + } else { + // The system apk may have been updated with an older + // version of the one on the data partition, but which + // granted a new system permission that it didn't have + // before. In this case we do want to allow the app to + // now get the new permission if the ancestral apk is + // privileged to get it. + if (disabledPs != null && disabledPkg != null + && isPackageRequestingPermission(disabledPkg, perm) + && ((privilegedPermission && disabledPs.isPrivileged()) + || (oemPermission && disabledPs.isOem() + && canGrantOemPermission(disabledPs, perm)))) { + allowed = true; + } + // Also if a privileged parent package on the system image or any of + // its children requested a privileged/oem permission, the updated child + // packages can also get the permission. + if (pkg.parentPackage != null) { + final PackageParser.Package disabledParentPkg = mPackageManagerInt + .getDisabledPackage(pkg.parentPackage.packageName); + final PackageSetting disabledParentPs = (disabledParentPkg != null) + ? (PackageSetting) disabledParentPkg.mExtras : null; + if (disabledParentPkg != null + && ((privilegedPermission && disabledParentPs.isPrivileged()) + || (oemPermission && disabledParentPs.isOem()))) { + if (isPackageRequestingPermission(disabledParentPkg, perm) + && canGrantOemPermission(disabledParentPs, perm)) { + allowed = true; + } else if (disabledParentPkg.childPackages != null) { + for (PackageParser.Package disabledChildPkg + : disabledParentPkg.childPackages) { + final PackageSetting disabledChildPs = + (disabledChildPkg != null) + ? (PackageSetting) disabledChildPkg.mExtras + : null; + if (isPackageRequestingPermission(disabledChildPkg, perm) + && canGrantOemPermission( + disabledChildPs, perm)) { + allowed = true; + break; + } + } + } + } + } + } + } else { + final PackageSetting ps = (PackageSetting) pkg.mExtras; + allowed = (privilegedPermission && pkg.isPrivileged()) + || (oemPermission && pkg.isOem() + && canGrantOemPermission(ps, perm)); + } + } + } + if (!allowed) { + if (!allowed + && bp.isPre23() + && pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) { + // If this was a previously normal/dangerous permission that got moved + // to a system permission as part of the runtime permission redesign, then + // we still want to blindly grant it to old apps. + allowed = true; + } + if (!allowed && bp.isInstaller() + && pkg.packageName.equals(mPackageManagerInt.getKnownPackageName( + PackageManagerInternal.PACKAGE_INSTALLER, UserHandle.USER_SYSTEM))) { + // If this permission is to be granted to the system installer and + // this app is an installer, then it gets the permission. + allowed = true; + } + if (!allowed && bp.isVerifier() + && pkg.packageName.equals(mPackageManagerInt.getKnownPackageName( + PackageManagerInternal.PACKAGE_VERIFIER, UserHandle.USER_SYSTEM))) { + // If this permission is to be granted to the system verifier and + // this app is a verifier, then it gets the permission. + allowed = true; + } + if (!allowed && bp.isPreInstalled() + && pkg.isSystem()) { + // Any pre-installed system app is allowed to get this permission. + allowed = true; + } + if (!allowed && bp.isDevelopment()) { + // For development permissions, a development permission + // is granted only if it was already granted. + allowed = origPermissions.hasInstallPermission(perm); + } + if (!allowed && bp.isSetup() + && pkg.packageName.equals(mPackageManagerInt.getKnownPackageName( + PackageManagerInternal.PACKAGE_SETUP_WIZARD, UserHandle.USER_SYSTEM))) { + // If this permission is to be granted to the system setup wizard and + // this app is a setup wizard, then it gets the permission. + allowed = true; + } + } + return allowed; + } + + private static boolean canGrantOemPermission(PackageSetting ps, String permission) { + if (!ps.isOem()) { + return false; + } + // all oem permissions must explicitly be granted or denied + final Boolean granted = + SystemConfig.getInstance().getOemPermissions(ps.name).get(permission); + if (granted == null) { + throw new IllegalStateException("OEM permission" + permission + " requested by package " + + ps.name + " must be explicitly declared granted or not"); + } + return Boolean.TRUE == granted; + } + + private boolean isPermissionsReviewRequired(PackageParser.Package pkg, int userId) { + if (!mSettings.mPermissionReviewRequired) { + return false; + } + + // Permission review applies only to apps not supporting the new permission model. + if (pkg.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.M) { + return false; + } + + // Legacy apps have the permission and get user consent on launch. + if (pkg == null || pkg.mExtras == null) { + return false; + } + final PackageSetting ps = (PackageSetting) pkg.mExtras; + final PermissionsState permissionsState = ps.getPermissionsState(); + return permissionsState.isPermissionReviewRequired(userId); + } + + private boolean isPackageRequestingPermission(PackageParser.Package pkg, String permission) { + final int permCount = pkg.requestedPermissions.size(); + for (int j = 0; j < permCount; j++) { + String requestedPermission = pkg.requestedPermissions.get(j); + if (permission.equals(requestedPermission)) { + return true; + } + } + return false; + } + private void grantRuntimePermissionsGrantedToDisabledPackageLocked( PackageParser.Package pkg, int callingUid, PermissionCallback callback) { if (pkg.parentPackage == null) { @@ -744,7 +1494,8 @@ public class PermissionManagerService { } } - private int[] revokeUnusedSharedUserPermissions(SharedUserSetting suSetting, int[] allUserIds) { + private int[] revokeUnusedSharedUserPermissionsLocked( + SharedUserSetting suSetting, int[] allUserIds) { // Collect all used permissions in the UID final ArraySet<String> usedPermissions = new ArraySet<>(); final List<PackageParser.Package> pkgList = suSetting.getPackages(); @@ -845,7 +1596,79 @@ public class PermissionManagerService { return permissionsState.getPermissionFlags(permName, userId); } - private int updatePermissions(String packageName, PackageParser.Package pkgInfo, int flags) { + private static final int UPDATE_PERMISSIONS_ALL = 1<<0; + private static final int UPDATE_PERMISSIONS_REPLACE_PKG = 1<<1; + private static final int UPDATE_PERMISSIONS_REPLACE_ALL = 1<<2; + + private void updatePermissions(String packageName, PackageParser.Package pkg, + boolean replaceGrant, Collection<PackageParser.Package> allPackages, + PermissionCallback callback) { + final int flags = (pkg != null ? UPDATE_PERMISSIONS_ALL : 0) | + (replaceGrant ? UPDATE_PERMISSIONS_REPLACE_PKG : 0); + updatePermissions( + packageName, pkg, getVolumeUuidForPackage(pkg), flags, allPackages, callback); + if (pkg != null && pkg.childPackages != null) { + for (PackageParser.Package childPkg : pkg.childPackages) { + updatePermissions(childPkg.packageName, childPkg, + getVolumeUuidForPackage(childPkg), flags, allPackages, callback); + } + } + } + + private void updateAllPermissions(String volumeUuid, boolean sdkUpdated, + Collection<PackageParser.Package> allPackages, PermissionCallback callback) { + final int flags = UPDATE_PERMISSIONS_ALL | + (sdkUpdated + ? UPDATE_PERMISSIONS_REPLACE_PKG | UPDATE_PERMISSIONS_REPLACE_ALL + : 0); + updatePermissions(null, null, volumeUuid, flags, allPackages, callback); + } + + private void updatePermissions(String changingPkgName, PackageParser.Package changingPkg, + String replaceVolumeUuid, int flags, Collection<PackageParser.Package> allPackages, + PermissionCallback callback) { + // TODO: Most of the methods exposing BasePermission internals [source package name, + // etc..] shouldn't be needed. Instead, when we've parsed a permission that doesn't + // have package settings, we should make note of it elsewhere [map between + // source package name and BasePermission] and cycle through that here. Then we + // define a single method on BasePermission that takes a PackageSetting, changing + // package name and a package. + // NOTE: With this approach, we also don't need to tree trees differently than + // normal permissions. Today, we need two separate loops because these BasePermission + // objects are stored separately. + // Make sure there are no dangling permission trees. + flags = updatePermissionTrees(changingPkgName, changingPkg, flags); + + // Make sure all dynamic permissions have been assigned to a package, + // and make sure there are no dangling permissions. + flags = updatePermissions(changingPkgName, changingPkg, flags); + + Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "grantPermissions"); + // Now update the permissions for all packages, in particular + // replace the granted permissions of the system packages. + if ((flags & UPDATE_PERMISSIONS_ALL) != 0) { + for (PackageParser.Package pkg : allPackages) { + if (pkg != changingPkg) { + // Only replace for packages on requested volume + final String volumeUuid = getVolumeUuidForPackage(pkg); + final boolean replace = ((flags & UPDATE_PERMISSIONS_REPLACE_ALL) != 0) + && Objects.equals(replaceVolumeUuid, volumeUuid); + grantPermissions(pkg, replace, changingPkgName, callback); + } + } + } + + if (changingPkg != null) { + // Only replace for packages on requested volume + final String volumeUuid = getVolumeUuidForPackage(changingPkg); + final boolean replace = ((flags & UPDATE_PERMISSIONS_REPLACE_PKG) != 0) + && Objects.equals(replaceVolumeUuid, volumeUuid); + grantPermissions(changingPkg, replace, changingPkgName, callback); + } + Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); + } + + private int updatePermissions(String packageName, PackageParser.Package pkg, int flags) { Set<BasePermission> needsUpdate = null; synchronized (mLock) { final Iterator<BasePermission> it = mSettings.mPermissions.values().iterator(); @@ -856,10 +1679,10 @@ public class PermissionManagerService { } if (bp.getSourcePackageSetting() != null) { if (packageName != null && packageName.equals(bp.getSourcePackageName()) - && (pkgInfo == null || !hasPermission(pkgInfo, bp.getName()))) { + && (pkg == null || !hasPermission(pkg, bp.getName()))) { Slog.i(TAG, "Removing old permission tree: " + bp.getName() + " from package " + bp.getSourcePackageName()); - flags |= PackageManagerService.UPDATE_PERMISSIONS_ALL; + flags |= UPDATE_PERMISSIONS_ALL; it.remove(); } continue; @@ -872,13 +1695,13 @@ public class PermissionManagerService { } if (needsUpdate != null) { for (final BasePermission bp : needsUpdate) { - final PackageParser.Package pkg = + final PackageParser.Package sourcePkg = mPackageManagerInt.getPackage(bp.getSourcePackageName()); synchronized (mLock) { - if (pkg != null && pkg.mExtras != null) { - final PackageSetting ps = (PackageSetting) pkg.mExtras; + if (sourcePkg != null && sourcePkg.mExtras != null) { + final PackageSetting sourcePs = (PackageSetting) sourcePkg.mExtras; if (bp.getSourcePackageSetting() == null) { - bp.setSourcePackageSetting(ps); + bp.setSourcePackageSetting(sourcePs); } continue; } @@ -891,7 +1714,7 @@ public class PermissionManagerService { return flags; } - private int updatePermissionTrees(String packageName, PackageParser.Package pkgInfo, + private int updatePermissionTrees(String packageName, PackageParser.Package pkg, int flags) { Set<BasePermission> needsUpdate = null; synchronized (mLock) { @@ -900,10 +1723,10 @@ public class PermissionManagerService { final BasePermission bp = it.next(); if (bp.getSourcePackageSetting() != null) { if (packageName != null && packageName.equals(bp.getSourcePackageName()) - && (pkgInfo == null || !hasPermission(pkgInfo, bp.getName()))) { + && (pkg == null || !hasPermission(pkg, bp.getName()))) { Slog.i(TAG, "Removing old permission tree: " + bp.getName() + " from package " + bp.getSourcePackageName()); - flags |= PackageManagerService.UPDATE_PERMISSIONS_ALL; + flags |= UPDATE_PERMISSIONS_ALL; it.remove(); } continue; @@ -916,13 +1739,13 @@ public class PermissionManagerService { } if (needsUpdate != null) { for (final BasePermission bp : needsUpdate) { - final PackageParser.Package pkg = + final PackageParser.Package sourcePkg = mPackageManagerInt.getPackage(bp.getSourcePackageName()); synchronized (mLock) { - if (pkg != null && pkg.mExtras != null) { - final PackageSetting ps = (PackageSetting) pkg.mExtras; + if (sourcePkg != null && sourcePkg.mExtras != null) { + final PackageSetting sourcePs = (PackageSetting) sourcePkg.mExtras; if (bp.getSourcePackageSetting() == null) { - bp.setSourcePackageSetting(ps); + bp.setSourcePackageSetting(sourcePs); } continue; } @@ -985,7 +1808,7 @@ public class PermissionManagerService { callback.onInstallPermissionUpdated(); } else if (permissionsState.getRuntimePermissionState(permName, userId) != null || hadState) { - callback.onPermissionUpdated(userId); + callback.onPermissionUpdated(new int[] { userId }, false); } } } @@ -1083,6 +1906,29 @@ public class PermissionManagerService { } } + private void systemReady() { + mSystemReady = true; + if (mPrivappPermissionsViolations != null) { + throw new IllegalStateException("Signature|privileged permissions not in " + + "privapp-permissions whitelist: " + mPrivappPermissionsViolations); + } + } + + private static String getVolumeUuidForPackage(PackageParser.Package pkg) { + if (pkg == null) { + return StorageManager.UUID_PRIVATE_INTERNAL; + } + if (pkg.isExternal()) { + if (TextUtils.isEmpty(pkg.volumeUuid)) { + return StorageManager.UUID_PRIMARY_PHYSICAL; + } else { + return pkg.volumeUuid; + } + } else { + return StorageManager.UUID_PRIVATE_INTERNAL; + } + } + private static boolean hasPermission(PackageParser.Package pkgInfo, String permName) { for (int i=pkgInfo.permissions.size()-1; i>=0; i--) { if (pkgInfo.permissions.get(i).info.name.equals(permName)) { @@ -1154,10 +2000,22 @@ public class PermissionManagerService { private class PermissionManagerInternalImpl extends PermissionManagerInternal { @Override + public void systemReady() { + PermissionManagerService.this.systemReady(); + } + @Override + public boolean isPermissionsReviewRequired(Package pkg, int userId) { + return PermissionManagerService.this.isPermissionsReviewRequired(pkg, userId); + } + @Override public void addAllPermissions(Package pkg, boolean chatty) { PermissionManagerService.this.addAllPermissions(pkg, chatty); } @Override + public void addAllPermissionGroups(Package pkg, boolean chatty) { + PermissionManagerService.this.addAllPermissionGroups(pkg, chatty); + } + @Override public void removeAllPermissions(Package pkg, boolean chatty) { PermissionManagerService.this.removeAllPermissions(pkg, chatty); } @@ -1198,10 +2056,16 @@ public class PermissionManagerService { overridePolicy, callingUid, userId, callback); } @Override - public int[] revokeUnusedSharedUserPermissions(SharedUserSetting suSetting, - int[] allUserIds) { - return PermissionManagerService.this.revokeUnusedSharedUserPermissions( - (SharedUserSetting) suSetting, allUserIds); + public void updatePermissions(String packageName, Package pkg, boolean replaceGrant, + Collection<PackageParser.Package> allPackages, PermissionCallback callback) { + PermissionManagerService.this.updatePermissions( + packageName, pkg, replaceGrant, allPackages, callback); + } + @Override + public void updateAllPermissions(String volumeUuid, boolean sdkUpdated, + Collection<PackageParser.Package> allPackages, PermissionCallback callback) { + PermissionManagerService.this.updateAllPermissions( + volumeUuid, sdkUpdated, allPackages, callback); } @Override public String[] getAppOpPermissionPackages(String permName) { @@ -1214,16 +2078,6 @@ public class PermissionManagerService { callingUid, userId); } @Override - public int updatePermissions(String packageName, - PackageParser.Package pkgInfo, int flags) { - return PermissionManagerService.this.updatePermissions(packageName, pkgInfo, flags); - } - @Override - public int updatePermissionTrees(String packageName, - PackageParser.Package pkgInfo, int flags) { - return PermissionManagerService.this.updatePermissionTrees(packageName, pkgInfo, flags); - } - @Override public void updatePermissionFlags(String permName, String packageName, int flagMask, int flagValues, int callingUid, int userId, PermissionCallback callback) { PermissionManagerService.this.updatePermissionFlags( @@ -1252,6 +2106,20 @@ public class PermissionManagerService { permName, packageName, callingUid, userId); } @Override + public int checkUidPermission(String permName, int uid, int callingUid) { + return PermissionManagerService.this.checkUidPermission(permName, uid, callingUid); + } + @Override + public PermissionGroupInfo getPermissionGroupInfo(String groupName, int flags, + int callingUid) { + return PermissionManagerService.this.getPermissionGroupInfo( + groupName, flags, callingUid); + } + @Override + public List<PermissionGroupInfo> getAllPermissionGroups(int flags, int callingUid) { + return PermissionManagerService.this.getAllPermissionGroups(flags, callingUid); + } + @Override public PermissionInfo getPermissionInfo(String permName, String packageName, int flags, int callingUid) { return PermissionManagerService.this.getPermissionInfo( @@ -1276,17 +2144,5 @@ public class PermissionManagerService { return mSettings.getPermissionLocked(permName); } } - @Override - public void putPermissionTEMP(String permName, BasePermission permission) { - synchronized (PermissionManagerService.this.mLock) { - mSettings.putPermissionLocked(permName, (BasePermission) permission); - } - } - @Override - public Iterator<BasePermission> getPermissionIteratorTEMP() { - synchronized (PermissionManagerService.this.mLock) { - return mSettings.getAllPermissionsLocked().iterator(); - } - } } } diff --git a/services/core/java/com/android/server/pm/permission/PermissionSettings.java b/services/core/java/com/android/server/pm/permission/PermissionSettings.java index 7d125c9ebe87..f6c4990c3294 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionSettings.java +++ b/services/core/java/com/android/server/pm/permission/PermissionSettings.java @@ -19,6 +19,7 @@ package com.android.server.pm.permission; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; +import android.content.pm.PackageParser; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; @@ -46,7 +47,8 @@ import java.util.Set; */ public class PermissionSettings { - final boolean mPermissionReviewRequired; + public final boolean mPermissionReviewRequired; + /** * All of the permissions known to the system. The mapping is from permission * name to permission object. @@ -64,6 +66,14 @@ public class PermissionSettings { new ArrayMap<String, BasePermission>(); /** + * All permisson groups know to the system. The mapping is from permission group + * name to permission group object. + */ + @GuardedBy("mLock") + final ArrayMap<String, PackageParser.PermissionGroup> mPermissionGroups = + new ArrayMap<String, PackageParser.PermissionGroup>(); + + /** * Set of packages that request a particular app op. The mapping is from permission * name to package names. */ diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index b917dae2adf8..2494bded1b86 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -58,10 +58,6 @@ import android.os.WorkSource; import android.provider.Settings; import android.provider.Settings.SettingNotFoundException; import android.service.dreams.DreamManagerInternal; -import android.service.power.PowerServiceDumpProto; -import android.service.power.PowerServiceSettingsAndConfigurationDumpProto; -import android.service.power.SuspendBlockerProto; -import android.service.power.WakeLockProto; import android.service.vr.IVrManager; import android.service.vr.IVrStateCallbacks; import android.util.EventLog; @@ -620,8 +616,8 @@ public final class PowerManagerService extends SystemService } void dumpProto(ProtoOutputStream proto) { - final long constantsToken = proto.start(PowerServiceDumpProto.CONSTANTS); - proto.write(PowerServiceDumpProto.ConstantsProto.IS_NO_CACHED_WAKE_LOCKS, + final long constantsToken = proto.start(PowerManagerServiceDumpProto.CONSTANTS); + proto.write(PowerManagerServiceDumpProto.ConstantsProto.IS_NO_CACHED_WAKE_LOCKS, NO_CACHED_WAKE_LOCKS); proto.end(constantsToken); } @@ -3396,112 +3392,112 @@ public final class PowerManagerService extends SystemService synchronized (mLock) { mConstants.dumpProto(proto); - proto.write(PowerServiceDumpProto.DIRTY, mDirty); - proto.write(PowerServiceDumpProto.WAKEFULNESS, mWakefulness); - proto.write(PowerServiceDumpProto.IS_WAKEFULNESS_CHANGING, mWakefulnessChanging); - proto.write(PowerServiceDumpProto.IS_POWERED, mIsPowered); - proto.write(PowerServiceDumpProto.PLUG_TYPE, mPlugType); - proto.write(PowerServiceDumpProto.BATTERY_LEVEL, mBatteryLevel); + proto.write(PowerManagerServiceDumpProto.DIRTY, mDirty); + proto.write(PowerManagerServiceDumpProto.WAKEFULNESS, mWakefulness); + proto.write(PowerManagerServiceDumpProto.IS_WAKEFULNESS_CHANGING, mWakefulnessChanging); + proto.write(PowerManagerServiceDumpProto.IS_POWERED, mIsPowered); + proto.write(PowerManagerServiceDumpProto.PLUG_TYPE, mPlugType); + proto.write(PowerManagerServiceDumpProto.BATTERY_LEVEL, mBatteryLevel); proto.write( - PowerServiceDumpProto.BATTERY_LEVEL_WHEN_DREAM_STARTED, + PowerManagerServiceDumpProto.BATTERY_LEVEL_WHEN_DREAM_STARTED, mBatteryLevelWhenDreamStarted); - proto.write(PowerServiceDumpProto.DOCK_STATE, mDockState); - proto.write(PowerServiceDumpProto.IS_STAY_ON, mStayOn); - proto.write(PowerServiceDumpProto.IS_PROXIMITY_POSITIVE, mProximityPositive); - proto.write(PowerServiceDumpProto.IS_BOOT_COMPLETED, mBootCompleted); - proto.write(PowerServiceDumpProto.IS_SYSTEM_READY, mSystemReady); + proto.write(PowerManagerServiceDumpProto.DOCK_STATE, mDockState); + proto.write(PowerManagerServiceDumpProto.IS_STAY_ON, mStayOn); + proto.write(PowerManagerServiceDumpProto.IS_PROXIMITY_POSITIVE, mProximityPositive); + proto.write(PowerManagerServiceDumpProto.IS_BOOT_COMPLETED, mBootCompleted); + proto.write(PowerManagerServiceDumpProto.IS_SYSTEM_READY, mSystemReady); proto.write( - PowerServiceDumpProto.IS_HAL_AUTO_SUSPEND_MODE_ENABLED, + PowerManagerServiceDumpProto.IS_HAL_AUTO_SUSPEND_MODE_ENABLED, mHalAutoSuspendModeEnabled); proto.write( - PowerServiceDumpProto.IS_HAL_AUTO_INTERACTIVE_MODE_ENABLED, + PowerManagerServiceDumpProto.IS_HAL_AUTO_INTERACTIVE_MODE_ENABLED, mHalInteractiveModeEnabled); - final long activeWakeLocksToken = proto.start(PowerServiceDumpProto.ACTIVE_WAKE_LOCKS); + final long activeWakeLocksToken = proto.start(PowerManagerServiceDumpProto.ACTIVE_WAKE_LOCKS); proto.write( - PowerServiceDumpProto.ActiveWakeLocksProto.IS_CPU, + PowerManagerServiceDumpProto.ActiveWakeLocksProto.IS_CPU, (mWakeLockSummary & WAKE_LOCK_CPU) != 0); proto.write( - PowerServiceDumpProto.ActiveWakeLocksProto.IS_SCREEN_BRIGHT, + PowerManagerServiceDumpProto.ActiveWakeLocksProto.IS_SCREEN_BRIGHT, (mWakeLockSummary & WAKE_LOCK_SCREEN_BRIGHT) != 0); proto.write( - PowerServiceDumpProto.ActiveWakeLocksProto.IS_SCREEN_DIM, + PowerManagerServiceDumpProto.ActiveWakeLocksProto.IS_SCREEN_DIM, (mWakeLockSummary & WAKE_LOCK_SCREEN_DIM) != 0); proto.write( - PowerServiceDumpProto.ActiveWakeLocksProto.IS_BUTTON_BRIGHT, + PowerManagerServiceDumpProto.ActiveWakeLocksProto.IS_BUTTON_BRIGHT, (mWakeLockSummary & WAKE_LOCK_BUTTON_BRIGHT) != 0); proto.write( - PowerServiceDumpProto.ActiveWakeLocksProto.IS_PROXIMITY_SCREEN_OFF, + PowerManagerServiceDumpProto.ActiveWakeLocksProto.IS_PROXIMITY_SCREEN_OFF, (mWakeLockSummary & WAKE_LOCK_PROXIMITY_SCREEN_OFF) != 0); proto.write( - PowerServiceDumpProto.ActiveWakeLocksProto.IS_STAY_AWAKE, + PowerManagerServiceDumpProto.ActiveWakeLocksProto.IS_STAY_AWAKE, (mWakeLockSummary & WAKE_LOCK_STAY_AWAKE) != 0); proto.write( - PowerServiceDumpProto.ActiveWakeLocksProto.IS_DOZE, + PowerManagerServiceDumpProto.ActiveWakeLocksProto.IS_DOZE, (mWakeLockSummary & WAKE_LOCK_DOZE) != 0); proto.write( - PowerServiceDumpProto.ActiveWakeLocksProto.IS_DRAW, + PowerManagerServiceDumpProto.ActiveWakeLocksProto.IS_DRAW, (mWakeLockSummary & WAKE_LOCK_DRAW) != 0); proto.end(activeWakeLocksToken); - proto.write(PowerServiceDumpProto.NOTIFY_LONG_SCHEDULED_MS, mNotifyLongScheduled); - proto.write(PowerServiceDumpProto.NOTIFY_LONG_DISPATCHED_MS, mNotifyLongDispatched); - proto.write(PowerServiceDumpProto.NOTIFY_LONG_NEXT_CHECK_MS, mNotifyLongNextCheck); + proto.write(PowerManagerServiceDumpProto.NOTIFY_LONG_SCHEDULED_MS, mNotifyLongScheduled); + proto.write(PowerManagerServiceDumpProto.NOTIFY_LONG_DISPATCHED_MS, mNotifyLongDispatched); + proto.write(PowerManagerServiceDumpProto.NOTIFY_LONG_NEXT_CHECK_MS, mNotifyLongNextCheck); - final long userActivityToken = proto.start(PowerServiceDumpProto.USER_ACTIVITY); + final long userActivityToken = proto.start(PowerManagerServiceDumpProto.USER_ACTIVITY); proto.write( - PowerServiceDumpProto.UserActivityProto.IS_SCREEN_BRIGHT, + PowerManagerServiceDumpProto.UserActivityProto.IS_SCREEN_BRIGHT, (mUserActivitySummary & USER_ACTIVITY_SCREEN_BRIGHT) != 0); proto.write( - PowerServiceDumpProto.UserActivityProto.IS_SCREEN_DIM, + PowerManagerServiceDumpProto.UserActivityProto.IS_SCREEN_DIM, (mUserActivitySummary & USER_ACTIVITY_SCREEN_DIM) != 0); proto.write( - PowerServiceDumpProto.UserActivityProto.IS_SCREEN_DREAM, + PowerManagerServiceDumpProto.UserActivityProto.IS_SCREEN_DREAM, (mUserActivitySummary & USER_ACTIVITY_SCREEN_DREAM) != 0); proto.end(userActivityToken); proto.write( - PowerServiceDumpProto.IS_REQUEST_WAIT_FOR_NEGATIVE_PROXIMITY, + PowerManagerServiceDumpProto.IS_REQUEST_WAIT_FOR_NEGATIVE_PROXIMITY, mRequestWaitForNegativeProximity); - proto.write(PowerServiceDumpProto.IS_SANDMAN_SCHEDULED, mSandmanScheduled); - proto.write(PowerServiceDumpProto.IS_SANDMAN_SUMMONED, mSandmanSummoned); - proto.write(PowerServiceDumpProto.IS_LOW_POWER_MODE_ENABLED, mLowPowerModeEnabled); - proto.write(PowerServiceDumpProto.IS_BATTERY_LEVEL_LOW, mBatteryLevelLow); - proto.write(PowerServiceDumpProto.IS_LIGHT_DEVICE_IDLE_MODE, mLightDeviceIdleMode); - proto.write(PowerServiceDumpProto.IS_DEVICE_IDLE_MODE, mDeviceIdleMode); + proto.write(PowerManagerServiceDumpProto.IS_SANDMAN_SCHEDULED, mSandmanScheduled); + proto.write(PowerManagerServiceDumpProto.IS_SANDMAN_SUMMONED, mSandmanSummoned); + proto.write(PowerManagerServiceDumpProto.IS_LOW_POWER_MODE_ENABLED, mLowPowerModeEnabled); + proto.write(PowerManagerServiceDumpProto.IS_BATTERY_LEVEL_LOW, mBatteryLevelLow); + proto.write(PowerManagerServiceDumpProto.IS_LIGHT_DEVICE_IDLE_MODE, mLightDeviceIdleMode); + proto.write(PowerManagerServiceDumpProto.IS_DEVICE_IDLE_MODE, mDeviceIdleMode); for (int id : mDeviceIdleWhitelist) { - proto.write(PowerServiceDumpProto.DEVICE_IDLE_WHITELIST, id); + proto.write(PowerManagerServiceDumpProto.DEVICE_IDLE_WHITELIST, id); } for (int id : mDeviceIdleTempWhitelist) { - proto.write(PowerServiceDumpProto.DEVICE_IDLE_TEMP_WHITELIST, id); + proto.write(PowerManagerServiceDumpProto.DEVICE_IDLE_TEMP_WHITELIST, id); } - proto.write(PowerServiceDumpProto.LAST_WAKE_TIME_MS, mLastWakeTime); - proto.write(PowerServiceDumpProto.LAST_SLEEP_TIME_MS, mLastSleepTime); - proto.write(PowerServiceDumpProto.LAST_USER_ACTIVITY_TIME_MS, mLastUserActivityTime); + proto.write(PowerManagerServiceDumpProto.LAST_WAKE_TIME_MS, mLastWakeTime); + proto.write(PowerManagerServiceDumpProto.LAST_SLEEP_TIME_MS, mLastSleepTime); + proto.write(PowerManagerServiceDumpProto.LAST_USER_ACTIVITY_TIME_MS, mLastUserActivityTime); proto.write( - PowerServiceDumpProto.LAST_USER_ACTIVITY_TIME_NO_CHANGE_LIGHTS_MS, + PowerManagerServiceDumpProto.LAST_USER_ACTIVITY_TIME_NO_CHANGE_LIGHTS_MS, mLastUserActivityTimeNoChangeLights); proto.write( - PowerServiceDumpProto.LAST_INTERACTIVE_POWER_HINT_TIME_MS, + PowerManagerServiceDumpProto.LAST_INTERACTIVE_POWER_HINT_TIME_MS, mLastInteractivePowerHintTime); proto.write( - PowerServiceDumpProto.LAST_SCREEN_BRIGHTNESS_BOOST_TIME_MS, + PowerManagerServiceDumpProto.LAST_SCREEN_BRIGHTNESS_BOOST_TIME_MS, mLastScreenBrightnessBoostTime); proto.write( - PowerServiceDumpProto.IS_SCREEN_BRIGHTNESS_BOOST_IN_PROGRESS, + PowerManagerServiceDumpProto.IS_SCREEN_BRIGHTNESS_BOOST_IN_PROGRESS, mScreenBrightnessBoostInProgress); - proto.write(PowerServiceDumpProto.IS_DISPLAY_READY, mDisplayReady); + proto.write(PowerManagerServiceDumpProto.IS_DISPLAY_READY, mDisplayReady); proto.write( - PowerServiceDumpProto.IS_HOLDING_WAKE_LOCK_SUSPEND_BLOCKER, + PowerManagerServiceDumpProto.IS_HOLDING_WAKE_LOCK_SUSPEND_BLOCKER, mHoldingWakeLockSuspendBlocker); proto.write( - PowerServiceDumpProto.IS_HOLDING_DISPLAY_SUSPEND_BLOCKER, + PowerManagerServiceDumpProto.IS_HOLDING_DISPLAY_SUSPEND_BLOCKER, mHoldingDisplaySuspendBlocker); final long settingsAndConfigurationToken = - proto.start(PowerServiceDumpProto.SETTINGS_AND_CONFIGURATION); + proto.start(PowerManagerServiceDumpProto.SETTINGS_AND_CONFIGURATION); proto.write( PowerServiceSettingsAndConfigurationDumpProto .IS_DECOUPLE_HAL_AUTO_SUSPEND_MODE_FROM_DISPLAY_CONFIG, @@ -3698,42 +3694,43 @@ public final class PowerManagerService extends SystemService final int sleepTimeout = getSleepTimeoutLocked(); final int screenOffTimeout = getScreenOffTimeoutLocked(sleepTimeout); final int screenDimDuration = getScreenDimDurationLocked(screenOffTimeout); - proto.write(PowerServiceDumpProto.SLEEP_TIMEOUT_MS, sleepTimeout); - proto.write(PowerServiceDumpProto.SCREEN_OFF_TIMEOUT_MS, screenOffTimeout); - proto.write(PowerServiceDumpProto.SCREEN_DIM_DURATION_MS, screenDimDuration); - proto.write(PowerServiceDumpProto.ARE_UIDS_CHANGING, mUidsChanging); - proto.write(PowerServiceDumpProto.ARE_UIDS_CHANGED, mUidsChanged); + proto.write(PowerManagerServiceDumpProto.SLEEP_TIMEOUT_MS, sleepTimeout); + proto.write(PowerManagerServiceDumpProto.SCREEN_OFF_TIMEOUT_MS, screenOffTimeout); + proto.write(PowerManagerServiceDumpProto.SCREEN_DIM_DURATION_MS, screenDimDuration); + proto.write(PowerManagerServiceDumpProto.ARE_UIDS_CHANGING, mUidsChanging); + proto.write(PowerManagerServiceDumpProto.ARE_UIDS_CHANGED, mUidsChanged); for (int i = 0; i < mUidState.size(); i++) { final UidState state = mUidState.valueAt(i); - final long uIDToken = proto.start(PowerServiceDumpProto.UIDS); + final long uIDToken = proto.start(PowerManagerServiceDumpProto.UID_STATES); final int uid = mUidState.keyAt(i); - proto.write(PowerServiceDumpProto.UidProto.UID, uid); - proto.write(PowerServiceDumpProto.UidProto.UID_STRING, UserHandle.formatUid(uid)); - proto.write(PowerServiceDumpProto.UidProto.IS_ACTIVE, state.mActive); - proto.write(PowerServiceDumpProto.UidProto.NUM_WAKE_LOCKS, state.mNumWakeLocks); + proto.write(PowerManagerServiceDumpProto.UidStateProto.UID, uid); + proto.write(PowerManagerServiceDumpProto.UidStateProto.UID_STRING, UserHandle.formatUid(uid)); + proto.write(PowerManagerServiceDumpProto.UidStateProto.IS_ACTIVE, state.mActive); + proto.write(PowerManagerServiceDumpProto.UidStateProto.NUM_WAKE_LOCKS, state.mNumWakeLocks); if (state.mProcState == ActivityManager.PROCESS_STATE_UNKNOWN) { - proto.write(PowerServiceDumpProto.UidProto.IS_PROCESS_STATE_UNKNOWN, true); + proto.write(PowerManagerServiceDumpProto.UidStateProto.IS_PROCESS_STATE_UNKNOWN, true); } else { - proto.write(PowerServiceDumpProto.UidProto.PROCESS_STATE, state.mProcState); + proto.write(PowerManagerServiceDumpProto.UidStateProto.PROCESS_STATE, + ActivityManager.processStateAmToProto(state.mProcState)); } proto.end(uIDToken); } - mHandler.getLooper().writeToProto(proto, PowerServiceDumpProto.LOOPER); + mHandler.getLooper().writeToProto(proto, PowerManagerServiceDumpProto.LOOPER); for (WakeLock wl : mWakeLocks) { - wl.writeToProto(proto, PowerServiceDumpProto.WAKE_LOCKS); + wl.writeToProto(proto, PowerManagerServiceDumpProto.WAKE_LOCKS); } for (SuspendBlocker sb : mSuspendBlockers) { - sb.writeToProto(proto, PowerServiceDumpProto.SUSPEND_BLOCKERS); + sb.writeToProto(proto, PowerManagerServiceDumpProto.SUSPEND_BLOCKERS); } wcd = mWirelessChargerDetector; } if (wcd != null) { - wcd.writeToProto(proto, PowerServiceDumpProto.WIRELESS_CHARGER_DETECTOR); + wcd.writeToProto(proto, PowerManagerServiceDumpProto.WIRELESS_CHARGER_DETECTOR); } proto.flush(); } diff --git a/services/core/java/com/android/server/power/ShutdownThread.java b/services/core/java/com/android/server/power/ShutdownThread.java index 515fa399007f..755c5f09599c 100644 --- a/services/core/java/com/android/server/power/ShutdownThread.java +++ b/services/core/java/com/android/server/power/ShutdownThread.java @@ -34,8 +34,6 @@ import android.content.IntentFilter; import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import android.media.AudioAttributes; -import android.nfc.INfcAdapter; -import android.nfc.NfcAdapter; import android.os.FileUtils; import android.os.Handler; import android.os.PowerManager; @@ -124,7 +122,6 @@ public final class ShutdownThread extends Thread { private static String METRIC_RADIOS = "shutdown_radios"; private static String METRIC_BT = "shutdown_bt"; private static String METRIC_RADIO = "shutdown_radio"; - private static String METRIC_NFC = "shutdown_nfc"; private static String METRIC_SM = "shutdown_storage_manager"; private final Object mActionDoneSync = new Object(); @@ -629,29 +626,14 @@ public final class ShutdownThread extends Thread { Thread t = new Thread() { public void run() { TimingsTraceLog shutdownTimingsTraceLog = newTimingsLog(); - boolean nfcOff; boolean bluetoothReadyForShutdown; boolean radioOff; - final INfcAdapter nfc = - INfcAdapter.Stub.asInterface(ServiceManager.checkService("nfc")); final ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone")); final IBluetoothManager bluetooth = IBluetoothManager.Stub.asInterface(ServiceManager.checkService( BluetoothAdapter.BLUETOOTH_MANAGER_SERVICE)); - try { - nfcOff = nfc == null || - nfc.getState() == NfcAdapter.STATE_OFF; - if (!nfcOff) { - Log.w(TAG, "Turning off NFC..."); - metricStarted(METRIC_NFC); - nfc.disable(false); // Don't persist new state - } - } catch (RemoteException ex) { - Log.e(TAG, "RemoteException during NFC shutdown", ex); - nfcOff = true; - } try { bluetoothReadyForShutdown = bluetooth == null || @@ -678,7 +660,7 @@ public final class ShutdownThread extends Thread { radioOff = true; } - Log.i(TAG, "Waiting for NFC, Bluetooth and Radio..."); + Log.i(TAG, "Waiting for Bluetooth and Radio..."); long delay = endTime - SystemClock.elapsedRealtime(); while (delay > 0) { @@ -722,23 +704,9 @@ public final class ShutdownThread extends Thread { .logDuration("ShutdownRadio", TRON_METRICS.get(METRIC_RADIO)); } } - if (!nfcOff) { - try { - nfcOff = nfc.getState() == NfcAdapter.STATE_OFF; - } catch (RemoteException ex) { - Log.e(TAG, "RemoteException during NFC shutdown", ex); - nfcOff = true; - } - if (nfcOff) { - Log.i(TAG, "NFC turned off."); - metricEnded(METRIC_NFC); - shutdownTimingsTraceLog - .logDuration("ShutdownNfc", TRON_METRICS.get(METRIC_NFC)); - } - } - if (radioOff && bluetoothReadyForShutdown && nfcOff) { - Log.i(TAG, "NFC, Radio and Bluetooth shutdown complete."); + if (radioOff && bluetoothReadyForShutdown) { + Log.i(TAG, "Radio and Bluetooth shutdown complete."); done[0] = true; break; } @@ -755,7 +723,7 @@ public final class ShutdownThread extends Thread { } catch (InterruptedException ex) { } if (!done[0]) { - Log.w(TAG, "Timed out waiting for NFC, Radio and Bluetooth shutdown."); + Log.w(TAG, "Timed out waiting for Radio and Bluetooth shutdown."); } } diff --git a/services/core/java/com/android/server/power/WirelessChargerDetector.java b/services/core/java/com/android/server/power/WirelessChargerDetector.java index 6ee9dcd3cfdb..54487e39d82f 100644 --- a/services/core/java/com/android/server/power/WirelessChargerDetector.java +++ b/services/core/java/com/android/server/power/WirelessChargerDetector.java @@ -24,7 +24,6 @@ import android.os.BatteryManager; import android.os.Handler; import android.os.Message; import android.os.SystemClock; -import android.service.power.WirelessChargerDetectorProto; import android.util.Slog; import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java index 52526e2f7d24..5ce4d46c6b4a 100644 --- a/services/core/java/com/android/server/wm/DockedStackDividerController.java +++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java @@ -137,6 +137,8 @@ public class DockedStackDividerController implements DimLayerUser { float mLastDividerProgress; private final DividerSnapAlgorithm[] mSnapAlgorithmForRotation = new DividerSnapAlgorithm[4]; private boolean mImeHideRequested; + private final Rect mLastDimLayerRect = new Rect(); + private float mLastDimLayerAlpha; DockedStackDividerController(WindowManagerService service, DisplayContent displayContent) { mService = service; @@ -525,7 +527,6 @@ public class DockedStackDividerController implements DimLayerUser { * display in that windowing mode. */ void setResizeDimLayer(boolean visible, int targetWindowingMode, float alpha) { - mService.openSurfaceTransaction(); // TODO: Maybe only allow split-screen windowing modes? final TaskStack stack = targetWindowingMode != WINDOWING_MODE_UNDEFINED ? mDisplayContent.getStack(targetWindowingMode) @@ -535,16 +536,33 @@ public class DockedStackDividerController implements DimLayerUser { if (visibleAndValid) { stack.getDimBounds(mTmpRect); if (mTmpRect.height() > 0 && mTmpRect.width() > 0) { - mDimLayer.setBounds(mTmpRect); - mDimLayer.show(getResizeDimLayer(), alpha, 0 /* duration */); + if (!mLastDimLayerRect.equals(mTmpRect) || mLastDimLayerAlpha != alpha) { + try { + // TODO: This should use the regular animation transaction - here and below + mService.openSurfaceTransaction(); + mDimLayer.setBounds(mTmpRect); + mDimLayer.show(getResizeDimLayer(), alpha, 0 /* duration */); + } finally { + mService.closeSurfaceTransaction(); + } + } + mLastDimLayerRect.set(mTmpRect); + mLastDimLayerAlpha = alpha; } else { visibleAndValid = false; } } if (!visibleAndValid) { - mDimLayer.hide(); + if (mLastDimLayerAlpha != 0f) { + try { + mService.openSurfaceTransaction(); + mDimLayer.hide(); + } finally { + mService.closeSurfaceTransaction(); + } + } + mLastDimLayerAlpha = 0f; } - mService.closeSurfaceTransaction(); } /** diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java new file mode 100644 index 000000000000..36871b1d8c5b --- /dev/null +++ b/services/core/java/com/android/server/wm/DragDropController.java @@ -0,0 +1,306 @@ +/* + * Copyright (C) 2017 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.server.wm; + +import static android.graphics.PixelFormat.TRANSLUCENT; +import static android.view.SurfaceControl.HIDDEN; +import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG; +import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS; +import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS; +import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; + +import android.content.ClipData; +import android.os.Binder; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.util.Slog; +import android.view.Display; +import android.view.IWindow; +import android.view.Surface; +import android.view.Surface.OutOfResourcesException; +import android.view.SurfaceControl; +import android.view.SurfaceSession; +import android.view.View; +import com.android.server.wm.WindowManagerService.H; + +/** + * Managing drag and drop operations initiated by View#startDragAndDrop. + */ +class DragDropController { + private static final float DRAG_SHADOW_ALPHA_TRANSPARENT = .7071f; + private static final long DRAG_TIMEOUT_MS = 5000; + + IBinder prepareDrag(WindowManagerService service, SurfaceSession session, int callerPid, + int callerUid, IWindow window, int flags, int width, int height, Surface outSurface) { + if (DEBUG_DRAG) { + Slog.d(TAG_WM, "prepare drag surface: w=" + width + " h=" + height + + " flags=" + Integer.toHexString(flags) + " win=" + window + + " asbinder=" + window.asBinder()); + } + + IBinder token = null; + + synchronized (service.mWindowMap) { + try { + if (service.mDragState == null) { + // TODO(multi-display): support other displays + final DisplayContent displayContent = + service.getDefaultDisplayContentLocked(); + final Display display = displayContent.getDisplay(); + + SurfaceControl surface = new SurfaceControl(session, "drag surface", + width, height, TRANSLUCENT, HIDDEN); + surface.setLayerStack(display.getLayerStack()); + float alpha = 1; + if ((flags & View.DRAG_FLAG_OPAQUE) == 0) { + alpha = DRAG_SHADOW_ALPHA_TRANSPARENT; + } + surface.setAlpha(alpha); + + if (SHOW_TRANSACTIONS) Slog.i(TAG_WM, " DRAG " + + surface + ": CREATE"); + outSurface.copyFrom(surface); + final IBinder winBinder = window.asBinder(); + token = new Binder(); + service.mDragState = + new DragState(service, token, surface, flags, winBinder); + service.mDragState.mPid = callerPid; + service.mDragState.mUid = callerUid; + service.mDragState.mOriginalAlpha = alpha; + token = service.mDragState.mToken = new Binder(); + + // 5 second timeout for this window to actually begin the drag + service.mH.removeMessages(H.DRAG_START_TIMEOUT, winBinder); + Message msg = service.mH.obtainMessage(H.DRAG_START_TIMEOUT, winBinder); + service.mH.sendMessageDelayed(msg, DRAG_TIMEOUT_MS); + } else { + Slog.w(TAG_WM, "Drag already in progress"); + } + } catch (OutOfResourcesException e) { + Slog.e(TAG_WM, "Can't allocate drag surface w=" + width + " h=" + height, + e); + if (service.mDragState != null) { + service.mDragState.reset(); + service.mDragState = null; + } + } + } + + return token; + } + + boolean performDrag(WindowManagerService service, IWindow window, IBinder dragToken, + int touchSource, float touchX, float touchY, float thumbCenterX, float thumbCenterY, + ClipData data) { + if (DEBUG_DRAG) { + Slog.d(TAG_WM, "perform drag: win=" + window + " data=" + data); + } + + synchronized (service.mWindowMap) { + if (service.mDragState == null) { + Slog.w(TAG_WM, "No drag prepared"); + throw new IllegalStateException("performDrag() without prepareDrag()"); + } + + if (dragToken != service.mDragState.mToken) { + Slog.w(TAG_WM, "Performing mismatched drag"); + throw new IllegalStateException("performDrag() does not match prepareDrag()"); + } + + final WindowState callingWin = service.windowForClientLocked(null, window, false); + if (callingWin == null) { + Slog.w(TAG_WM, "Bad requesting window " + window); + return false; // !!! TODO: throw here? + } + + // !!! TODO: if input is not still focused on the initiating window, fail + // the drag initiation (e.g. an alarm window popped up just as the application + // called performDrag() + + service.mH.removeMessages(H.DRAG_START_TIMEOUT, window.asBinder()); + + // !!! TODO: extract the current touch (x, y) in screen coordinates. That + // will let us eliminate the (touchX,touchY) parameters from the API. + + // !!! FIXME: put all this heavy stuff onto the mH looper, as well as + // the actual drag event dispatch stuff in the dragstate + + final DisplayContent displayContent = callingWin.getDisplayContent(); + if (displayContent == null) { + return false; + } + Display display = displayContent.getDisplay(); + service.mDragState.register(display); + if (!service.mInputManager.transferTouchFocus(callingWin.mInputChannel, + service.mDragState.getInputChannel())) { + Slog.e(TAG_WM, "Unable to transfer touch focus"); + service.mDragState.unregister(); + service.mDragState.reset(); + service.mDragState = null; + return false; + } + + service.mDragState.mDisplayContent = displayContent; + service.mDragState.mData = data; + service.mDragState.broadcastDragStartedLw(touchX, touchY); + service.mDragState.overridePointerIconLw(touchSource); + + // remember the thumb offsets for later + service.mDragState.mThumbOffsetX = thumbCenterX; + service.mDragState.mThumbOffsetY = thumbCenterY; + + // Make the surface visible at the proper location + final SurfaceControl surfaceControl = service.mDragState.mSurfaceControl; + if (SHOW_LIGHT_TRANSACTIONS) Slog.i( + TAG_WM, ">>> OPEN TRANSACTION performDrag"); + service.openSurfaceTransaction(); + try { + surfaceControl.setPosition(touchX - thumbCenterX, + touchY - thumbCenterY); + surfaceControl.setLayer(service.mDragState.getDragLayerLw()); + surfaceControl.setLayerStack(display.getLayerStack()); + surfaceControl.show(); + } finally { + service.closeSurfaceTransaction(); + if (SHOW_LIGHT_TRANSACTIONS) Slog.i( + TAG_WM, "<<< CLOSE TRANSACTION performDrag"); + } + + service.mDragState.notifyLocationLw(touchX, touchY); + } + + return true; // success! + } + + void reportDropResult(WindowManagerService service, IWindow window, boolean consumed) { + IBinder token = window.asBinder(); + if (DEBUG_DRAG) { + Slog.d(TAG_WM, "Drop result=" + consumed + " reported by " + token); + } + + synchronized (service.mWindowMap) { + if (service.mDragState == null) { + // Most likely the drop recipient ANRed and we ended the drag + // out from under it. Log the issue and move on. + Slog.w(TAG_WM, "Drop result given but no drag in progress"); + return; + } + + if (service.mDragState.mToken != token) { + // We're in a drag, but the wrong window has responded. + Slog.w(TAG_WM, "Invalid drop-result claim by " + window); + throw new IllegalStateException("reportDropResult() by non-recipient"); + } + + // The right window has responded, even if it's no longer around, + // so be sure to halt the timeout even if the later WindowState + // lookup fails. + service.mH.removeMessages(H.DRAG_END_TIMEOUT, window.asBinder()); + WindowState callingWin = service.windowForClientLocked(null, window, false); + if (callingWin == null) { + Slog.w(TAG_WM, "Bad result-reporting window " + window); + return; // !!! TODO: throw here? + } + + service.mDragState.mDragResult = consumed; + service.mDragState.endDragLw(); + } + } + + void cancelDragAndDrop(WindowManagerService service, IBinder dragToken) { + if (DEBUG_DRAG) { + Slog.d(TAG_WM, "cancelDragAndDrop"); + } + + synchronized (service.mWindowMap) { + if (service.mDragState == null) { + Slog.w(TAG_WM, "cancelDragAndDrop() without prepareDrag()"); + throw new IllegalStateException("cancelDragAndDrop() without prepareDrag()"); + } + + if (service.mDragState.mToken != dragToken) { + Slog.w(TAG_WM, + "cancelDragAndDrop() does not match prepareDrag()"); + throw new IllegalStateException( + "cancelDragAndDrop() does not match prepareDrag()"); + } + + service.mDragState.mDragResult = false; + service.mDragState.cancelDragLw(); + } + } + + void dragRecipientEntered(IWindow window) { + if (DEBUG_DRAG) { + Slog.d(TAG_WM, "Drag into new candidate view @ " + window.asBinder()); + } + } + + void dragRecipientExited(IWindow window) { + if (DEBUG_DRAG) { + Slog.d(TAG_WM, "Drag from old candidate view @ " + window.asBinder()); + } + } + + void handleMessage(WindowManagerService service, Message msg) { + switch (msg.what) { + case H.DRAG_START_TIMEOUT: { + IBinder win = (IBinder) msg.obj; + if (DEBUG_DRAG) { + Slog.w(TAG_WM, "Timeout starting drag by win " + win); + } + synchronized (service.mWindowMap) { + // !!! TODO: ANR the app that has failed to start the drag in time + if (service.mDragState != null) { + service.mDragState.unregister(); + service.mDragState.reset(); + service.mDragState = null; + } + } + break; + } + + case H.DRAG_END_TIMEOUT: { + IBinder win = (IBinder) msg.obj; + if (DEBUG_DRAG) { + Slog.w(TAG_WM, "Timeout ending drag to win " + win); + } + synchronized (service.mWindowMap) { + // !!! TODO: ANR the drag-receiving app + if (service.mDragState != null) { + service.mDragState.mDragResult = false; + service.mDragState.endDragLw(); + } + } + break; + } + + case H.TEAR_DOWN_DRAG_AND_DROP_INPUT: { + if (DEBUG_DRAG) + Slog.d(TAG_WM, "Drag ending; tearing down input channel"); + DragState.InputInterceptor interceptor = (DragState.InputInterceptor) msg.obj; + if (interceptor != null) { + synchronized (service.mWindowMap) { + interceptor.tearDown(); + } + } + break; + } + } + } +} diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java index 3fdafc7bebdf..4a75e010add5 100644 --- a/services/core/java/com/android/server/wm/DragState.java +++ b/services/core/java/com/android/server/wm/DragState.java @@ -22,6 +22,10 @@ import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACT import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; +import android.animation.Animator; +import android.animation.PropertyValuesHolder; +import android.animation.ValueAnimator; +import android.annotation.Nullable; import android.content.ClipData; import android.content.ClipDescription; import android.content.Context; @@ -29,7 +33,9 @@ import android.graphics.Matrix; import android.graphics.Point; import android.hardware.input.InputManager; import android.os.Build; +import android.os.Handler; import android.os.IBinder; +import android.os.Looper; import android.os.Message; import android.os.Process; import android.os.RemoteException; @@ -44,16 +50,12 @@ import android.view.InputChannel; import android.view.InputDevice; import android.view.PointerIcon; import android.view.SurfaceControl; +import android.view.SurfaceControl.Transaction; import android.view.View; import android.view.WindowManager; -import android.view.animation.AlphaAnimation; -import android.view.animation.Animation; -import android.view.animation.AnimationSet; import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; -import android.view.animation.ScaleAnimation; import android.view.animation.Transformation; -import android.view.animation.TranslateAnimation; import com.android.server.input.InputApplicationHandle; import com.android.server.input.InputWindowHandle; @@ -78,8 +80,20 @@ class DragState { View.DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION | View.DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION; + // Property names for animations + private static final String ANIMATED_PROPERTY_X = "x"; + private static final String ANIMATED_PROPERTY_Y = "y"; + private static final String ANIMATED_PROPERTY_ALPHA = "alpha"; + private static final String ANIMATED_PROPERTY_SCALE = "scale"; + + // Messages for Handler. + private static final int MSG_ANIMATION_END = 0; + final WindowManagerService mService; IBinder mToken; + /** + * Do not use the variable from the out of animation thread while mAnimator is not null. + */ SurfaceControl mSurfaceControl; int mFlags; IBinder mLocalWin; @@ -101,10 +115,10 @@ class DragState { boolean mDragInProgress; DisplayContent mDisplayContent; - private Animation mAnimation; - final Transformation mTransformation = new Transformation(); + @Nullable private ValueAnimator mAnimator; private final Interpolator mCubicEaseOutInterpolator = new DecelerateInterpolator(1.5f); private Point mDisplaySize = new Point(); + private final Handler mHandler; DragState(WindowManagerService service, IBinder token, SurfaceControl surface, int flags, IBinder localWin) { @@ -114,9 +128,14 @@ class DragState { mFlags = flags; mLocalWin = localWin; mNotifiedWindows = new ArrayList<WindowState>(); + mHandler = new DragStateHandler(service.mH.getLooper()); } void reset() { + if (mAnimator != null) { + Slog.wtf(TAG_WM, + "Unexpectedly destroying mSurfaceControl while animation is running"); + } if (mSurfaceControl != null) { mSurfaceControl.destroy(); } @@ -388,23 +407,33 @@ class DragState { } void endDragLw() { - if (mAnimation != null) { + if (mAnimator != null) { return; } if (!mDragResult) { - mAnimation = createReturnAnimationLocked(); - mService.scheduleAnimationLocked(); + mAnimator = createReturnAnimationLocked(); return; // Will call cleanUpDragLw when the animation is done. } cleanUpDragLw(); } void cancelDragLw() { - if (mAnimation != null) { + if (mAnimator != null) { + return; + } + if (!mDragInProgress) { + // This can happen if an app invokes Session#cancelDragAndDrop before + // Session#performDrag. Reset the drag state: + // 1. without sending the end broadcast because the start broadcast has not been sent, + // and + // 2. without playing the cancel animation because H.DRAG_START_TIMEOUT may be sent to + // WindowManagerService, which will cause DragState#reset() while playing the + // cancel animation. + reset(); + mService.mDragState = null; return; } - mAnimation = createCancelAnimationLocked(); - mService.scheduleAnimationLocked(); + mAnimator = createCancelAnimationLocked(); } private void cleanUpDragLw() { @@ -422,7 +451,7 @@ class DragState { } void notifyMoveLw(float x, float y) { - if (mAnimation != null) { + if (mAnimator != null) { return; } mCurrentX = x; @@ -491,7 +520,7 @@ class DragState { // dispatch the global drag-ended message, 'false' if we need to wait for a // result from the recipient. boolean notifyDropLw(float x, float y) { - if (mAnimation != null) { + if (mAnimator != null) { return false; } mCurrentX = x; @@ -560,56 +589,52 @@ class DragState { dragAndDropPermissions, result); } - boolean stepAnimationLocked(long currentTimeMs) { - if (mAnimation == null) { - return false; - } - - mTransformation.clear(); - if (!mAnimation.getTransformation(currentTimeMs, mTransformation)) { - cleanUpDragLw(); - return false; - } - - mTransformation.getMatrix().postTranslate( - mCurrentX - mThumbOffsetX, mCurrentY - mThumbOffsetY); - final float tmpFloats[] = mService.mTmpFloats; - mTransformation.getMatrix().getValues(tmpFloats); - mSurfaceControl.setPosition(tmpFloats[Matrix.MTRANS_X], tmpFloats[Matrix.MTRANS_Y]); - mSurfaceControl.setAlpha(mTransformation.getAlpha()); - mSurfaceControl.setMatrix(tmpFloats[Matrix.MSCALE_X], tmpFloats[Matrix.MSKEW_Y], - tmpFloats[Matrix.MSKEW_X], tmpFloats[Matrix.MSCALE_Y]); - return true; - } + private ValueAnimator createReturnAnimationLocked() { + final ValueAnimator animator = ValueAnimator.ofPropertyValuesHolder( + PropertyValuesHolder.ofFloat( + ANIMATED_PROPERTY_X, mCurrentX - mThumbOffsetX, + mOriginalX - mThumbOffsetX), + PropertyValuesHolder.ofFloat( + ANIMATED_PROPERTY_Y, mCurrentY - mThumbOffsetY, + mOriginalY - mThumbOffsetY), + PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_SCALE, 1, 1), + PropertyValuesHolder.ofFloat( + ANIMATED_PROPERTY_ALPHA, mOriginalAlpha, mOriginalAlpha / 2)); - private Animation createReturnAnimationLocked() { - final AnimationSet set = new AnimationSet(false); final float translateX = mOriginalX - mCurrentX; final float translateY = mOriginalY - mCurrentY; - set.addAnimation(new TranslateAnimation( 0, translateX, 0, translateY)); - set.addAnimation(new AlphaAnimation(mOriginalAlpha, mOriginalAlpha / 2)); // Adjust the duration to the travel distance. final double travelDistance = Math.sqrt(translateX * translateX + translateY * translateY); final double displayDiagonal = Math.sqrt(mDisplaySize.x * mDisplaySize.x + mDisplaySize.y * mDisplaySize.y); final long duration = MIN_ANIMATION_DURATION_MS + (long) (travelDistance / displayDiagonal * (MAX_ANIMATION_DURATION_MS - MIN_ANIMATION_DURATION_MS)); - set.setDuration(duration); - set.setInterpolator(mCubicEaseOutInterpolator); - set.initialize(0, 0, 0, 0); - set.start(); // Will start on the first call to getTransformation. - return set; + final AnimationListener listener = new AnimationListener(); + animator.setDuration(duration); + animator.setInterpolator(mCubicEaseOutInterpolator); + animator.addListener(listener); + animator.addUpdateListener(listener); + + mService.mAnimationHandler.post(() -> animator.start()); + return animator; } - private Animation createCancelAnimationLocked() { - final AnimationSet set = new AnimationSet(false); - set.addAnimation(new ScaleAnimation(1, 0, 1, 0, mThumbOffsetX, mThumbOffsetY)); - set.addAnimation(new AlphaAnimation(mOriginalAlpha, 0)); - set.setDuration(MIN_ANIMATION_DURATION_MS); - set.setInterpolator(mCubicEaseOutInterpolator); - set.initialize(0, 0, 0, 0); - set.start(); // Will start on the first call to getTransformation. - return set; + private ValueAnimator createCancelAnimationLocked() { + final ValueAnimator animator = ValueAnimator.ofPropertyValuesHolder( + PropertyValuesHolder.ofFloat( + ANIMATED_PROPERTY_X, mCurrentX - mThumbOffsetX, mCurrentX), + PropertyValuesHolder.ofFloat( + ANIMATED_PROPERTY_Y, mCurrentY - mThumbOffsetY, mCurrentY), + PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_SCALE, 1, 0), + PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_ALPHA, mOriginalAlpha, 0)); + final AnimationListener listener = new AnimationListener(); + animator.setDuration(MIN_ANIMATION_DURATION_MS); + animator.setInterpolator(mCubicEaseOutInterpolator); + animator.addListener(listener); + animator.addUpdateListener(listener); + + mService.mAnimationHandler.post(() -> animator.start()); + return animator; } private boolean isFromSource(int source) { @@ -622,4 +647,68 @@ class DragState { InputManager.getInstance().setPointerIconType(PointerIcon.TYPE_GRABBING); } } + + private class DragStateHandler extends Handler { + DragStateHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_ANIMATION_END: + synchronized (mService.mWindowMap) { + if (mService.mDragState != DragState.this) { + Slog.wtf(TAG_WM, "mDragState is updated unexpectedly while " + + "playing animation"); + return; + } + if (mAnimator == null) { + Slog.wtf(TAG_WM, "Unexpected null mAnimator"); + return; + } + mAnimator = null; + cleanUpDragLw(); + } + break; + } + } + } + + private class AnimationListener + implements ValueAnimator.AnimatorUpdateListener, Animator.AnimatorListener { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + try (final SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()) { + transaction.setPosition( + mSurfaceControl, + (float) mAnimator.getAnimatedValue(ANIMATED_PROPERTY_X), + (float) mAnimator.getAnimatedValue(ANIMATED_PROPERTY_Y)); + transaction.setAlpha( + mSurfaceControl, + (float) mAnimator.getAnimatedValue(ANIMATED_PROPERTY_ALPHA)); + transaction.setMatrix( + mSurfaceControl, + (float) mAnimator.getAnimatedValue(ANIMATED_PROPERTY_SCALE), 0, + 0, (float) mAnimator.getAnimatedValue(ANIMATED_PROPERTY_SCALE)); + transaction.apply(); + } + } + + @Override + public void onAnimationStart(Animator animator) {} + + @Override + public void onAnimationCancel(Animator animator) {} + + @Override + public void onAnimationRepeat(Animator animator) {} + + @Override + public void onAnimationEnd(Animator animator) { + // Updating mDragState requires the WM lock so continues it on the out of + // AnimationThread. + mHandler.sendEmptyMessage(MSG_ANIMATION_END); + } + } } diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java index 4dd147e53588..717c5774f9f3 100644 --- a/services/core/java/com/android/server/wm/Session.java +++ b/services/core/java/com/android/server/wm/Session.java @@ -68,9 +68,7 @@ import java.util.Set; * This class represents an active client session. There is generally one * Session object per process that is interacting with the window manager. */ -// Needs to be public and not final so we can mock during tests...sucks I know :( -public class Session extends IWindowSession.Stub - implements IBinder.DeathRecipient { +class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { final WindowManagerService mService; final IWindowSessionCallback mCallback; final IInputMethodClient mClient; @@ -83,6 +81,7 @@ public class Session extends IWindowSession.Stub private final Set<WindowSurfaceController> mAppOverlaySurfaces = new HashSet<>(); // Set of visible alert window surfaces connected to this session. private final Set<WindowSurfaceController> mAlertWindowSurfaces = new HashSet<>(); + private final DragDropController mDragDropController; final boolean mCanAddInternalSystemWindow; final boolean mCanHideNonSystemOverlayWindows; final boolean mCanAcquireSleepToken; @@ -108,6 +107,7 @@ public class Session extends IWindowSession.Stub mCanAcquireSleepToken = service.mContext.checkCallingOrSelfPermission(DEVICE_POWER) == PERMISSION_GRANTED; mShowingAlertWindowNotificationAllowed = mService.mShowAlertWindowNotifications; + mDragDropController = mService.mDragDropController; StringBuilder sb = new StringBuilder(); sb.append("Session{"); sb.append(Integer.toHexString(System.identityHashCode(this))); @@ -306,190 +306,68 @@ public class Session extends IWindowSession.Stub /* Drag/drop */ @Override - public IBinder prepareDrag(IWindow window, int flags, - int width, int height, Surface outSurface) { - return mService.prepareDragSurface(window, mSurfaceSession, flags, - width, height, outSurface); + public IBinder prepareDrag(IWindow window, int flags, int width, int height, + Surface outSurface) { + final int callerPid = Binder.getCallingPid(); + final int callerUid = Binder.getCallingUid(); + final long ident = Binder.clearCallingIdentity(); + try { + return mDragDropController.prepareDrag( + mService, mSurfaceSession, callerPid, callerUid, window, flags, width, height, + outSurface); + } finally { + Binder.restoreCallingIdentity(ident); + } } @Override public boolean performDrag(IWindow window, IBinder dragToken, int touchSource, float touchX, float touchY, float thumbCenterX, float thumbCenterY, ClipData data) { - if (DEBUG_DRAG) { - Slog.d(TAG_WM, "perform drag: win=" + window + " data=" + data); - } - - synchronized (mService.mWindowMap) { - if (mService.mDragState == null) { - Slog.w(TAG_WM, "No drag prepared"); - throw new IllegalStateException("performDrag() without prepareDrag()"); - } - - if (dragToken != mService.mDragState.mToken) { - Slog.w(TAG_WM, "Performing mismatched drag"); - throw new IllegalStateException("performDrag() does not match prepareDrag()"); - } - - WindowState callingWin = mService.windowForClientLocked(null, window, false); - if (callingWin == null) { - Slog.w(TAG_WM, "Bad requesting window " + window); - return false; // !!! TODO: throw here? - } - - // !!! TODO: if input is not still focused on the initiating window, fail - // the drag initiation (e.g. an alarm window popped up just as the application - // called performDrag() - - mService.mH.removeMessages(H.DRAG_START_TIMEOUT, window.asBinder()); - - // !!! TODO: extract the current touch (x, y) in screen coordinates. That - // will let us eliminate the (touchX,touchY) parameters from the API. - - // !!! FIXME: put all this heavy stuff onto the mH looper, as well as - // the actual drag event dispatch stuff in the dragstate - - final DisplayContent displayContent = callingWin.getDisplayContent(); - if (displayContent == null) { - return false; - } - Display display = displayContent.getDisplay(); - mService.mDragState.register(display); - if (!mService.mInputManager.transferTouchFocus(callingWin.mInputChannel, - mService.mDragState.getInputChannel())) { - Slog.e(TAG_WM, "Unable to transfer touch focus"); - mService.mDragState.unregister(); - mService.mDragState.reset(); - mService.mDragState = null; - return false; - } - - mService.mDragState.mDisplayContent = displayContent; - mService.mDragState.mData = data; - mService.mDragState.broadcastDragStartedLw(touchX, touchY); - mService.mDragState.overridePointerIconLw(touchSource); - - // remember the thumb offsets for later - mService.mDragState.mThumbOffsetX = thumbCenterX; - mService.mDragState.mThumbOffsetY = thumbCenterY; - - // Make the surface visible at the proper location - final SurfaceControl surfaceControl = mService.mDragState.mSurfaceControl; - if (SHOW_LIGHT_TRANSACTIONS) Slog.i( - TAG_WM, ">>> OPEN TRANSACTION performDrag"); - mService.openSurfaceTransaction(); - try { - surfaceControl.setPosition(touchX - thumbCenterX, - touchY - thumbCenterY); - surfaceControl.setLayer(mService.mDragState.getDragLayerLw()); - surfaceControl.setLayerStack(display.getLayerStack()); - surfaceControl.show(); - } finally { - mService.closeSurfaceTransaction(); - if (SHOW_LIGHT_TRANSACTIONS) Slog.i( - TAG_WM, "<<< CLOSE TRANSACTION performDrag"); - } - - mService.mDragState.notifyLocationLw(touchX, touchY); - } - - return true; // success! + return mDragDropController.performDrag(mService, window, dragToken, touchSource, + touchX, touchY, thumbCenterX, thumbCenterY, data); } @Override - public boolean startMovingTask(IWindow window, float startX, float startY) { - if (DEBUG_TASK_POSITIONING) Slog.d( - TAG_WM, "startMovingTask: {" + startX + "," + startY + "}"); - - long ident = Binder.clearCallingIdentity(); + public void reportDropResult(IWindow window, boolean consumed) { + final long ident = Binder.clearCallingIdentity(); try { - return mService.startMovingTask(window, startX, startY); + mDragDropController.reportDropResult(mService, window, consumed); } finally { Binder.restoreCallingIdentity(ident); } } @Override - public void reportDropResult(IWindow window, boolean consumed) { - IBinder token = window.asBinder(); - if (DEBUG_DRAG) { - Slog.d(TAG_WM, "Drop result=" + consumed + " reported by " + token); - } - - synchronized (mService.mWindowMap) { - long ident = Binder.clearCallingIdentity(); - try { - if (mService.mDragState == null) { - // Most likely the drop recipient ANRed and we ended the drag - // out from under it. Log the issue and move on. - Slog.w(TAG_WM, "Drop result given but no drag in progress"); - return; - } - - if (mService.mDragState.mToken != token) { - // We're in a drag, but the wrong window has responded. - Slog.w(TAG_WM, "Invalid drop-result claim by " + window); - throw new IllegalStateException("reportDropResult() by non-recipient"); - } - - // The right window has responded, even if it's no longer around, - // so be sure to halt the timeout even if the later WindowState - // lookup fails. - mService.mH.removeMessages(H.DRAG_END_TIMEOUT, window.asBinder()); - WindowState callingWin = mService.windowForClientLocked(null, window, false); - if (callingWin == null) { - Slog.w(TAG_WM, "Bad result-reporting window " + window); - return; // !!! TODO: throw here? - } - - mService.mDragState.mDragResult = consumed; - mService.mDragState.endDragLw(); - } finally { - Binder.restoreCallingIdentity(ident); - } - } - } - - @Override public void cancelDragAndDrop(IBinder dragToken) { - if (DEBUG_DRAG) { - Slog.d(TAG_WM, "cancelDragAndDrop"); - } - - synchronized (mService.mWindowMap) { - long ident = Binder.clearCallingIdentity(); - try { - if (mService.mDragState == null) { - Slog.w(TAG_WM, "cancelDragAndDrop() without prepareDrag()"); - throw new IllegalStateException("cancelDragAndDrop() without prepareDrag()"); - } - - if (mService.mDragState.mToken != dragToken) { - Slog.w(TAG_WM, - "cancelDragAndDrop() does not match prepareDrag()"); - throw new IllegalStateException( - "cancelDragAndDrop() does not match prepareDrag()"); - } - - mService.mDragState.mDragResult = false; - mService.mDragState.cancelDragLw(); - } finally { - Binder.restoreCallingIdentity(ident); - } + final long ident = Binder.clearCallingIdentity(); + try { + mDragDropController.cancelDragAndDrop(mService, dragToken); + } finally { + Binder.restoreCallingIdentity(ident); } } @Override public void dragRecipientEntered(IWindow window) { - if (DEBUG_DRAG) { - Slog.d(TAG_WM, "Drag into new candidate view @ " + window.asBinder()); - } + mDragDropController.dragRecipientEntered(window); } @Override public void dragRecipientExited(IWindow window) { - if (DEBUG_DRAG) { - Slog.d(TAG_WM, "Drag from old candidate view @ " + window.asBinder()); + mDragDropController.dragRecipientExited(window); + } + + @Override + public boolean startMovingTask(IWindow window, float startX, float startY) { + if (DEBUG_TASK_POSITIONING) Slog.d( + TAG_WM, "startMovingTask: {" + startX + "," + startY + "}"); + + long ident = Binder.clearCallingIdentity(); + try { + return mService.startMovingTask(window, startX, startY); + } finally { + Binder.restoreCallingIdentity(ident); } } diff --git a/services/core/java/com/android/server/wm/StackWindowController.java b/services/core/java/com/android/server/wm/StackWindowController.java index 1fda832dd5be..aff1bc62563f 100644 --- a/services/core/java/com/android/server/wm/StackWindowController.java +++ b/services/core/java/com/android/server/wm/StackWindowController.java @@ -152,7 +152,8 @@ public class StackWindowController } } - public void positionChildAtBottom(TaskWindowContainerController child) { + public void positionChildAtBottom(TaskWindowContainerController child, + boolean includingParents) { if (child == null) { // TODO: Fix the call-points that cause this to happen. return; @@ -164,7 +165,7 @@ public class StackWindowController Slog.e(TAG_WM, "positionChildAtBottom: task=" + child + " not found"); return; } - mContainer.positionChildAt(POSITION_BOTTOM, childTask, false /* includingParents */); + mContainer.positionChildAt(POSITION_BOTTOM, childTask, includingParents); if (mService.mAppTransition.isTransitionSet()) { childTask.setSendingToBottom(true); diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java index 5e5c6c5351af..791accf8f347 100644 --- a/services/core/java/com/android/server/wm/TaskStack.java +++ b/services/core/java/com/android/server/wm/TaskStack.java @@ -1008,10 +1008,13 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye void resetAdjustedForIme(boolean adjustBoundsNow) { if (adjustBoundsNow) { mImeWin = null; - mAdjustedForIme = false; mImeGoingAway = false; mAdjustImeAmount = 0f; mAdjustDividerAmount = 0f; + if (!mAdjustedForIme) { + return; + } + mAdjustedForIme = false; updateAdjustedBounds(); mService.setResizeDimLayer(false, getWindowingMode(), 1.0f); } else { diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java index c01ee31e8c1c..e409a68f2dfe 100644 --- a/services/core/java/com/android/server/wm/WindowAnimator.java +++ b/services/core/java/com/android/server/wm/WindowAnimator.java @@ -223,10 +223,6 @@ public class WindowAnimator { } } - if (mService.mDragState != null) { - mAnimating |= mService.mDragState.stepAnimationLocked(mCurrentTime); - } - if (!mAnimating) { cancelAnimation(); } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 01f2ccb46513..ad6fc39d91f8 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -357,8 +357,6 @@ public class WindowManagerService extends IWindowManager.Stub // trying to apply a new one. private static final boolean ALWAYS_KEEP_CURRENT = true; - private static final float DRAG_SHADOW_ALPHA_TRANSPARENT = .7071f; - // Enums for animation scale update types. @Retention(RetentionPolicy.SOURCE) @IntDef({WINDOW_ANIMATION_SCALE, TRANSITION_ANIMATION_SCALE, ANIMATION_DURATION_SCALE}) @@ -752,6 +750,7 @@ public class WindowManagerService extends IWindowManager.Stub boolean mAllowTheaterModeWakeFromLayout; TaskPositioner mTaskPositioner; + final DragDropController mDragDropController = new DragDropController(); DragState mDragState = null; // For frozen screen animations. @@ -4637,73 +4636,6 @@ public class WindowManagerService extends IWindowManager.Stub } // ------------------------------------------------------------- - // Drag and drop - // ------------------------------------------------------------- - - IBinder prepareDragSurface(IWindow window, SurfaceSession session, - int flags, int width, int height, Surface outSurface) { - if (DEBUG_DRAG) { - Slog.d(TAG_WM, "prepare drag surface: w=" + width + " h=" + height - + " flags=" + Integer.toHexString(flags) + " win=" + window - + " asbinder=" + window.asBinder()); - } - - final int callerPid = Binder.getCallingPid(); - final int callerUid = Binder.getCallingUid(); - final long origId = Binder.clearCallingIdentity(); - IBinder token = null; - - try { - synchronized (mWindowMap) { - try { - if (mDragState == null) { - // TODO(multi-display): support other displays - final DisplayContent displayContent = getDefaultDisplayContentLocked(); - final Display display = displayContent.getDisplay(); - - SurfaceControl surface = new SurfaceControl(session, "drag surface", - width, height, PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN); - surface.setLayerStack(display.getLayerStack()); - float alpha = 1; - if ((flags & View.DRAG_FLAG_OPAQUE) == 0) { - alpha = DRAG_SHADOW_ALPHA_TRANSPARENT; - } - surface.setAlpha(alpha); - - if (SHOW_TRANSACTIONS) Slog.i(TAG_WM, " DRAG " - + surface + ": CREATE"); - outSurface.copyFrom(surface); - final IBinder winBinder = window.asBinder(); - token = new Binder(); - mDragState = new DragState(this, token, surface, flags, winBinder); - mDragState.mPid = callerPid; - mDragState.mUid = callerUid; - mDragState.mOriginalAlpha = alpha; - token = mDragState.mToken = new Binder(); - - // 5 second timeout for this window to actually begin the drag - mH.removeMessages(H.DRAG_START_TIMEOUT, winBinder); - Message msg = mH.obtainMessage(H.DRAG_START_TIMEOUT, winBinder); - mH.sendMessageDelayed(msg, 5000); - } else { - Slog.w(TAG_WM, "Drag already in progress"); - } - } catch (OutOfResourcesException e) { - Slog.e(TAG_WM, "Can't allocate drag surface w=" + width + " h=" + height, e); - if (mDragState != null) { - mDragState.reset(); - mDragState = null; - } - } - } - } finally { - Binder.restoreCallingIdentity(origId); - } - - return token; - } - - // ------------------------------------------------------------- // Input Events and Focus Management // ------------------------------------------------------------- @@ -4866,6 +4798,7 @@ public class WindowManagerService extends IWindowManager.Stub public static final int REPORT_WINDOWS_CHANGE = 19; public static final int DRAG_START_TIMEOUT = 20; public static final int DRAG_END_TIMEOUT = 21; + public static final int REPORT_HARD_KEYBOARD_STATUS_CHANGE = 22; public static final int BOOT_TIMEOUT = 23; public static final int WAITING_FOR_DRAWN_TIMEOUT = 24; @@ -5120,47 +5053,12 @@ public class WindowManagerService extends IWindowManager.Stub break; } - case DRAG_START_TIMEOUT: { - IBinder win = (IBinder)msg.obj; - if (DEBUG_DRAG) { - Slog.w(TAG_WM, "Timeout starting drag by win " + win); - } - synchronized (mWindowMap) { - // !!! TODO: ANR the app that has failed to start the drag in time - if (mDragState != null) { - mDragState.unregister(); - mDragState.reset(); - mDragState = null; - } - } - break; - } - - case DRAG_END_TIMEOUT: { - IBinder win = (IBinder)msg.obj; - if (DEBUG_DRAG) { - Slog.w(TAG_WM, "Timeout ending drag to win " + win); - } - synchronized (mWindowMap) { - // !!! TODO: ANR the drag-receiving app - if (mDragState != null) { - mDragState.mDragResult = false; - mDragState.endDragLw(); - } - } - break; - } - + case DRAG_START_TIMEOUT: + case DRAG_END_TIMEOUT: case TEAR_DOWN_DRAG_AND_DROP_INPUT: { - if (DEBUG_DRAG) Slog.d(TAG_WM, "Drag ending; tearing down input channel"); - DragState.InputInterceptor interceptor = (DragState.InputInterceptor) msg.obj; - if (interceptor != null) { - synchronized (mWindowMap) { - interceptor.tearDown(); - } - } + mDragDropController.handleMessage(WindowManagerService.this, msg); + break; } - break; case REPORT_HARD_KEYBOARD_STATUS_CHANGE: { notifyHardKeyboardStatusChange(); @@ -7709,7 +7607,8 @@ public class WindowManagerService extends IWindowManager.Stub } boolean hasWideColorGamutSupport() { - return mHasWideColorGamutSupport; + return mHasWideColorGamutSupport && + !SystemProperties.getBoolean("persist.sys.sf.native_mode", false); } void updateNonSystemOverlayWindowsVisibilityIfNeeded(WindowState win, boolean surfaceShown) { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index c59f44e7e707..80f6a4b9f258 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -175,6 +175,7 @@ import com.android.internal.os.BackgroundThread; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.util.DumpUtils; import com.android.internal.util.FastXmlSerializer; +import com.android.internal.util.FunctionalUtils.ThrowingRunnable; import com.android.internal.util.JournaledFile; import com.android.internal.util.Preconditions; import com.android.internal.util.XmlUtils; @@ -1682,6 +1683,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return getCallingUid() == Process.myUid(); } + void binderWithCleanCallingIdentity(@NonNull ThrowingRunnable action) { + Binder.withCleanCallingIdentity(action); + } + final int userHandleGetCallingUserId() { return UserHandle.getUserId(binderGetCallingUid()); } @@ -9023,6 +9028,31 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } @Override + public boolean setTime(ComponentName who, long millis) { + Preconditions.checkNotNull(who, "ComponentName is null in setTime"); + getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); + // Don't allow set time when auto time is on. + if (mInjector.settingsGlobalGetInt(Global.AUTO_TIME, 0) == 1) { + return false; + } + mInjector.binderWithCleanCallingIdentity(() -> mInjector.getAlarmManager().setTime(millis)); + return true; + } + + @Override + public boolean setTimeZone(ComponentName who, String timeZone) { + Preconditions.checkNotNull(who, "ComponentName is null in setTimeZone"); + getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); + // Don't allow set timezone when auto timezone is on. + if (mInjector.settingsGlobalGetInt(Global.AUTO_TIME_ZONE, 0) == 1) { + return false; + } + mInjector.binderWithCleanCallingIdentity(() -> + mInjector.getAlarmManager().setTimeZone(timeZone)); + return true; + } + + @Override public void setSecureSetting(ComponentName who, String setting, String value) { Preconditions.checkNotNull(who, "ComponentName is null"); int callingUserId = mInjector.userHandleGetCallingUserId(); diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index d9db22e9b62d..b78fcddee119 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -1551,6 +1551,11 @@ public final class SystemServer { traceEnd(); } + // Statsd helper + traceBeginAndSlog("StartStatsCompanionService"); + mSystemServiceManager.startService(StatsCompanionService.Lifecycle.class); + traceEnd(); + // Before things start rolling, be sure we have decided whether // we are in safe mode. final boolean safeMode = wm.detectSafeMode(); diff --git a/services/net/java/android/net/util/VersionedBroadcastListener.java b/services/net/java/android/net/util/VersionedBroadcastListener.java new file mode 100644 index 000000000000..115aa464f4f1 --- /dev/null +++ b/services/net/java/android/net/util/VersionedBroadcastListener.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2017 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.net.util; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Handler; +import android.util.Log; + +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; + + +/** + * A utility class that runs the provided callback on the provided handler when + * intents matching the provided filter arrive. Intents received by a stale + * receiver are safely ignored. + * + * Calls to startListening() and stopListening() must happen on the same thread. + * + * @hide + */ +public class VersionedBroadcastListener { + private static final boolean DBG = false; + + public interface IntentCallback { + public void run(Intent intent); + } + + private final String mTag; + private final Context mContext; + private final Handler mHandler; + private final IntentFilter mFilter; + private final Consumer<Intent> mCallback; + private final AtomicInteger mGenerationNumber; + private BroadcastReceiver mReceiver; + + public VersionedBroadcastListener(String tag, Context ctx, Handler handler, + IntentFilter filter, Consumer<Intent> callback) { + mTag = tag; + mContext = ctx; + mHandler = handler; + mFilter = filter; + mCallback = callback; + mGenerationNumber = new AtomicInteger(0); + } + + public int generationNumber() { + return mGenerationNumber.get(); + } + + public void startListening() { + if (DBG) Log.d(mTag, "startListening"); + if (mReceiver != null) return; + + mReceiver = new Receiver(mTag, mGenerationNumber, mCallback); + mContext.registerReceiver(mReceiver, mFilter, null, mHandler); + } + + public void stopListening() { + if (DBG) Log.d(mTag, "stopListening"); + if (mReceiver == null) return; + + mGenerationNumber.incrementAndGet(); + mContext.unregisterReceiver(mReceiver); + mReceiver = null; + } + + private static class Receiver extends BroadcastReceiver { + public final String tag; + public final AtomicInteger atomicGenerationNumber; + public final Consumer<Intent> callback; + // Used to verify this receiver is still current. + public final int generationNumber; + + public Receiver( + String tag, AtomicInteger atomicGenerationNumber, Consumer<Intent> callback) { + this.tag = tag; + this.atomicGenerationNumber = atomicGenerationNumber; + this.callback = callback; + generationNumber = atomicGenerationNumber.incrementAndGet(); + } + + @Override + public void onReceive(Context context, Intent intent) { + final int currentGenerationNumber = atomicGenerationNumber.get(); + + if (DBG) { + Log.d(tag, "receiver generationNumber=" + generationNumber + + ", current generationNumber=" + currentGenerationNumber); + } + if (generationNumber != currentGenerationNumber) return; + + callback.accept(intent); + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java index 679be1d40b60..cb13e85157aa 100644 --- a/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java +++ b/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java @@ -51,13 +51,8 @@ import org.junit.Test; @Presubmit @RunWith(AndroidJUnit4.class) public class ActivityRecordTests extends ActivityTestsBase { - private final ComponentName testActivityComponent = - ComponentName.unflattenFromString("com.foo/.BarActivity"); - private final ComponentName secondaryActivityComponent = - ComponentName.unflattenFromString("com.foo/.BarActivity2"); - private ActivityManagerService mService; - private ActivityStack mStack; + private TestActivityStack mStack; private TaskRecord mTask; private ActivityRecord mActivity; @@ -69,20 +64,20 @@ public class ActivityRecordTests extends ActivityTestsBase { mService = createActivityManagerService(); mStack = mService.mStackSupervisor.getDefaultDisplay().createStack( WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - mTask = createTask(mService.mStackSupervisor, testActivityComponent, mStack); - mActivity = createActivity(mService, testActivityComponent, mTask); + mTask = new TaskBuilder(mService.mStackSupervisor).setStack(mStack).build(); + mActivity = new ActivityBuilder(mService).setTask(mTask).build(); } @Test public void testStackCleanupOnClearingTask() throws Exception { mActivity.setTask(null); - assertEquals(getActivityRemovedFromStackCount(), 1); + assertEquals(mStack.onActivityRemovedFromStackInvocationCount(), 1); } @Test public void testStackCleanupOnActivityRemoval() throws Exception { mTask.removeActivity(mActivity); - assertEquals(getActivityRemovedFromStackCount(), 1); + assertEquals(mStack.onActivityRemovedFromStackInvocationCount(), 1); } @Test @@ -94,10 +89,10 @@ public class ActivityRecordTests extends ActivityTestsBase { @Test public void testNoCleanupMovingActivityInSameStack() throws Exception { - final TaskRecord newTask = - createTask(mService.mStackSupervisor, testActivityComponent, mStack); + final TaskRecord newTask = new TaskBuilder(mService.mStackSupervisor).setStack(mStack) + .build(); mActivity.reparent(newTask, 0, null /*reason*/); - assertEquals(getActivityRemovedFromStackCount(), 0); + assertEquals(mStack.onActivityRemovedFromStackInvocationCount(), 0); } @Test @@ -129,15 +124,6 @@ public class ActivityRecordTests extends ActivityTestsBase { assertEquals(expectedActivityBounds, mActivity.getBounds()); } - private int getActivityRemovedFromStackCount() { - if (mStack instanceof ActivityStackReporter) { - return ((ActivityStackReporter) mStack).onActivityRemovedFromStackInvocationCount(); - } - - return -1; - } - - @Test public void testCanBeLaunchedOnDisplay() throws Exception { testSupportsLaunchingResizeable(false /*taskPresent*/, true /*taskResizeable*/, @@ -158,13 +144,13 @@ public class ActivityRecordTests extends ActivityTestsBase { mService.mSupportsMultiWindow = true; final TaskRecord task = taskPresent - ? createTask(mService.mStackSupervisor, testActivityComponent, mStack) : null; + ? new TaskBuilder(mService.mStackSupervisor).setStack(mStack).build() : null; if (task != null) { task.setResizeMode(taskResizeable ? RESIZE_MODE_RESIZEABLE : RESIZE_MODE_UNRESIZEABLE); } - final ActivityRecord record = createActivity(mService, secondaryActivityComponent, task); + final ActivityRecord record = new ActivityBuilder(mService).setTask(task).build(); record.info.resizeMode = activityResizeable ? RESIZE_MODE_RESIZEABLE : RESIZE_MODE_UNRESIZEABLE; diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java index 60a55fa941a7..fc17da843a9a 100644 --- a/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java +++ b/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java @@ -51,9 +51,6 @@ import static com.android.server.am.ActivityStackSupervisor.MATCH_TASK_IN_STACKS @Presubmit @RunWith(AndroidJUnit4.class) public class ActivityStackSupervisorTests extends ActivityTestsBase { - private final ComponentName testActivityComponent = - ComponentName.unflattenFromString("com.foo/.BarActivity"); - private ActivityManagerService mService; private ActivityStackSupervisor mSupervisor; private ActivityStack mFullscreenStack; @@ -87,15 +84,14 @@ public class ActivityStackSupervisorTests extends ActivityTestsBase { */ @Test public void testReplacingTaskInPinnedStack() throws Exception { - final TaskRecord firstTask = createTask( - mSupervisor, testActivityComponent, mFullscreenStack); - final ActivityRecord firstActivity = createActivity(mService, testActivityComponent, - firstTask); - // Create a new task on the full screen stack - final TaskRecord secondTask = createTask( - mSupervisor, testActivityComponent, mFullscreenStack); - final ActivityRecord secondActivity = createActivity(mService, testActivityComponent, - secondTask); + final ActivityRecord firstActivity = new ActivityBuilder(mService).setCreateTask(true) + .setStack(mFullscreenStack).build(); + final TaskRecord firstTask = firstActivity.getTask(); + + final ActivityRecord secondActivity = new ActivityBuilder(mService).setCreateTask(true) + .setStack(mFullscreenStack).build(); + final TaskRecord secondTask = secondActivity.getTask(); + mSupervisor.setFocusStackUnchecked("testReplacingTaskInPinnedStack", mFullscreenStack); // Ensure full screen stack has both tasks. @@ -104,7 +100,7 @@ public class ActivityStackSupervisorTests extends ActivityTestsBase { // Move first activity to pinned stack. final Rect sourceBounds = new Rect(); mSupervisor.moveActivityToPinnedStackLocked(firstActivity, sourceBounds, - 0f /*aspectRatio*/, false, "initialMove"); + 0f /*aspectRatio*/, "initialMove"); final ActivityDisplay display = mFullscreenStack.getDisplay(); ActivityStack pinnedStack = display.getPinnedStack(); @@ -114,7 +110,7 @@ public class ActivityStackSupervisorTests extends ActivityTestsBase { // Move second activity to pinned stack. mSupervisor.moveActivityToPinnedStackLocked(secondActivity, sourceBounds, - 0f /*aspectRatio*/, false, "secondMove"); + 0f /*aspectRatio*/, "secondMove"); // Need to get stacks again as a new instance might have been created. pinnedStack = display.getPinnedStack(); @@ -142,10 +138,8 @@ public class ActivityStackSupervisorTests extends ActivityTestsBase { */ @Test public void testStoppingActivityRemovedWhenResumed() throws Exception { - final TaskRecord firstTask = createTask( - mSupervisor, testActivityComponent, mFullscreenStack); - final ActivityRecord firstActivity = createActivity(mService, testActivityComponent, - firstTask); + final ActivityRecord firstActivity = new ActivityBuilder(mService).setCreateTask(true) + .setStack(mFullscreenStack).build(); mSupervisor.mStoppingActivities.add(firstActivity); firstActivity.completeResumeLocked(); diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java index 4ee1f47d02e0..480b210c17b8 100644 --- a/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java +++ b/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java @@ -16,13 +16,21 @@ package com.android.server.am; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; +import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; +import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; + import static com.android.server.am.ActivityStack.REMOVE_TASK_MODE_DESTROYING; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import android.content.ComponentName; import android.content.pm.ActivityInfo; @@ -45,12 +53,6 @@ import org.junit.Test; @Presubmit @RunWith(AndroidJUnit4.class) public class ActivityStackTests extends ActivityTestsBase { - private static final int TEST_STACK_ID = 100; - private static final ComponentName testActivityComponent = - ComponentName.unflattenFromString("com.foo/.BarActivity"); - private static final ComponentName testOverlayComponent = - ComponentName.unflattenFromString("com.foo/.OverlayActivity"); - private ActivityManagerService mService; private ActivityStackSupervisor mSupervisor; private ActivityStack mStack; @@ -65,7 +67,7 @@ public class ActivityStackTests extends ActivityTestsBase { mSupervisor = mService.mStackSupervisor; mStack = mService.mStackSupervisor.getDefaultDisplay().createStack( WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - mTask = createTask(mSupervisor, testActivityComponent, mStack); + mTask = new TaskBuilder(mSupervisor).setStack(mStack).build(); } @Test @@ -77,7 +79,7 @@ public class ActivityStackTests extends ActivityTestsBase { @Test public void testOccupiedTaskCleanupOnRemove() throws Exception { - final ActivityRecord r = createActivity(mService, testActivityComponent, mTask); + final ActivityRecord r = new ActivityBuilder(mService).setTask(mTask).build(); assertNotNull(mTask.getWindowContainerController()); mStack.removeTask(mTask, "testOccupiedTaskCleanupOnRemove", REMOVE_TASK_MODE_DESTROYING); assertNotNull(mTask.getWindowContainerController()); @@ -85,7 +87,7 @@ public class ActivityStackTests extends ActivityTestsBase { @Test public void testNoPauseDuringResumeTopActivity() throws Exception { - final ActivityRecord r = createActivity(mService, testActivityComponent, mTask); + final ActivityRecord r = new ActivityBuilder(mService).setTask(mTask).build(); // Simulate the a resumed activity set during // {@link ActivityStack#resumeTopActivityUncheckedLocked}. @@ -103,7 +105,7 @@ public class ActivityStackTests extends ActivityTestsBase { @Test public void testStopActivityWhenActivityDestroyed() throws Exception { - final ActivityRecord r = createActivity(mService, testActivityComponent, mTask); + final ActivityRecord r = new ActivityBuilder(mService).setTask(mTask).build(); r.info.flags |= ActivityInfo.FLAG_NO_HISTORY; mSupervisor.setFocusStackUnchecked("testStopActivityWithDestroy", mStack); mStack.stopActivityLocked(r); @@ -113,18 +115,140 @@ public class ActivityStackTests extends ActivityTestsBase { @Test public void testFindTaskWithOverlay() throws Exception { - final ActivityRecord r = createActivity(mService, testActivityComponent, mTask, 0); + final ActivityRecord r = new ActivityBuilder(mService) + .setCreateTask(true) + .setStack(mStack) + .setUid(0) + .build(); + final TaskRecord task = r.getTask(); // Overlay must be for a different user to prevent recognizing a matching top activity - final ActivityRecord taskOverlay = createActivity(mService, testOverlayComponent, mTask, - UserHandle.PER_USER_RANGE * 2); + final ActivityRecord taskOverlay = new ActivityBuilder(mService).setTask(task) + .setUid(UserHandle.PER_USER_RANGE * 2).build(); taskOverlay.mTaskOverlay = true; final ActivityStackSupervisor.FindTaskResult result = new ActivityStackSupervisor.FindTaskResult(); mStack.findTaskLocked(r, result); - assertEquals(mTask.getTopActivity(false /* includeOverlays */), r); - assertEquals(mTask.getTopActivity(true /* includeOverlays */), taskOverlay); + assertEquals(task.getTopActivity(false /* includeOverlays */), r); + assertEquals(task.getTopActivity(true /* includeOverlays */), taskOverlay); assertNotNull(result.r); } + + @Test + public void testShouldBeVisible_Fullscreen() throws Exception { + final ActivityDisplay display = mService.mStackSupervisor.getDefaultDisplay(); + final TestActivityStack homeStack = createStackForShouldBeVisibleTest(display, + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); + final ActivityStack pinnedStack = createStackForShouldBeVisibleTest(display, + WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */); + + assertTrue(homeStack.shouldBeVisible(null /* starting */)); + assertTrue(pinnedStack.shouldBeVisible(null /* starting */)); + + final TestActivityStack fullscreenStack = createStackForShouldBeVisibleTest(display, + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); + // Home stack shouldn't be visible behind an opaque fullscreen stack, but pinned stack + // should be visible since it is always on-top. + fullscreenStack.setIsTranslucent(false); + assertFalse(homeStack.shouldBeVisible(null /* starting */)); + assertTrue(pinnedStack.shouldBeVisible(null /* starting */)); + assertTrue(fullscreenStack.shouldBeVisible(null /* starting */)); + + // Home stack should be visible behind a translucent fullscreen stack. + fullscreenStack.setIsTranslucent(true); + assertTrue(homeStack.shouldBeVisible(null /* starting */)); + assertTrue(pinnedStack.shouldBeVisible(null /* starting */)); + } + + @Test + public void testShouldBeVisible_SplitScreen() throws Exception { + final ActivityDisplay display = mService.mStackSupervisor.getDefaultDisplay(); + final TestActivityStack homeStack = createStackForShouldBeVisibleTest(display, + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); + final TestActivityStack splitScreenPrimary = createStackForShouldBeVisibleTest(display, + WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, true /* onTop */); + final TestActivityStack splitScreenSecondary = createStackForShouldBeVisibleTest(display, + WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD, true /* onTop */); + + // Home stack shouldn't be visible if both halves of split-screen are opaque. + splitScreenPrimary.setIsTranslucent(false); + splitScreenSecondary.setIsTranslucent(false); + assertFalse(homeStack.shouldBeVisible(null /* starting */)); + assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */)); + assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */)); + + // Home stack should be visible if one of the halves of split-screen is translucent. + splitScreenPrimary.setIsTranslucent(true); + assertTrue(homeStack.shouldBeVisible(null /* starting */)); + assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */)); + assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */)); + + final TestActivityStack splitScreenSecondary2 = createStackForShouldBeVisibleTest(display, + WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD, true /* onTop */); + // First split-screen secondary shouldn't be visible behind another opaque split-split + // secondary. + splitScreenSecondary2.setIsTranslucent(false); + assertFalse(splitScreenSecondary.shouldBeVisible(null /* starting */)); + assertTrue(splitScreenSecondary2.shouldBeVisible(null /* starting */)); + + // First split-screen secondary should be visible behind another translucent split-split + // secondary. + splitScreenSecondary2.setIsTranslucent(true); + assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */)); + assertTrue(splitScreenSecondary2.shouldBeVisible(null /* starting */)); + + final TestActivityStack assistantStack = createStackForShouldBeVisibleTest(display, + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_ASSISTANT, true /* onTop */); + + // Split-screen stacks shouldn't be visible behind an opaque fullscreen stack. + assistantStack.setIsTranslucent(false); + assertTrue(assistantStack.shouldBeVisible(null /* starting */)); + assertFalse(splitScreenPrimary.shouldBeVisible(null /* starting */)); + assertFalse(splitScreenSecondary.shouldBeVisible(null /* starting */)); + assertFalse(splitScreenSecondary2.shouldBeVisible(null /* starting */)); + + // Split-screen stacks should be visible behind a translucent fullscreen stack. + assistantStack.setIsTranslucent(true); + assertTrue(assistantStack.shouldBeVisible(null /* starting */)); + assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */)); + assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */)); + assertTrue(splitScreenSecondary2.shouldBeVisible(null /* starting */)); + } + + @Test + public void testShouldBeVisible_Finishing() throws Exception { + final ActivityDisplay display = mService.mStackSupervisor.getDefaultDisplay(); + final TestActivityStack homeStack = createStackForShouldBeVisibleTest(display, + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); + final TestActivityStack translucentStack = createStackForShouldBeVisibleTest(display, + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); + translucentStack.setIsTranslucent(true); + + assertTrue(homeStack.shouldBeVisible(null /* starting */)); + assertTrue(translucentStack.shouldBeVisible(null /* starting */)); + + final ActivityRecord topRunningHomeActivity = homeStack.topRunningActivityLocked(); + topRunningHomeActivity.finishing = true; + final ActivityRecord topRunningTranslucentActivity = + translucentStack.topRunningActivityLocked(); + topRunningTranslucentActivity.finishing = true; + + // Home shouldn't be visible since its activity is marked as finishing and it isn't the top + // of the stack list. + assertFalse(homeStack.shouldBeVisible(null /* starting */)); + // Home should be visible if we are starting an activity within it. + assertTrue(homeStack.shouldBeVisible(topRunningHomeActivity /* starting */)); + // The translucent stack should be visible since it is the top of the stack list even though + // it has its activity marked as finishing. + assertTrue(translucentStack.shouldBeVisible(null /* starting */)); + } + + private <T extends ActivityStack> T createStackForShouldBeVisibleTest( + ActivityDisplay display, int windowingMode, int activityType, boolean onTop) { + final T stack = display.createStack(windowingMode, activityType, onTop); + final ActivityRecord r = new ActivityBuilder(mService).setUid(0).setStack(stack) + .setCreateTask(true).build(); + return stack; + } } diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java index 026abce42b7d..5b1e4b731d38 100644 --- a/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java +++ b/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java @@ -50,9 +50,6 @@ import static org.mockito.Mockito.times; @Presubmit @RunWith(AndroidJUnit4.class) public class ActivityStarterTests extends ActivityTestsBase { - private static final ComponentName testActivityComponent = - ComponentName.unflattenFromString("com.foo/.BarActivity"); - private ActivityManagerService mService; private ActivityStarter mStarter; @@ -66,9 +63,10 @@ public class ActivityStarterTests extends ActivityTestsBase { @Test public void testUpdateLaunchBounds() throws Exception { // When in a non-resizeable stack, the task bounds should be updated. - final TaskRecord task = createTask(mService.mStackSupervisor, testActivityComponent, - mService.mStackSupervisor.getDefaultDisplay().createStack( - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */)); + final TaskRecord task = new TaskBuilder(mService.mStackSupervisor) + .setStack(mService.mStackSupervisor.getDefaultDisplay().createStack( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */)) + .build(); final Rect bounds = new Rect(10, 10, 100, 100); mStarter.updateBounds(task, bounds); @@ -76,9 +74,10 @@ public class ActivityStarterTests extends ActivityTestsBase { assertEquals(task.getStack().mBounds, null); // When in a resizeable stack, the stack bounds should be updated as well. - final TaskRecord task2 = createTask(mService.mStackSupervisor, testActivityComponent, - mService.mStackSupervisor.getDefaultDisplay().createStack( - WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */)); + final TaskRecord task2 = new TaskBuilder(mService.mStackSupervisor) + .setStack(mService.mStackSupervisor.getDefaultDisplay().createStack( + WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */)) + .build(); assertTrue(task2.getStack() instanceof PinnedActivityStack); mStarter.updateBounds(task2, bounds); diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java index 20077f3e94b0..3d5d87c1359a 100644 --- a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java +++ b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java @@ -16,6 +16,8 @@ package com.android.server.am; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.view.Display.DEFAULT_DISPLAY; import static org.mockito.Mockito.mock; @@ -52,12 +54,20 @@ import org.mockito.MockitoAnnotations; * A base class to handle common operations in activity related unit tests. */ public class ActivityTestsBase { + private static boolean sOneTimeSetupDone = false; + private final Context mContext = InstrumentationRegistry.getContext(); private HandlerThread mHandlerThread; @Before public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); + if (!sOneTimeSetupDone) { + sOneTimeSetupDone = true; + + // Allows to mock package local classes and methods + System.setProperty("dexmaker.share_classloader", "true"); + MockitoAnnotations.initMocks(this); + } mHandlerThread = new HandlerThread("ActivityTestsBaseThread"); mHandlerThread.start(); } @@ -73,57 +83,156 @@ public class ActivityTestsBase { return service; } - protected static ActivityRecord createActivity(ActivityManagerService service, - ComponentName component, TaskRecord task) { - return createActivity(service, component, task, 0 /* userId */); - } + /** + * Builder for creating new activities. + */ + protected static class ActivityBuilder { + // An id appended to the end of the component name to make it unique + private static int sCurrentActivityId = 0; - protected static ActivityRecord createActivity(ActivityManagerService service, - ComponentName component, TaskRecord task, int uid) { - Intent intent = new Intent(); - intent.setComponent(component); - final ActivityInfo aInfo = new ActivityInfo(); - aInfo.applicationInfo = new ApplicationInfo(); - aInfo.applicationInfo.packageName = component.getPackageName(); - aInfo.applicationInfo.uid = uid; - AttributeCache.init(service.mContext); - final ActivityRecord activity = new ActivityRecord(service, null /* caller */, - 0 /* launchedFromPid */, 0, null, intent, null, - aInfo /*aInfo*/, new Configuration(), null /* resultTo */, null /* resultWho */, - 0 /* reqCode */, false /*componentSpecified*/, false /* rootVoiceInteraction */, - service.mStackSupervisor, null /* options */, null /* sourceRecord */); - activity.mWindowContainerController = mock(AppWindowContainerController.class); - - if (task != null) { - task.addActivityToTop(activity); - } - - return activity; - } + // Default package name + private static final String DEFAULT_PACKAGE = "com.foo"; + + // Default base activity name + private static final String DEFAULT_BASE_ACTIVITY_NAME = ".BarActivity"; + + private final ActivityManagerService mService; + + private ComponentName mComponent; + private TaskRecord mTaskRecord; + private int mUid; + private boolean mCreateTask; + private ActivityStack mStack; + + ActivityBuilder(ActivityManagerService service) { + mService = service; + } + + ActivityBuilder setComponent(ComponentName component) { + mComponent = component; + return this; + } + + ActivityBuilder setTask(TaskRecord task) { + mTaskRecord = task; + return this; + } - protected static TaskRecord createTask(ActivityStackSupervisor supervisor, - ComponentName component, ActivityStack stack) { - return createTask(supervisor, component, 0 /* flags */, 0 /* taskId */, stack); + ActivityBuilder setStack(ActivityStack stack) { + mStack = stack; + return this; + } + + ActivityBuilder setCreateTask(boolean createTask) { + mCreateTask = createTask; + return this; + } + + ActivityBuilder setUid(int uid) { + mUid = uid; + return this; + } + + ActivityRecord build() { + if (mComponent == null) { + final int id = sCurrentActivityId++; + mComponent = ComponentName.createRelative(DEFAULT_PACKAGE, + DEFAULT_BASE_ACTIVITY_NAME + id); + } + + if (mCreateTask) { + mTaskRecord = new TaskBuilder(mService.mStackSupervisor) + .setComponent(mComponent) + .setStack(mStack).build(); + } + + Intent intent = new Intent(); + intent.setComponent(mComponent); + final ActivityInfo aInfo = new ActivityInfo(); + aInfo.applicationInfo = new ApplicationInfo(); + aInfo.applicationInfo.packageName = mComponent.getPackageName(); + aInfo.applicationInfo.uid = mUid; + AttributeCache.init(mService.mContext); + final ActivityRecord activity = new ActivityRecord(mService, null /* caller */, + 0 /* launchedFromPid */, 0, null, intent, null, + aInfo /*aInfo*/, new Configuration(), null /* resultTo */, null /* resultWho */, + 0 /* reqCode */, false /*componentSpecified*/, false /* rootVoiceInteraction */, + mService.mStackSupervisor, null /* options */, null /* sourceRecord */); + activity.mWindowContainerController = mock(AppWindowContainerController.class); + + if (mTaskRecord != null) { + mTaskRecord.addActivityToTop(activity); + } + + return activity; + } } - protected static TaskRecord createTask(ActivityStackSupervisor supervisor, - ComponentName component, int flags, int taskId, ActivityStack stack) { - final ActivityInfo aInfo = new ActivityInfo(); - aInfo.applicationInfo = new ApplicationInfo(); - aInfo.applicationInfo.packageName = component.getPackageName(); + /** + * Builder for creating new tasks. + */ + protected static class TaskBuilder { + private final ActivityStackSupervisor mSupervisor; + + private ComponentName mComponent; + private String mPackage; + private int mFlags = 0; + private int mTaskId = 0; + + private ActivityStack mStack; + + TaskBuilder(ActivityStackSupervisor supervisor) { + mSupervisor = supervisor; + } + + TaskBuilder setComponent(ComponentName component) { + mComponent = component; + return this; + } + + TaskBuilder setPackage(String packageName) { + mPackage = packageName; + return this; + } + + TaskBuilder setFlags(int flags) { + mFlags = flags; + return this; + } + + TaskBuilder setTaskId(int taskId) { + mTaskId = taskId; + return this; + } + + TaskBuilder setStack(ActivityStack stack) { + mStack = stack; + return this; + } + + TaskRecord build() { + if (mStack == null) { + mStack = mSupervisor.getDefaultDisplay().createStack( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); + } + + final ActivityInfo aInfo = new ActivityInfo(); + aInfo.applicationInfo = new ApplicationInfo(); + aInfo.applicationInfo.packageName = mPackage; - Intent intent = new Intent(); - intent.setComponent(component); - intent.setFlags(flags); + Intent intent = new Intent(); + intent.setComponent(mComponent); + intent.setFlags(mFlags); - final TaskRecord task = new TaskRecord(supervisor.mService, taskId, aInfo, - intent /*intent*/, null /*_taskDescription*/); - supervisor.setFocusStackUnchecked("test", stack); - stack.addTask(task, true, "creating test task"); - task.setStack(stack); - task.setWindowContainerController(mock(TaskWindowContainerController.class)); + final TaskRecord task = new TaskRecord(mSupervisor.mService, mTaskId, aInfo, + intent /*intent*/, null /*_taskDescription*/); + mSupervisor.setFocusStackUnchecked("test", mStack); + mStack.addTask(task, true, "creating test task"); + task.setStack(mStack); + task.setWindowContainerController(mock(TaskWindowContainerController.class)); - return task; + return task; + } } /** @@ -268,20 +377,21 @@ public class ActivityTestsBase { return service; } - protected interface ActivityStackReporter { - int onActivityRemovedFromStackInvocationCount(); - } - /** * Overrided of {@link ActivityStack} that tracks test metrics, such as the number of times a * method is called. Note that its functionality depends on the implementations of the * construction arguments. */ protected static class TestActivityStack<T extends StackWindowController> - extends ActivityStack<T> implements ActivityStackReporter { + extends ActivityStack<T> { private int mOnActivityRemovedFromStackCount = 0; private T mContainerController; + static final int IS_TRANSLUCENT_UNSET = 0; + static final int IS_TRANSLUCENT_FALSE = 1; + static final int IS_TRANSLUCENT_TRUE = 2; + private int mIsTranslucent = IS_TRANSLUCENT_UNSET; + TestActivityStack(ActivityDisplay display, int stackId, ActivityStackSupervisor supervisor, int windowingMode, int activityType, boolean onTop) { super(display, stackId, supervisor, windowingMode, activityType, onTop); @@ -294,8 +404,7 @@ public class ActivityTestsBase { } // Returns the number of times {@link #onActivityRemovedFromStack} has been called - @Override - public int onActivityRemovedFromStackInvocationCount() { + int onActivityRemovedFromStackInvocationCount() { return mOnActivityRemovedFromStackCount; } @@ -309,5 +418,22 @@ public class ActivityTestsBase { T getWindowContainerController() { return mContainerController; } + + void setIsTranslucent(boolean isTranslucent) { + mIsTranslucent = isTranslucent ? IS_TRANSLUCENT_TRUE : IS_TRANSLUCENT_FALSE; + } + + @Override + boolean isStackTranslucent(ActivityRecord starting) { + switch (mIsTranslucent) { + case IS_TRANSLUCENT_TRUE: + return true; + case IS_TRANSLUCENT_FALSE: + return false; + case IS_TRANSLUCENT_UNSET: + default: + return super.isStackTranslucent(starting); + } + } } } diff --git a/services/tests/servicestests/src/com/android/server/am/LaunchingActivityPositionerTests.java b/services/tests/servicestests/src/com/android/server/am/LaunchingActivityPositionerTests.java new file mode 100644 index 000000000000..de831a09c2ae --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/am/LaunchingActivityPositionerTests.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2017 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.server.am; + +import android.app.ActivityOptions; +import android.content.ComponentName; +import android.content.pm.ActivityInfo; +import android.graphics.Rect; +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.MediumTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.runner.RunWith; +import org.junit.Before; +import org.junit.Test; + +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static com.android.server.am.LaunchingBoundsController.LaunchingBoundsPositioner.RESULT_DONE; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.doAnswer; + +import static com.android.server.am.LaunchingBoundsController.LaunchingBoundsPositioner.RESULT_SKIP; + +/** + * Tests for exercising resizing bounds due to activity options. + * + * Build/Install/Run: + * bit FrameworksServicesTests:com.android.server.am.LaunchingActivityPositionerTests + */ +@MediumTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class LaunchingActivityPositionerTests extends ActivityTestsBase { + private LaunchingActivityPositioner mPositioner; + private ActivityManagerService mService; + private ActivityStack mStack; + private TaskRecord mTask; + private ActivityRecord mActivity; + + private Rect mCurrent; + private Rect mResult; + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + mService = createActivityManagerService(); + mPositioner = new LaunchingActivityPositioner(mService.mStackSupervisor); + mCurrent = new Rect(); + mResult = new Rect(); + + + mStack = mService.mStackSupervisor.getDefaultDisplay().createStack( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); + mTask = new TaskBuilder(mService.mStackSupervisor).setStack(mStack).build(); + mActivity = new ActivityBuilder(mService).setTask(mTask).build(); + } + + + @Test + public void testSkippedInvocations() throws Exception { + // No specified activity should be ignored + assertEquals(RESULT_SKIP, mPositioner.onCalculateBounds(null /*task*/, null /*layout*/, + null /*activity*/, null /*options*/, mCurrent, mResult)); + + // No specified activity options should be ignored + assertEquals(RESULT_SKIP, mPositioner.onCalculateBounds(null /*task*/, null /*layout*/, + mActivity, null /*options*/, mCurrent, mResult)); + + // launch bounds specified should be ignored. + final ActivityOptions options = ActivityOptions.makeBasic(); + assertEquals(RESULT_SKIP, mPositioner.onCalculateBounds(null /*task*/, null /*layout*/, + mActivity, options /*options*/, mCurrent, mResult)); + + // Non-resizeable records should be ignored + mActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_UNRESIZEABLE; + assertFalse(mActivity.isResizeable()); + assertEquals(RESULT_SKIP, mPositioner.onCalculateBounds(null /*task*/, null /*layout*/, + mActivity, options /*options*/, mCurrent, mResult)); + + // make record resizeable + mActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_RESIZEABLE; + assertTrue(mActivity.isResizeable()); + + assertEquals(RESULT_SKIP, mPositioner.onCalculateBounds(null /*task*/, null /*layout*/, + mActivity, options /*options*/, mCurrent, mResult)); + + // Does not support freeform + mService.mSupportsFreeformWindowManagement = false; + assertFalse(mService.mStackSupervisor.canUseActivityOptionsLaunchBounds(options)); + assertEquals(RESULT_SKIP, mPositioner.onCalculateBounds(null /*task*/, null /*layout*/, + mActivity, options /*options*/, mCurrent, mResult)); + + mService.mSupportsFreeformWindowManagement = true; + options.setLaunchBounds(new Rect()); + assertTrue(mService.mStackSupervisor.canUseActivityOptionsLaunchBounds(options)); + + // Invalid bounds + assertEquals(RESULT_SKIP, mPositioner.onCalculateBounds(null /*task*/, null /*layout*/, + mActivity, options /*options*/, mCurrent, mResult)); + options.setLaunchBounds(new Rect(0, 0, -1, -1)); + assertEquals(RESULT_SKIP, mPositioner.onCalculateBounds(null /*task*/, null /*layout*/, + mActivity, options /*options*/, mCurrent, mResult)); + + // Valid bounds should cause the positioner to be applied. + options.setLaunchBounds(new Rect(0, 0, 100, 100)); + assertEquals(RESULT_DONE, mPositioner.onCalculateBounds(null /*task*/, null /*layout*/, + mActivity, options /*options*/, mCurrent, mResult)); + } + + @Test + public void testBoundsExtraction() throws Exception { + // Make activity resizeable and enable freeform mode. + mActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_RESIZEABLE; + mService.mSupportsFreeformWindowManagement = true; + + ActivityOptions options = ActivityOptions.makeBasic(); + final Rect proposedBounds = new Rect(20, 30, 45, 40); + options.setLaunchBounds(proposedBounds); + + assertEquals(RESULT_DONE, mPositioner.onCalculateBounds(null /*task*/, null /*layout*/, + mActivity, options /*options*/, mCurrent, mResult)); + assertEquals(mResult, proposedBounds); + } +} diff --git a/services/tests/servicestests/src/com/android/server/am/LaunchingBoundsControllerTests.java b/services/tests/servicestests/src/com/android/server/am/LaunchingBoundsControllerTests.java new file mode 100644 index 000000000000..f24a273d66d1 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/am/LaunchingBoundsControllerTests.java @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2017 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.server.am; + +import android.app.ActivityOptions; +import android.content.pm.ActivityInfo; +import android.graphics.Rect; +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.MediumTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.runner.RunWith; +import org.junit.Before; +import org.junit.Test; + +import com.android.server.am.LaunchingBoundsController.LaunchingBoundsPositioner; + +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import static com.android.server.am.LaunchingBoundsController.LaunchingBoundsPositioner.RESULT_DONE; +import static com.android.server.am.LaunchingBoundsController.LaunchingBoundsPositioner.RESULT_CONTINUE; +import static com.android.server.am.LaunchingBoundsController.LaunchingBoundsPositioner.RESULT_SKIP; + +import static org.junit.Assert.assertEquals; + +/** + * Tests for exercising {@link LaunchingBoundsController}. + * + * Build/Install/Run: + * bit FrameworksServicesTests:com.android.server.am.LaunchingBoundsControllerTests + */ +@MediumTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class LaunchingBoundsControllerTests extends ActivityTestsBase { + private LaunchingBoundsController mController; + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + mController = new LaunchingBoundsController(); + } + + /** + * Ensures positioners further down the chain are not called when RESULT_DONE is returned. + */ + @Test + public void testEarlyExit() { + final LaunchingBoundsPositioner ignoredPositioner = mock(LaunchingBoundsPositioner.class); + final LaunchingBoundsPositioner earlyExitPositioner = + (task, layout, activity, options, current, result) -> RESULT_DONE; + + mController.registerPositioner(ignoredPositioner); + mController.registerPositioner(earlyExitPositioner); + + mController.calculateBounds(null /*task*/, null /*layout*/, null /*activity*/, + null /*options*/, new Rect()); + verify(ignoredPositioner, never()).onCalculateBounds(any(), any(), any(), any(), any(), + any()); + } + + /** + * Ensures that positioners are called in the correct order. + */ + @Test + public void testRegistration() { + LaunchingBoundsPositioner earlyExitPositioner = + new InstrumentedPositioner(RESULT_DONE, new Rect()); + + final LaunchingBoundsPositioner firstPositioner = spy(earlyExitPositioner); + + mController.registerPositioner(firstPositioner); + + mController.calculateBounds(null /*task*/, null /*layout*/, null /*activity*/, + null /*options*/, new Rect()); + verify(firstPositioner, times(1)).onCalculateBounds(any(), any(), any(), any(), any(), + any()); + + final LaunchingBoundsPositioner secondPositioner = spy(earlyExitPositioner); + + mController.registerPositioner(secondPositioner); + + mController.calculateBounds(null /*task*/, null /*layout*/, null /*activity*/, + null /*options*/, new Rect()); + verify(firstPositioner, times(1)).onCalculateBounds(any(), any(), any(), any(), any(), + any()); + verify(secondPositioner, times(1)).onCalculateBounds(any(), any(), any(), any(), any(), + any()); + } + + /** + * Makes sure positioners further down the registration chain are called. + */ + @Test + public void testPassThrough() { + final LaunchingBoundsPositioner positioner1 = mock(LaunchingBoundsPositioner.class); + final InstrumentedPositioner positioner2 = new InstrumentedPositioner(RESULT_CONTINUE, + new Rect (0, 0, 30, 20)); + + mController.registerPositioner(positioner1); + mController.registerPositioner(positioner2); + + mController.calculateBounds(null /*task*/, null /*layout*/, null /*activity*/, + null /*options*/, new Rect()); + + verify(positioner1, times(1)).onCalculateBounds(any(), any(), any(), any(), + eq(positioner2.getLaunchBounds()), any()); + } + + /** + * Ensures skipped results are not propagated. + */ + @Test + public void testSkip() { + final InstrumentedPositioner positioner1 = + new InstrumentedPositioner(RESULT_SKIP, new Rect(0, 0, 10, 10)); + + + final InstrumentedPositioner positioner2 = + new InstrumentedPositioner(RESULT_CONTINUE, new Rect(0, 0, 20, 30)); + + mController.registerPositioner(positioner1); + mController.registerPositioner(positioner2); + + final Rect resultBounds = new Rect(); + + mController.calculateBounds(null /*task*/, null /*layout*/, null /*activity*/, + null /*options*/, resultBounds); + + assertEquals(resultBounds, positioner2.getLaunchBounds()); + } + + public static class InstrumentedPositioner implements LaunchingBoundsPositioner { + private int mReturnVal; + private Rect mBounds; + InstrumentedPositioner(int returnVal, Rect bounds) { + mReturnVal = returnVal; + mBounds = bounds; + } + + @Override + public int onCalculateBounds(TaskRecord task, ActivityInfo.WindowLayout layout, + ActivityRecord activity, ActivityOptions options, Rect current, Rect result) { + result.set(mBounds); + return mReturnVal; + } + + Rect getLaunchBounds() { + return mBounds; + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/am/LaunchBoundsTests.java b/services/tests/servicestests/src/com/android/server/am/LaunchingTaskPositionerTests.java index e6d683120e79..30666c0455f8 100644 --- a/services/tests/servicestests/src/com/android/server/am/LaunchBoundsTests.java +++ b/services/tests/servicestests/src/com/android/server/am/LaunchingTaskPositionerTests.java @@ -17,15 +17,12 @@ package com.android.server.am; import android.content.ComponentName; -import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo.WindowLayout; -import android.graphics.Point; import android.graphics.Rect; import android.platform.test.annotations.Presubmit; import android.support.test.filters.MediumTest; import android.support.test.runner.AndroidJUnit4; -import android.view.Display; import android.view.Gravity; import org.junit.runner.RunWith; import org.junit.Before; @@ -33,10 +30,11 @@ import org.junit.Test; import org.mockito.invocation.InvocationOnMock; -import java.util.ArrayList; - import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; + +import static com.android.server.am.LaunchingBoundsController.LaunchingBoundsPositioner.RESULT_CONTINUE; + import static org.mockito.Mockito.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -48,17 +46,12 @@ import static org.junit.Assert.assertEquals; * Tests for exercising resizing bounds. * * Build/Install/Run: - * bit FrameworksServicesTests:com.android.server.am.LaunchBoundsTests + * bit FrameworksServicesTests:com.android.server.am.LaunchingTaskPositionerTests */ @MediumTest @Presubmit @RunWith(AndroidJUnit4.class) -public class LaunchBoundsTests extends ActivityTestsBase { - private final ComponentName testActivityComponent = - ComponentName.unflattenFromString("com.foo/.BarActivity"); - private final ComponentName testActivityComponent2 = - ComponentName.unflattenFromString("com.foo/.BarActivity2"); - +public class LaunchingTaskPositionerTests extends ActivityTestsBase { private final static int STACK_WIDTH = 100; private final static int STACK_HEIGHT = 200; @@ -68,6 +61,11 @@ public class LaunchBoundsTests extends ActivityTestsBase { private ActivityStack mStack; private TaskRecord mTask; + private LaunchingTaskPositioner mPositioner; + + private Rect mCurrent; + private Rect mResult; + @Before @Override public void setUp() throws Exception { @@ -80,7 +78,12 @@ public class LaunchBoundsTests extends ActivityTestsBase { // We must create the task after resizing to make sure it does not inherit the stack // dimensions on resize. - mTask = createTask(mService.mStackSupervisor, testActivityComponent, mStack); + mTask = new TaskBuilder(mService.mStackSupervisor).setStack(mStack).build(); + + mPositioner = new LaunchingTaskPositioner(); + + mResult = new Rect(); + mCurrent = new Rect(); } /** @@ -101,12 +104,9 @@ public class LaunchBoundsTests extends ActivityTestsBase { */ @Test public void testLaunchNoWindowLayout() throws Exception { - final Rect expectedTaskBounds = getDefaultBounds(Gravity.NO_GRAVITY); - - mStack.layoutTaskInStack(mTask, null); - - // We expect the task to be placed in the middle of the screen with margins applied. - assertEquals(mTask.mBounds, expectedTaskBounds); + assertEquals(RESULT_CONTINUE, mPositioner.onCalculateBounds(mTask, null /*layout*/, + null /*record*/, null /*options*/, mCurrent, mResult)); + assertEquals(getDefaultBounds(Gravity.NO_GRAVITY), mResult); } /** @@ -116,11 +116,10 @@ public class LaunchBoundsTests extends ActivityTestsBase { */ @Test public void testlaunchEmptyWindowLayout() throws Exception { - final Rect expectedTaskBounds = getDefaultBounds(Gravity.NO_GRAVITY); - - WindowLayout layout = new WindowLayout(0, 0, 0, 0, 0, 0, 0); - mStack.layoutTaskInStack(mTask, layout); - assertEquals(mTask.mBounds, expectedTaskBounds); + assertEquals(RESULT_CONTINUE, mPositioner.onCalculateBounds(mTask, + new WindowLayout(0, 0, 0, 0, Gravity.NO_GRAVITY, 0, 0), null /*activity*/, + null /*options*/, mCurrent, mResult)); + assertEquals(mResult, getDefaultBounds(Gravity.NO_GRAVITY)); } /** @@ -149,9 +148,15 @@ public class LaunchBoundsTests extends ActivityTestsBase { } private void testGravity(int gravity) { - final WindowLayout gravityLayout = new WindowLayout(0, 0, 0, 0, gravity, 0, 0); - mStack.layoutTaskInStack(mTask, gravityLayout); - assertEquals(mTask.mBounds, getDefaultBounds(gravity)); + try { + assertEquals(RESULT_CONTINUE, mPositioner.onCalculateBounds(mTask, + new WindowLayout(0, 0, 0, 0, gravity, 0, 0), null /*activity*/, + null /*options*/, mCurrent, mResult)); + assertEquals(mResult, getDefaultBounds(gravity)); + } finally { + mCurrent.setEmpty(); + mResult.setEmpty(); + } } /** @@ -174,7 +179,7 @@ public class LaunchBoundsTests extends ActivityTestsBase { final WindowLayout layout = new WindowLayout(0, 0, 0, 0, gravity, 0, 0); // layout first task - mStack.layoutTaskInStack(mTask, layout /*windowLayout*/); + mService.mStackSupervisor.getLaunchingBoundsController().layoutTask(mTask, layout); // Second task will be laid out on top of the first so starting bounds is the same. final Rect expectedBounds = new Rect(mTask.mBounds); @@ -185,14 +190,15 @@ public class LaunchBoundsTests extends ActivityTestsBase { // wrap with try/finally to ensure cleanup of activity/stack. try { // empty tasks are ignored in conflicts - activity = createActivity(mService, testActivityComponent, mTask); + activity = new ActivityBuilder(mService).setTask(mTask).build(); // Create secondary task - secondTask = createTask(mService.mStackSupervisor, testActivityComponent, - mStack); + secondTask = new TaskBuilder(mService.mStackSupervisor).setStack(mStack).build(); // layout second task - mStack.layoutTaskInStack(secondTask, layout /*windowLayout*/); + assertEquals(RESULT_CONTINUE, + mPositioner.onCalculateBounds(secondTask, layout, null /*activity*/, + null /*options*/, mCurrent, mResult)); if ((gravity & (Gravity.TOP | Gravity.RIGHT)) == (Gravity.TOP | Gravity.RIGHT) || (gravity & (Gravity.BOTTOM | Gravity.RIGHT)) @@ -207,7 +213,7 @@ public class LaunchBoundsTests extends ActivityTestsBase { LaunchingTaskPositioner.getVerticalStep(mStack.mBounds)); } - assertEquals(secondTask.mBounds, expectedBounds); + assertEquals(mResult, expectedBounds); } finally { // Remove task and activity to prevent influencing future tests if (activity != null) { diff --git a/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java b/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java index e607228efa24..939e989c5fe8 100644 --- a/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java +++ b/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java @@ -335,8 +335,9 @@ public class RecentTasksTest extends ActivityTestsBase { } private TaskRecord createTask(String className, int flags, int userId) { - TaskRecord task = createTask(mService.mStackSupervisor, createComponent(className), flags, - LAST_TASK_ID++, mStack); + TaskRecord task = new TaskBuilder(mService.mStackSupervisor) + .setComponent(createComponent(className)) + .setStack(mStack).setFlags(flags).setTaskId(LAST_TASK_ID++).build(); task.userId = userId; task.touchActiveTime(); return task; diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java index 54717157d069..ae4b569ec56c 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java @@ -15,6 +15,7 @@ */ package com.android.server.devicepolicy; +import android.app.AlarmManager; import android.app.IActivityManager; import android.app.NotificationManager; import android.app.PendingIntent; @@ -34,11 +35,13 @@ import android.os.UserHandle; import android.os.UserManager; import android.os.UserManagerInternal; import android.security.KeyChain; +import android.support.annotation.NonNull; import android.telephony.TelephonyManager; import android.util.ArrayMap; import android.util.Pair; import android.view.IWindowManager; +import com.android.internal.util.FunctionalUtils.ThrowingRunnable; import com.android.internal.widget.LockPatternUtils; import java.io.File; @@ -194,6 +197,9 @@ public class DevicePolicyManagerServiceTestable extends DevicePolicyManagerServi } @Override + AlarmManager getAlarmManager() {return services.alarmManager;} + + @Override LockPatternUtils newLockPatternUtils() { return services.lockPatternUtils; } @@ -234,6 +240,11 @@ public class DevicePolicyManagerServiceTestable extends DevicePolicyManagerServi } @Override + void binderWithCleanCallingIdentity(@NonNull ThrowingRunnable action) { + context.binder.withCleanCallingIdentity(action); + } + + @Override int binderGetCallingUid() { return context.binder.getCallingUid(); } diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index a8bf8f18b09a..e1e9cf5bc013 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -3090,6 +3090,47 @@ public class DevicePolicyManagerTest extends DpmTestBase { assertEquals(-1, dpm.getLastSecurityLogRetrievalTime()); } + public void testSetTime() throws Exception { + mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; + setupDeviceOwner(); + dpm.setTime(admin1, 0); + verify(getServices().alarmManager).setTime(0); + } + + public void testSetTimeFailWithPO() throws Exception { + setupProfileOwner(); + assertExpectException(SecurityException.class, null, () -> dpm.setTime(admin1, 0)); + } + + public void testSetTimeWithAutoTimeOn() throws Exception { + mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; + setupDeviceOwner(); + when(getServices().settings.settingsGlobalGetInt(Settings.Global.AUTO_TIME, 0)) + .thenReturn(1); + assertFalse(dpm.setTime(admin1, 0)); + } + + public void testSetTimeZone() throws Exception { + mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; + setupDeviceOwner(); + dpm.setTimeZone(admin1, "Asia/Shanghai"); + verify(getServices().alarmManager).setTimeZone("Asia/Shanghai"); + } + + public void testSetTimeZoneFailWithPO() throws Exception { + setupProfileOwner(); + assertExpectException(SecurityException.class, null, + () -> dpm.setTimeZone(admin1, "Asia/Shanghai")); + } + + public void testSetTimeZoneWithAutoTimeZoneOn() throws Exception { + mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; + setupDeviceOwner(); + when(getServices().settings.settingsGlobalGetInt(Settings.Global.AUTO_TIME_ZONE, 0)) + .thenReturn(1); + assertFalse(dpm.setTimeZone(admin1, "Asia/Shanghai")); + } + public void testGetLastBugReportRequestTime() throws Exception { mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; setupDeviceOwner(); diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java index 97021181c384..7e11e87b9f22 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java @@ -30,8 +30,12 @@ import android.os.Bundle; import android.os.Handler; import android.os.UserHandle; import android.os.UserManagerInternal; +import android.support.annotation.NonNull; import android.test.mock.MockContext; import android.util.ArrayMap; +import android.util.ExceptionUtils; + +import com.android.internal.util.FunctionalUtils; import org.junit.Assert; @@ -95,6 +99,21 @@ public class DpmMockContext extends MockContext { callingPid = (int) token; } + public void withCleanCallingIdentity(@NonNull FunctionalUtils.ThrowingRunnable action) { + long callingIdentity = clearCallingIdentity(); + Throwable throwableToPropagate = null; + try { + action.run(); + } catch (Throwable throwable) { + throwableToPropagate = throwable; + } finally { + restoreCallingIdentity(callingIdentity); + if (throwableToPropagate != null) { + throw ExceptionUtils.propagate(throwableToPropagate); + } + } + } + public int getCallingUid() { return callingUid; } diff --git a/services/tests/servicestests/src/com/android/server/pm/UserDataPreparerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserDataPreparerTest.java index 7a676e258aba..bb35beb20a23 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserDataPreparerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserDataPreparerTest.java @@ -154,9 +154,6 @@ public class UserDataPreparerTest { File systemDeDir = mUserDataPreparer.getDataSystemDeDirectory(TEST_USER_ID); systemDeDir.mkdirs(); writeFile(new File(systemDeDir, "file"), "-----" ); - File miscDeDir = mUserDataPreparer.getDataMiscDeDirectory(TEST_USER_ID); - miscDeDir.mkdirs(); - writeFile(new File(miscDeDir, "file"), "-----" ); mUserDataPreparer.destroyUserData(TEST_USER_ID, StorageManager.FLAG_STORAGE_DE); @@ -168,8 +165,6 @@ public class UserDataPreparerTest { assertEquals(Collections.emptyList(), Arrays.asList(FileUtils.listFilesOrEmpty(systemDir))); assertEquals(Collections.emptyList(), Arrays.asList(FileUtils.listFilesOrEmpty( systemDeDir))); - assertEquals(Collections.emptyList(), Arrays.asList(FileUtils.listFilesOrEmpty( - miscDeDir))); } @Test @@ -177,9 +172,6 @@ public class UserDataPreparerTest { File systemCeDir = mUserDataPreparer.getDataSystemCeDirectory(TEST_USER_ID); systemCeDir.mkdirs(); writeFile(new File(systemCeDir, "file"), "-----" ); - File miscCeDir = mUserDataPreparer.getDataMiscCeDirectory(TEST_USER_ID); - miscCeDir.mkdirs(); - writeFile(new File(miscCeDir, "file"), "-----" ); mUserDataPreparer.destroyUserData(TEST_USER_ID, StorageManager.FLAG_STORAGE_CE); @@ -190,8 +182,6 @@ public class UserDataPreparerTest { assertEquals(Collections.emptyList(), Arrays.asList(FileUtils.listFilesOrEmpty( systemCeDir))); - assertEquals(Collections.emptyList(), Arrays.asList(FileUtils.listFilesOrEmpty( - miscCeDir))); } @Test diff --git a/services/tests/servicestests/src/com/android/server/usage/AppIdleHistoryTests.java b/services/tests/servicestests/src/com/android/server/usage/AppIdleHistoryTests.java index 42ddedf0b340..67ffe5847cbc 100644 --- a/services/tests/servicestests/src/com/android/server/usage/AppIdleHistoryTests.java +++ b/services/tests/servicestests/src/com/android/server/usage/AppIdleHistoryTests.java @@ -16,6 +16,11 @@ package com.android.server.usage; +import static android.app.usage.AppStandby.REASON_TIMEOUT; +import static android.app.usage.AppStandby.STANDBY_BUCKET_ACTIVE; +import static android.app.usage.AppStandby.STANDBY_BUCKET_RARE; + +import android.app.usage.AppStandby; import android.os.FileUtils; import android.test.AndroidTestCase; @@ -28,6 +33,8 @@ public class AppIdleHistoryTests extends AndroidTestCase { final static String PACKAGE_1 = "com.android.testpackage1"; final static String PACKAGE_2 = "com.android.testpackage2"; + final static int USER_ID = 0; + @Override protected void setUp() throws Exception { super.setUp(); @@ -42,7 +49,6 @@ public class AppIdleHistoryTests extends AndroidTestCase { } public void testFilesCreation() { - final int userId = 0; AppIdleHistory aih = new AppIdleHistory(mStorageDir, 0); aih.updateDisplay(true, /* elapsedRealtime= */ 1000); @@ -50,9 +56,9 @@ public class AppIdleHistoryTests extends AndroidTestCase { // Screen On time file should be written right away assertTrue(aih.getScreenOnTimeFile().exists()); - aih.writeAppIdleTimes(userId); + aih.writeAppIdleTimes(USER_ID); // stats file should be written now - assertTrue(new File(new File(mStorageDir, "users/" + userId), + assertTrue(new File(new File(mStorageDir, "users/" + USER_ID), AppIdleHistory.APP_IDLE_FILENAME).exists()); } @@ -77,24 +83,33 @@ public class AppIdleHistoryTests extends AndroidTestCase { assertEquals(aih2.getScreenOnTime(13000), 4000); } - public void testPackageEvents() { + public void testBuckets() { AppIdleHistory aih = new AppIdleHistory(mStorageDir, 1000); - aih.setThresholds(4000, 1000); - aih.updateDisplay(true, 1000); - // App is not-idle by default - assertFalse(aih.isIdle(PACKAGE_1, 0, 1500)); - // Still not idle - assertFalse(aih.isIdle(PACKAGE_1, 0, 3000)); - // Idle now - assertTrue(aih.isIdle(PACKAGE_1, 0, 8000)); - // Not idle - assertFalse(aih.isIdle(PACKAGE_2, 0, 9000)); - - // Screen off - aih.updateDisplay(false, 9100); - // Still idle after 10 seconds because screen hasn't been on long enough - assertFalse(aih.isIdle(PACKAGE_2, 0, 20000)); - aih.updateDisplay(true, 21000); - assertTrue(aih.isIdle(PACKAGE_2, 0, 23000)); + + aih.setAppStandbyBucket(PACKAGE_1, USER_ID, 1000, STANDBY_BUCKET_ACTIVE, + AppStandby.REASON_USAGE); + // ACTIVE means not idle + assertFalse(aih.isIdle(PACKAGE_1, USER_ID, 2000)); + + aih.setAppStandbyBucket(PACKAGE_2, USER_ID, 2000, STANDBY_BUCKET_ACTIVE, + AppStandby.REASON_USAGE); + aih.setAppStandbyBucket(PACKAGE_1, USER_ID, 3000, STANDBY_BUCKET_RARE, + REASON_TIMEOUT); + + assertEquals(aih.getAppStandbyBucket(PACKAGE_1, USER_ID, 3000), STANDBY_BUCKET_RARE); + assertEquals(aih.getAppStandbyBucket(PACKAGE_2, USER_ID, 3000), STANDBY_BUCKET_ACTIVE); + assertEquals(aih.getAppStandbyReason(PACKAGE_1, USER_ID, 3000), REASON_TIMEOUT); + + // RARE is considered idle + assertTrue(aih.isIdle(PACKAGE_1, USER_ID, 3000)); + assertFalse(aih.isIdle(PACKAGE_2, USER_ID, 3000)); + + // Check persistence + aih.writeAppIdleDurations(); + aih.writeAppIdleTimes(USER_ID); + aih = new AppIdleHistory(mStorageDir, 4000); + assertEquals(aih.getAppStandbyBucket(PACKAGE_1, USER_ID, 5000), STANDBY_BUCKET_RARE); + assertEquals(aih.getAppStandbyBucket(PACKAGE_2, USER_ID, 5000), STANDBY_BUCKET_ACTIVE); + assertEquals(aih.getAppStandbyReason(PACKAGE_1, USER_ID, 5000), REASON_TIMEOUT); } }
\ No newline at end of file diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java new file mode 100644 index 000000000000..9846d6f6f346 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java @@ -0,0 +1,322 @@ +/* + * Copyright (C) 2017 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.server.usage; + +import static android.app.usage.AppStandby.STANDBY_BUCKET_ACTIVE; +import static android.app.usage.AppStandby.STANDBY_BUCKET_FREQUENT; +import static android.app.usage.AppStandby.STANDBY_BUCKET_RARE; +import static android.app.usage.AppStandby.STANDBY_BUCKET_WORKING_SET; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; + +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; + +import android.app.usage.AppStandby; +import android.app.usage.UsageEvents; +import android.appwidget.AppWidgetManager; +import android.content.Context; +import android.content.ContextWrapper; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.hardware.display.DisplayManager; +import android.os.Handler; +import android.os.Looper; +import android.os.RemoteException; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; +import android.view.Display; + +import com.android.server.SystemService; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +/** + * Unit test for AppStandbyController. + */ +@RunWith(AndroidJUnit4.class) +public class AppStandbyControllerTests { + + private static final String PACKAGE_1 = "com.example.foo"; + private static final int UID_1 = 10000; + private static final int USER_ID = 0; + + private static final long MINUTE_MS = 60 * 1000; + private static final long HOUR_MS = 60 * MINUTE_MS; + private static final long DAY_MS = 24 * HOUR_MS; + + private MyInjector mInjector; + + static class MyContextWrapper extends ContextWrapper { + PackageManager mockPm = mock(PackageManager.class); + + public MyContextWrapper(Context base) { + super(base); + } + + public PackageManager getPackageManager() { + return mockPm; + } + } + + static class MyInjector extends AppStandbyController.Injector { + long mElapsedRealtime; + boolean mIsCharging; + List<String> mPowerSaveWhitelistExceptIdle = new ArrayList<>(); + boolean mDisplayOn; + DisplayManager.DisplayListener mDisplayListener; + String mBoundWidgetPackage; + + MyInjector(Context context, Looper looper) { + super(context, looper); + } + + @Override + void onBootPhase(int phase) { + } + + @Override + int getBootPhase() { + return SystemService.PHASE_BOOT_COMPLETED; + } + + @Override + long elapsedRealtime() { + return mElapsedRealtime; + } + + @Override + long currentTimeMillis() { + return mElapsedRealtime; + } + + @Override + boolean isAppIdleEnabled() { + return true; + } + + @Override + boolean isCharging() { + return mIsCharging; + } + + @Override + boolean isPowerSaveWhitelistExceptIdleApp(String packageName) throws RemoteException { + return mPowerSaveWhitelistExceptIdle.contains(packageName); + } + + @Override + File getDataSystemDirectory() { + return new File(getContext().getFilesDir(), Long.toString(Math.randomLongInternal())); + } + + @Override + void noteEvent(int event, String packageName, int uid) throws RemoteException { + } + + @Override + boolean isPackageEphemeral(int userId, String packageName) { + // TODO: update when testing ephemeral apps scenario + return false; + } + + @Override + int[] getRunningUserIds() { + return new int[] {USER_ID}; + } + + @Override + boolean isDefaultDisplayOn() { + return mDisplayOn; + } + + @Override + void registerDisplayListener(DisplayManager.DisplayListener listener, Handler handler) { + mDisplayListener = listener; + } + + @Override + String getActiveNetworkScorer() { + return null; + } + + @Override + public boolean isBoundWidgetPackage(AppWidgetManager appWidgetManager, String packageName, + int userId) { + return packageName != null && packageName.equals(mBoundWidgetPackage); + } + + // Internal methods + + void setDisplayOn(boolean on) { + mDisplayOn = on; + if (mDisplayListener != null) { + mDisplayListener.onDisplayChanged(Display.DEFAULT_DISPLAY); + } + } + } + + private void setupPm(PackageManager mockPm) throws PackageManager.NameNotFoundException { + List<PackageInfo> packages = new ArrayList<>(); + PackageInfo pi = new PackageInfo(); + pi.applicationInfo = new ApplicationInfo(); + pi.applicationInfo.uid = UID_1; + pi.packageName = PACKAGE_1; + packages.add(pi); + + doReturn(packages).when(mockPm).getInstalledPackagesAsUser(anyInt(), anyInt()); + try { + doReturn(UID_1).when(mockPm).getPackageUidAsUser(anyString(), anyInt(), anyInt()); + doReturn(pi.applicationInfo).when(mockPm).getApplicationInfo(anyString(), anyInt()); + } catch (PackageManager.NameNotFoundException nnfe) {} + } + + private void setChargingState(AppStandbyController controller, boolean charging) { + mInjector.mIsCharging = charging; + if (controller != null) { + controller.setChargingState(charging); + } + } + + private AppStandbyController setupController() throws Exception { + mInjector.mElapsedRealtime = 0; + AppStandbyController controller = new AppStandbyController(mInjector); + controller.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY); + controller.onBootPhase(SystemService.PHASE_BOOT_COMPLETED); + mInjector.setDisplayOn(false); + mInjector.setDisplayOn(true); + setChargingState(controller, false); + setupPm(mInjector.getContext().getPackageManager()); + controller.checkIdleStates(USER_ID); + + return controller; + } + + @Before + public void setUp() throws Exception { + MyContextWrapper myContext = new MyContextWrapper(InstrumentationRegistry.getContext()); + mInjector = new MyInjector(myContext, Looper.getMainLooper()); + } + + @Test + public void testCharging() throws Exception { + AppStandbyController controller = setupController(); + + setChargingState(controller, true); + mInjector.mElapsedRealtime = 8 * DAY_MS; + assertFalse(controller.isAppIdleFilteredOrParoled(PACKAGE_1, USER_ID, + mInjector.mElapsedRealtime, false)); + + setChargingState(controller, false); + mInjector.mElapsedRealtime = 16 * DAY_MS; + controller.checkIdleStates(USER_ID); + assertTrue(controller.isAppIdleFilteredOrParoled(PACKAGE_1, USER_ID, + mInjector.mElapsedRealtime, false)); + setChargingState(controller, true); + assertFalse(controller.isAppIdleFilteredOrParoled(PACKAGE_1,USER_ID, + mInjector.mElapsedRealtime, false)); + } + + private void assertTimeout(AppStandbyController controller, long elapsedTime, int bucket) { + mInjector.mElapsedRealtime = elapsedTime; + controller.checkIdleStates(USER_ID); + assertEquals(bucket, + controller.getAppStandbyBucket(PACKAGE_1, USER_ID, mInjector.mElapsedRealtime, + false)); + } + + @Test + public void testBuckets() throws Exception { + AppStandbyController controller = setupController(); + + // ACTIVE bucket + assertTimeout(controller, 11 * HOUR_MS, STANDBY_BUCKET_ACTIVE); + + // WORKING_SET bucket + assertTimeout(controller, 25 * HOUR_MS, STANDBY_BUCKET_WORKING_SET); + + // WORKING_SET bucket + assertTimeout(controller, 47 * HOUR_MS, STANDBY_BUCKET_WORKING_SET); + + // FREQUENT bucket + assertTimeout(controller, 4 * DAY_MS, STANDBY_BUCKET_FREQUENT); + + // RARE bucket + assertTimeout(controller, 9 * DAY_MS, STANDBY_BUCKET_RARE); + + // Back to ACTIVE on event + UsageEvents.Event ev = new UsageEvents.Event(); + ev.mPackage = PACKAGE_1; + ev.mEventType = UsageEvents.Event.USER_INTERACTION; + controller.reportEvent(ev, mInjector.mElapsedRealtime, USER_ID); + + assertTimeout(controller, 9 * DAY_MS, STANDBY_BUCKET_ACTIVE); + + // RARE bucket + assertTimeout(controller, 18 * DAY_MS, STANDBY_BUCKET_RARE); + } + + @Test + public void testScreenTimeAndBuckets() throws Exception { + AppStandbyController controller = setupController(); + mInjector.setDisplayOn(false); + + // ACTIVE bucket + assertTimeout(controller, 11 * HOUR_MS, STANDBY_BUCKET_ACTIVE); + + // WORKING_SET bucket + assertTimeout(controller, 25 * HOUR_MS, STANDBY_BUCKET_WORKING_SET); + + // RARE bucket, should fail because the screen wasn't ON. + mInjector.mElapsedRealtime = 9 * DAY_MS; + controller.checkIdleStates(USER_ID); + assertNotEquals(STANDBY_BUCKET_RARE, + controller.getAppStandbyBucket(PACKAGE_1, USER_ID, mInjector.mElapsedRealtime, + false)); + + mInjector.setDisplayOn(true); + assertTimeout(controller, 18 * DAY_MS, STANDBY_BUCKET_RARE); + } + + @Test + public void testForcedIdle() throws Exception { + AppStandbyController controller = setupController(); + setChargingState(controller, false); + + controller.forceIdleState(PACKAGE_1, USER_ID, true); + assertEquals(STANDBY_BUCKET_RARE, controller.getAppStandbyBucket(PACKAGE_1, USER_ID, 0, + true)); + assertTrue(controller.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0)); + + controller.forceIdleState(PACKAGE_1, USER_ID, false); + assertEquals(STANDBY_BUCKET_ACTIVE, controller.getAppStandbyBucket(PACKAGE_1, USER_ID, 0, + true)); + assertFalse(controller.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0)); + } +} diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java index 56a3fb094280..0980f7ec4df0 100644 --- a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java @@ -63,7 +63,7 @@ import java.util.LinkedList; class WindowTestsBase { static WindowManagerService sWm = null; private static final IWindow sIWindow = new TestIWindow(); - private static final Session sMockSession = mock(Session.class); + private static Session sMockSession; // The default display is removed in {@link #setUp} and then we iterate over all displays to // make sure we don't collide with any existing display. If we run into no other display, the // added display should be treated as default. This cannot be the default display @@ -89,7 +89,11 @@ class WindowTestsBase { public void setUp() throws Exception { if (!sOneTimeSetupDone) { sOneTimeSetupDone = true; + + // Allows to mock package local classes and methods + System.setProperty("dexmaker.share_classloader", "true"); MockitoAnnotations.initMocks(this); + sMockSession = mock(Session.class); } final Context context = InstrumentationRegistry.getTargetContext(); diff --git a/services/usage/java/com/android/server/usage/AppIdleHistory.java b/services/usage/java/com/android/server/usage/AppIdleHistory.java index f2985597573f..e5d3915dde4c 100644 --- a/services/usage/java/com/android/server/usage/AppIdleHistory.java +++ b/services/usage/java/com/android/server/usage/AppIdleHistory.java @@ -16,6 +16,9 @@ package com.android.server.usage; +import static android.app.usage.AppStandby.*; + +import android.app.usage.AppStandby; import android.os.Environment; import android.os.SystemClock; import android.util.ArrayMap; @@ -51,8 +54,10 @@ public class AppIdleHistory { private static final String TAG = "AppIdleHistory"; + private static final boolean DEBUG = AppStandbyController.DEBUG; + // History for all users and all packages - private SparseArray<ArrayMap<String,PackageHistory>> mIdleHistory = new SparseArray<>(); + private SparseArray<ArrayMap<String,AppUsageHistory>> mIdleHistory = new SparseArray<>(); private long mLastPeriod = 0; private static final long ONE_MINUTE = 60 * 1000; private static final int HISTORY_SIZE = 100; @@ -70,6 +75,13 @@ public class AppIdleHistory { private static final String ATTR_SCREEN_IDLE = "screenIdleTime"; // Elapsed timebase time when app was last used private static final String ATTR_ELAPSED_IDLE = "elapsedIdleTime"; + private static final String ATTR_CURRENT_BUCKET = "appLimitBucket"; + private static final String ATTR_BUCKETING_REASON = "bucketReason"; + + // State that was last informed to listeners, since boot + private static final int STATE_UNINFORMED = 0; + private static final int STATE_ACTIVE = 1; + private static final int STATE_IDLE = 2; // device on time = mElapsedDuration + (timeNow - mElapsedSnapshot) private long mElapsedSnapshot; // Elapsed time snapshot when last write of mDeviceOnDuration @@ -85,17 +97,15 @@ public class AppIdleHistory { private boolean mScreenOn; - private static class PackageHistory { + private static class AppUsageHistory { final byte[] recent = new byte[HISTORY_SIZE]; long lastUsedElapsedTime; long lastUsedScreenTime; + @StandbyBuckets int currentBucket; + String bucketingReason; + int lastInformedState; } - AppIdleHistory(long elapsedRealtime) { - this(Environment.getDataSystemDirectory(), elapsedRealtime); - } - - @VisibleForTesting AppIdleHistory(File storageDir, long elapsedRealtime) { mElapsedSnapshot = elapsedRealtime; mScreenOnSnapshot = elapsedRealtime; @@ -119,6 +129,9 @@ public class AppIdleHistory { mElapsedDuration += elapsedRealtime - mElapsedSnapshot; mElapsedSnapshot = elapsedRealtime; } + if (DEBUG) Slog.d(TAG, "mScreenOnSnapshot=" + mScreenOnSnapshot + + ", mScreenOnDuration=" + mScreenOnDuration + + ", mScreenOn=" + mScreenOn); } public long getScreenOnTime(long elapsedRealtime) { @@ -174,29 +187,35 @@ public class AppIdleHistory { } public void reportUsage(String packageName, int userId, long elapsedRealtime) { - ArrayMap<String, PackageHistory> userHistory = getUserHistory(userId); - PackageHistory packageHistory = getPackageHistory(userHistory, packageName, - elapsedRealtime); + ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId); + AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName, + elapsedRealtime, true); shiftHistoryToNow(userHistory, elapsedRealtime); - packageHistory.lastUsedElapsedTime = mElapsedDuration + appUsageHistory.lastUsedElapsedTime = mElapsedDuration + (elapsedRealtime - mElapsedSnapshot); - packageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime); - packageHistory.recent[HISTORY_SIZE - 1] = FLAG_LAST_STATE | FLAG_PARTIAL_ACTIVE; + appUsageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime); + appUsageHistory.recent[HISTORY_SIZE - 1] = FLAG_LAST_STATE | FLAG_PARTIAL_ACTIVE; + appUsageHistory.currentBucket = AppStandby.STANDBY_BUCKET_ACTIVE; + appUsageHistory.bucketingReason = AppStandby.REASON_USAGE; + if (DEBUG) { + Slog.d(TAG, "Moved " + packageName + " to bucket=" + appUsageHistory.currentBucket + + ", reason=" + appUsageHistory.bucketingReason); + } } public void setIdle(String packageName, int userId, long elapsedRealtime) { - ArrayMap<String, PackageHistory> userHistory = getUserHistory(userId); - PackageHistory packageHistory = getPackageHistory(userHistory, packageName, - elapsedRealtime); + ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId); + AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName, + elapsedRealtime, true); shiftHistoryToNow(userHistory, elapsedRealtime); - packageHistory.recent[HISTORY_SIZE - 1] &= ~FLAG_LAST_STATE; + appUsageHistory.recent[HISTORY_SIZE - 1] &= ~FLAG_LAST_STATE; } - private void shiftHistoryToNow(ArrayMap<String, PackageHistory> userHistory, + private void shiftHistoryToNow(ArrayMap<String, AppUsageHistory> userHistory, long elapsedRealtime) { long thisPeriod = elapsedRealtime / PERIOD_DURATION; // Has the period switched over? Slide all users' package histories @@ -206,7 +225,7 @@ public class AppIdleHistory { final int NUSERS = mIdleHistory.size(); for (int u = 0; u < NUSERS; u++) { userHistory = mIdleHistory.valueAt(u); - for (PackageHistory idleState : userHistory.values()) { + for (AppUsageHistory idleState : userHistory.values()) { // Shift left System.arraycopy(idleState.recent, diff, idleState.recent, 0, HISTORY_SIZE - diff); @@ -221,8 +240,8 @@ public class AppIdleHistory { mLastPeriod = thisPeriod; } - private ArrayMap<String, PackageHistory> getUserHistory(int userId) { - ArrayMap<String, PackageHistory> userHistory = mIdleHistory.get(userId); + private ArrayMap<String, AppUsageHistory> getUserHistory(int userId) { + ArrayMap<String, AppUsageHistory> userHistory = mIdleHistory.get(userId); if (userHistory == null) { userHistory = new ArrayMap<>(); mIdleHistory.put(userId, userHistory); @@ -231,16 +250,18 @@ public class AppIdleHistory { return userHistory; } - private PackageHistory getPackageHistory(ArrayMap<String, PackageHistory> userHistory, - String packageName, long elapsedRealtime) { - PackageHistory packageHistory = userHistory.get(packageName); - if (packageHistory == null) { - packageHistory = new PackageHistory(); - packageHistory.lastUsedElapsedTime = getElapsedTime(elapsedRealtime); - packageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime); - userHistory.put(packageName, packageHistory); + private AppUsageHistory getPackageHistory(ArrayMap<String, AppUsageHistory> userHistory, + String packageName, long elapsedRealtime, boolean create) { + AppUsageHistory appUsageHistory = userHistory.get(packageName); + if (appUsageHistory == null && create) { + appUsageHistory = new AppUsageHistory(); + appUsageHistory.lastUsedElapsedTime = getElapsedTime(elapsedRealtime); + appUsageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime); + appUsageHistory.currentBucket = AppStandby.STANDBY_BUCKET_NEVER; + appUsageHistory.bucketingReason = REASON_DEFAULT; + userHistory.put(packageName, appUsageHistory); } - return packageHistory; + return appUsageHistory; } public void onUserRemoved(int userId) { @@ -248,48 +269,124 @@ public class AppIdleHistory { } public boolean isIdle(String packageName, int userId, long elapsedRealtime) { - ArrayMap<String, PackageHistory> userHistory = getUserHistory(userId); - PackageHistory packageHistory = - getPackageHistory(userHistory, packageName, elapsedRealtime); - if (packageHistory == null) { + ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId); + AppUsageHistory appUsageHistory = + getPackageHistory(userHistory, packageName, elapsedRealtime, true); + if (appUsageHistory == null) { return false; // Default to not idle } else { - return hasPassedThresholds(packageHistory, elapsedRealtime); + return appUsageHistory.currentBucket >= AppStandby.STANDBY_BUCKET_RARE; + // Whether or not it's passed will now be externally calculated and the + // bucket will be pushed to the history using setAppStandbyBucket() + //return hasPassedThresholds(appUsageHistory, elapsedRealtime); } } + public void setAppStandbyBucket(String packageName, int userId, long elapsedRealtime, + int bucket, String reason) { + ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId); + AppUsageHistory appUsageHistory = + getPackageHistory(userHistory, packageName, elapsedRealtime, true); + appUsageHistory.currentBucket = bucket; + appUsageHistory.bucketingReason = reason; + if (DEBUG) { + Slog.d(TAG, "Moved " + packageName + " to bucket=" + appUsageHistory.currentBucket + + ", reason=" + appUsageHistory.bucketingReason); + } + } + + public int getAppStandbyBucket(String packageName, int userId, long elapsedRealtime) { + ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId); + AppUsageHistory appUsageHistory = + getPackageHistory(userHistory, packageName, elapsedRealtime, true); + return appUsageHistory.currentBucket; + } + + public String getAppStandbyReason(String packageName, int userId, long elapsedRealtime) { + ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId); + AppUsageHistory appUsageHistory = + getPackageHistory(userHistory, packageName, elapsedRealtime, false); + return appUsageHistory != null ? appUsageHistory.bucketingReason : null; + } + private long getElapsedTime(long elapsedRealtime) { return (elapsedRealtime - mElapsedSnapshot + mElapsedDuration); } public void setIdle(String packageName, int userId, boolean idle, long elapsedRealtime) { - ArrayMap<String, PackageHistory> userHistory = getUserHistory(userId); - PackageHistory packageHistory = getPackageHistory(userHistory, packageName, - elapsedRealtime); - packageHistory.lastUsedElapsedTime = getElapsedTime(elapsedRealtime) - - mElapsedTimeThreshold; - packageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime) - - (idle ? mScreenOnTimeThreshold : 0) - 1000 /* just a second more */; + ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId); + AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName, + elapsedRealtime, true); + if (idle) { + appUsageHistory.currentBucket = STANDBY_BUCKET_RARE; + appUsageHistory.bucketingReason = REASON_FORCED; + } else { + appUsageHistory.currentBucket = STANDBY_BUCKET_ACTIVE; + // This is to pretend that the app was just used, don't freeze the state anymore. + appUsageHistory.bucketingReason = REASON_USAGE; + } } public void clearUsage(String packageName, int userId) { - ArrayMap<String, PackageHistory> userHistory = getUserHistory(userId); + ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId); userHistory.remove(packageName); } - private boolean hasPassedThresholds(PackageHistory packageHistory, long elapsedRealtime) { - return (packageHistory.lastUsedScreenTime - <= getScreenOnTime(elapsedRealtime) - mScreenOnTimeThreshold) - && (packageHistory.lastUsedElapsedTime - <= getElapsedTime(elapsedRealtime) - mElapsedTimeThreshold); + boolean shouldInformListeners(String packageName, int userId, + long elapsedRealtime, boolean isIdle) { + ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId); + AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName, + elapsedRealtime, true); + int targetState = isIdle? STATE_IDLE : STATE_ACTIVE; + if (appUsageHistory.lastInformedState != (isIdle ? STATE_IDLE : STATE_ACTIVE)) { + appUsageHistory.lastInformedState = targetState; + return true; + } + return false; + } + + /** + * Returns the index in the arrays of screenTimeThresholds and elapsedTimeThresholds + * that corresponds to how long since the app was used. + * @param packageName + * @param userId + * @param elapsedRealtime current time + * @param screenTimeThresholds Array of screen times, in ascending order, first one is 0 + * @param elapsedTimeThresholds Array of elapsed time, in ascending order, first one is 0 + * @return The index whose values the app's used time exceeds (in both arrays) + */ + int getThresholdIndex(String packageName, int userId, long elapsedRealtime, + long[] screenTimeThresholds, long[] elapsedTimeThresholds) { + ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId); + AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName, + elapsedRealtime, false); + // If we don't have any state for the app, assume never used + if (appUsageHistory == null) return screenTimeThresholds.length - 1; + + long screenOnDelta = getScreenOnTime(elapsedRealtime) - appUsageHistory.lastUsedScreenTime; + long elapsedDelta = getElapsedTime(elapsedRealtime) - appUsageHistory.lastUsedElapsedTime; + + if (DEBUG) Slog.d(TAG, packageName + + " lastUsedScreen=" + appUsageHistory.lastUsedScreenTime + + " lastUsedElapsed=" + appUsageHistory.lastUsedElapsedTime); + if (DEBUG) Slog.d(TAG, packageName + " screenOn=" + screenOnDelta + + ", elapsed=" + elapsedDelta); + for (int i = screenTimeThresholds.length - 1; i >= 0; i--) { + if (screenOnDelta >= screenTimeThresholds[i] + && elapsedDelta >= elapsedTimeThresholds[i]) { + return i; + } + } + return 0; } - private File getUserFile(int userId) { + @VisibleForTesting + File getUserFile(int userId) { return new File(new File(new File(mStorageDir, "users"), Integer.toString(userId)), APP_IDLE_FILENAME); } - private void readAppIdleTimes(int userId, ArrayMap<String, PackageHistory> userHistory) { + private void readAppIdleTimes(int userId, ArrayMap<String, AppUsageHistory> userHistory) { FileInputStream fis = null; try { AtomicFile appIdleFile = new AtomicFile(getUserFile(userId)); @@ -315,12 +412,22 @@ public class AppIdleHistory { final String name = parser.getName(); if (name.equals(TAG_PACKAGE)) { final String packageName = parser.getAttributeValue(null, ATTR_NAME); - PackageHistory packageHistory = new PackageHistory(); - packageHistory.lastUsedElapsedTime = + AppUsageHistory appUsageHistory = new AppUsageHistory(); + appUsageHistory.lastUsedElapsedTime = Long.parseLong(parser.getAttributeValue(null, ATTR_ELAPSED_IDLE)); - packageHistory.lastUsedScreenTime = + appUsageHistory.lastUsedScreenTime = Long.parseLong(parser.getAttributeValue(null, ATTR_SCREEN_IDLE)); - userHistory.put(packageName, packageHistory); + String currentBucketString = parser.getAttributeValue(null, + ATTR_CURRENT_BUCKET); + appUsageHistory.currentBucket = currentBucketString == null + ? AppStandby.STANDBY_BUCKET_ACTIVE + : Integer.parseInt(currentBucketString); + appUsageHistory.bucketingReason = + parser.getAttributeValue(null, ATTR_BUCKETING_REASON); + if (appUsageHistory.bucketingReason == null) { + appUsageHistory.bucketingReason = REASON_DEFAULT; + } + userHistory.put(packageName, appUsageHistory); } } } @@ -345,17 +452,20 @@ public class AppIdleHistory { xml.startTag(null, TAG_PACKAGES); - ArrayMap<String,PackageHistory> userHistory = getUserHistory(userId); + ArrayMap<String,AppUsageHistory> userHistory = getUserHistory(userId); final int N = userHistory.size(); for (int i = 0; i < N; i++) { String packageName = userHistory.keyAt(i); - PackageHistory history = userHistory.valueAt(i); + AppUsageHistory history = userHistory.valueAt(i); xml.startTag(null, TAG_PACKAGE); xml.attribute(null, ATTR_NAME, packageName); xml.attribute(null, ATTR_ELAPSED_IDLE, Long.toString(history.lastUsedElapsedTime)); xml.attribute(null, ATTR_SCREEN_IDLE, Long.toString(history.lastUsedScreenTime)); + xml.attribute(null, ATTR_CURRENT_BUCKET, + Integer.toString(history.currentBucket)); + xml.attribute(null, ATTR_BUCKETING_REASON, history.bucketingReason); xml.endTag(null, TAG_PACKAGE); } @@ -368,10 +478,10 @@ public class AppIdleHistory { } } - public void dump(IndentingPrintWriter idpw, int userId) { + public void dump(IndentingPrintWriter idpw, int userId, String pkg) { idpw.println("Package idle stats:"); idpw.increaseIndent(); - ArrayMap<String, PackageHistory> userHistory = mIdleHistory.get(userId); + ArrayMap<String, AppUsageHistory> userHistory = mIdleHistory.get(userId); final long elapsedRealtime = SystemClock.elapsedRealtime(); final long totalElapsedTime = getElapsedTime(elapsedRealtime); final long screenOnTime = getScreenOnTime(elapsedRealtime); @@ -379,13 +489,18 @@ public class AppIdleHistory { final int P = userHistory.size(); for (int p = 0; p < P; p++) { final String packageName = userHistory.keyAt(p); - final PackageHistory packageHistory = userHistory.valueAt(p); + final AppUsageHistory appUsageHistory = userHistory.valueAt(p); + if (pkg != null && !pkg.equals(packageName)) { + continue; + } idpw.print("package=" + packageName); idpw.print(" lastUsedElapsed="); - TimeUtils.formatDuration(totalElapsedTime - packageHistory.lastUsedElapsedTime, idpw); + TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.lastUsedElapsedTime, idpw); idpw.print(" lastUsedScreenOn="); - TimeUtils.formatDuration(screenOnTime - packageHistory.lastUsedScreenTime, idpw); + TimeUtils.formatDuration(screenOnTime - appUsageHistory.lastUsedScreenTime, idpw); idpw.print(" idle=" + (isIdle(packageName, userId, elapsedRealtime) ? "y" : "n")); + idpw.print(" bucket=" + appUsageHistory.currentBucket + + " reason=" + appUsageHistory.bucketingReason); idpw.println(); } idpw.println(); @@ -399,7 +514,7 @@ public class AppIdleHistory { } public void dumpHistory(IndentingPrintWriter idpw, int userId) { - ArrayMap<String, PackageHistory> userHistory = mIdleHistory.get(userId); + ArrayMap<String, AppUsageHistory> userHistory = mIdleHistory.get(userId); final long elapsedRealtime = SystemClock.elapsedRealtime(); if (userHistory == null) return; final int P = userHistory.size(); diff --git a/services/usage/java/com/android/server/usage/AppStandbyController.java b/services/usage/java/com/android/server/usage/AppStandbyController.java index b2446ba7158d..17fde57907d7 100644 --- a/services/usage/java/com/android/server/usage/AppStandbyController.java +++ b/services/usage/java/com/android/server/usage/AppStandbyController.java @@ -18,13 +18,14 @@ package com.android.server.usage; import static com.android.server.SystemService.PHASE_BOOT_COMPLETED; import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY; -import static com.android.server.usage.UsageStatsService.MSG_REPORT_EVENT; import android.app.ActivityManager; import android.app.AppGlobals; import android.app.admin.DevicePolicyManager; +import android.app.usage.AppStandby; +import android.app.usage.AppStandby.StandbyBuckets; import android.app.usage.UsageEvents; -import android.app.usage.UsageStatsManagerInternal; +import android.app.usage.UsageStatsManagerInternal.AppIdleStateChangeListener; import android.appwidget.AppWidgetManager; import android.content.BroadcastReceiver; import android.content.ContentResolver; @@ -41,6 +42,7 @@ import android.hardware.display.DisplayManager; import android.net.NetworkScoreManager; import android.os.BatteryManager; import android.os.BatteryStats; +import android.os.Environment; import android.os.Handler; import android.os.IDeviceIdleController; import android.os.Looper; @@ -66,8 +68,10 @@ import com.android.internal.util.ArrayUtils; import com.android.internal.util.IndentingPrintWriter; import com.android.server.LocalServices; +import java.io.File; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; /** @@ -76,10 +80,33 @@ import java.util.List; public class AppStandbyController { private static final String TAG = "AppStandbyController"; - private static final boolean DEBUG = false; + static final boolean DEBUG = false; static final boolean COMPRESS_TIME = false; private static final long ONE_MINUTE = 60 * 1000; + private static final long ONE_HOUR = ONE_MINUTE * 60; + private static final long ONE_DAY = ONE_HOUR * 24; + + static final long[] SCREEN_TIME_THRESHOLDS = { + 0, + 0, + COMPRESS_TIME ? 120 * 1000 : 1 * ONE_HOUR, + COMPRESS_TIME ? 240 * 1000 : 8 * ONE_HOUR + }; + + static final long[] ELAPSED_TIME_THRESHOLDS = { + 0, + COMPRESS_TIME ? 1 * ONE_MINUTE : 12 * ONE_HOUR, + COMPRESS_TIME ? 4 * ONE_MINUTE : 2 * ONE_DAY, + COMPRESS_TIME ? 16 * ONE_MINUTE : 8 * ONE_DAY + }; + + static final int[] THRESHOLD_BUCKETS = { + AppStandby.STANDBY_BUCKET_ACTIVE, + AppStandby.STANDBY_BUCKET_WORKING_SET, + AppStandby.STANDBY_BUCKET_FREQUENT, + AppStandby.STANDBY_BUCKET_RARE + }; // To name the lock for stack traces static class Lock {} @@ -92,7 +119,7 @@ public class AppStandbyController { private AppIdleHistory mAppIdleHistory; @GuardedBy("mAppIdleLock") - private ArrayList<UsageStatsManagerInternal.AppIdleStateChangeListener> + private ArrayList<AppIdleStateChangeListener> mPackageAccessListeners = new ArrayList<>(); /** Whether we've queried the list of carrier privileged apps. */ @@ -118,6 +145,9 @@ public class AppStandbyController { long mAppIdleWallclockThresholdMillis; long mAppIdleParoleIntervalMillis; long mAppIdleParoleDurationMillis; + long[] mAppStandbyScreenThresholds = SCREEN_TIME_THRESHOLDS; + long[] mAppStandbyElapsedThresholds = ELAPSED_TIME_THRESHOLDS; + boolean mAppIdleEnabled; boolean mAppIdleTempParoled; boolean mCharging; @@ -129,20 +159,26 @@ public class AppStandbyController { private final Handler mHandler; private final Context mContext; - private DisplayManager mDisplayManager; - private IDeviceIdleController mDeviceIdleController; + // TODO: Provide a mechanism to set an external bucketing service + private boolean mUseInternalBucketingHeuristics = true; + private AppWidgetManager mAppWidgetManager; - private IBatteryStats mBatteryStats; private PowerManager mPowerManager; private PackageManager mPackageManager; - private PackageManagerInternal mPackageManagerInternal; + private Injector mInjector; + AppStandbyController(Context context, Looper looper) { - mContext = context; - mHandler = new AppStandbyHandler(looper); + this(new Injector(context, looper)); + } + + AppStandbyController(Injector injector) { + mInjector = injector; + mContext = mInjector.getContext(); + mHandler = new AppStandbyHandler(mInjector.getLooper()); mPackageManager = mContext.getPackageManager(); - mAppIdleEnabled = mContext.getResources().getBoolean( - com.android.internal.R.bool.config_enableAutoPowerModes); + mAppIdleEnabled = mInjector.isAppIdleEnabled(); + if (mAppIdleEnabled) { IntentFilter deviceStates = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); deviceStates.addAction(BatteryManager.ACTION_DISCHARGING); @@ -150,7 +186,8 @@ public class AppStandbyController { mContext.registerReceiver(new DeviceStateReceiver(), deviceStates); } synchronized (mAppIdleLock) { - mAppIdleHistory = new AppIdleHistory(SystemClock.elapsedRealtime()); + mAppIdleHistory = new AppIdleHistory(mInjector.getDataSystemDirectory(), + mInjector.elapsedRealtime()); } IntentFilter packageFilter = new IntentFilter(); @@ -164,6 +201,7 @@ public class AppStandbyController { } public void onBootPhase(int phase) { + mInjector.onBootPhase(phase); if (phase == PHASE_SYSTEM_SERVICES_READY) { // Observe changes to the threshold SettingsObserver settingsObserver = new SettingsObserver(mHandler); @@ -171,18 +209,11 @@ public class AppStandbyController { settingsObserver.updateSettings(); mAppWidgetManager = mContext.getSystemService(AppWidgetManager.class); - mDeviceIdleController = IDeviceIdleController.Stub.asInterface( - ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER)); - mBatteryStats = IBatteryStats.Stub.asInterface( - ServiceManager.getService(BatteryStats.SERVICE_NAME)); - mDisplayManager = (DisplayManager) mContext.getSystemService( - Context.DISPLAY_SERVICE); mPowerManager = mContext.getSystemService(PowerManager.class); - mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); - mDisplayManager.registerDisplayListener(mDisplayListener, mHandler); + mInjector.registerDisplayListener(mDisplayListener, mHandler); synchronized (mAppIdleLock) { - mAppIdleHistory.updateDisplay(isDisplayOn(), SystemClock.elapsedRealtime()); + mAppIdleHistory.updateDisplay(isDisplayOn(), mInjector.elapsedRealtime()); } if (mPendingOneTimeCheckIdleStates) { @@ -191,7 +222,7 @@ public class AppStandbyController { mSystemServicesReady = true; } else if (phase == PHASE_BOOT_COMPLETED) { - setChargingState(mContext.getSystemService(BatteryManager.class).isCharging()); + setChargingState(mInjector.isCharging()); } } @@ -229,7 +260,7 @@ public class AppStandbyController { /** Paroled here means temporary pardon from being inactive */ void setAppIdleParoled(boolean paroled) { synchronized (mAppIdleLock) { - final long now = System.currentTimeMillis(); + final long now = mInjector.currentTimeMillis(); if (mAppIdleTempParoled != paroled) { mAppIdleTempParoled = paroled; if (DEBUG) Slog.d(TAG, "Changing paroled to " + mAppIdleTempParoled); @@ -284,7 +315,7 @@ public class AppStandbyController { * scheduling a series of repeating checkIdleStates each time we fired off one. */ void postOneTimeCheckIdleStates() { - if (mDeviceIdleController == null) { + if (mInjector.getBootPhase() < PHASE_SYSTEM_SERVICES_READY) { // Not booted yet; wait for it! mPendingOneTimeCheckIdleStates = true; } else { @@ -304,7 +335,7 @@ public class AppStandbyController { final int[] runningUserIds; try { - runningUserIds = ActivityManager.getService().getRunningUserIds(); + runningUserIds = mInjector.getRunningUserIds(); if (checkUserId != UserHandle.USER_ALL && !ArrayUtils.contains(runningUserIds, checkUserId)) { return false; @@ -313,7 +344,7 @@ public class AppStandbyController { throw re.rethrowFromSystemServer(); } - final long elapsedRealtime = SystemClock.elapsedRealtime(); + final long elapsedRealtime = mInjector.elapsedRealtime(); for (int i = 0; i < runningUserIds.length; i++) { final int userId = runningUserIds[i]; if (checkUserId != UserHandle.USER_ALL && checkUserId != userId) { @@ -329,30 +360,71 @@ public class AppStandbyController { for (int p = 0; p < packageCount; p++) { final PackageInfo pi = packages.get(p); final String packageName = pi.packageName; - final boolean isIdle = isAppIdleFiltered(packageName, + final boolean isSpecial = isAppSpecial(packageName, UserHandle.getAppId(pi.applicationInfo.uid), - userId, elapsedRealtime); - mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS, - userId, isIdle ? 1 : 0, packageName)); - if (isIdle) { + userId); + if (DEBUG) { + Slog.d(TAG, " Checking idle state for " + packageName); + } + if (isSpecial) { + maybeInformListeners(packageName, userId, elapsedRealtime, false); + } else if (mUseInternalBucketingHeuristics) { synchronized (mAppIdleLock) { - mAppIdleHistory.setIdle(packageName, userId, elapsedRealtime); + int oldBucket = mAppIdleHistory.getAppStandbyBucket(packageName, userId, + elapsedRealtime); + String bucketingReason = mAppIdleHistory.getAppStandbyReason(packageName, + userId, elapsedRealtime); + if (bucketingReason != null + && (bucketingReason.equals(AppStandby.REASON_FORCED) + || bucketingReason.startsWith(AppStandby.REASON_PREDICTED))) { + continue; + } + int newBucket = getBucketForLocked(packageName, userId, + elapsedRealtime); + if (DEBUG) { + Slog.d(TAG, " Old bucket=" + oldBucket + + ", newBucket=" + newBucket); + } + if (oldBucket != newBucket) { + mAppIdleHistory.setAppStandbyBucket(packageName, userId, + elapsedRealtime, newBucket, AppStandby.REASON_TIMEOUT); + maybeInformListeners(packageName, userId, elapsedRealtime, + newBucket >= AppStandby.STANDBY_BUCKET_RARE); + } } } } } if (DEBUG) { Slog.d(TAG, "checkIdleStates took " - + (SystemClock.elapsedRealtime() - elapsedRealtime)); + + (mInjector.elapsedRealtime() - elapsedRealtime)); } return true; } + private void maybeInformListeners(String packageName, int userId, + long elapsedRealtime, boolean isIdle) { + synchronized (mAppIdleLock) { + if (mAppIdleHistory.shouldInformListeners(packageName, userId, + elapsedRealtime, isIdle)) { + mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS, + userId, isIdle ? 1 : 0, packageName)); + } + } + } + + @StandbyBuckets int getBucketForLocked(String packageName, int userId, + long elapsedRealtime) { + int bucketIndex = mAppIdleHistory.getThresholdIndex(packageName, userId, + elapsedRealtime, mAppStandbyScreenThresholds, mAppStandbyElapsedThresholds); + return THRESHOLD_BUCKETS[bucketIndex]; + } + /** Check if it's been a while since last parole and let idle apps do some work */ void checkParoleTimeout() { boolean setParoled = false; synchronized (mAppIdleLock) { - final long now = System.currentTimeMillis(); + final long now = mInjector.currentTimeMillis(); if (!mAppIdleTempParoled) { final long timeSinceLastParole = now - mLastAppIdleParoledTime; if (timeSinceLastParole > mAppIdleParoleIntervalMillis) { @@ -374,10 +446,10 @@ public class AppStandbyController { final int uid = mPackageManager.getPackageUidAsUser(packageName, PackageManager.MATCH_UNINSTALLED_PACKAGES, userId); if (idle) { - mBatteryStats.noteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_INACTIVE, + mInjector.noteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_INACTIVE, packageName, uid); } else { - mBatteryStats.noteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_ACTIVE, + mInjector.noteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_ACTIVE, packageName, uid); } } catch (PackageManager.NameNotFoundException | RemoteException e) { @@ -389,7 +461,7 @@ public class AppStandbyController { if (DEBUG) Slog.i(TAG, "DeviceIdleMode changed to " + deviceIdle); boolean paroled = false; synchronized (mAppIdleLock) { - final long timeSinceLastParole = System.currentTimeMillis() - mLastAppIdleParoledTime; + final long timeSinceLastParole = mInjector.currentTimeMillis() - mLastAppIdleParoledTime; if (!deviceIdle && timeSinceLastParole >= mAppIdleParoleIntervalMillis) { if (DEBUG) { @@ -419,13 +491,11 @@ public class AppStandbyController { || event.mEventType == UsageEvents.Event.USER_INTERACTION)) { mAppIdleHistory.reportUsage(event.mPackage, userId, elapsedRealtime); if (previouslyIdle) { - mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS, userId, - /* idle = */ 0, event.mPackage)); + maybeInformListeners(event.mPackage, userId, elapsedRealtime, false); notifyBatteryStats(event.mPackage, userId, false); } } } - } /** @@ -439,7 +509,7 @@ public class AppStandbyController { void forceIdleState(String packageName, int userId, boolean idle) { final int appId = getAppId(packageName); if (appId < 0) return; - final long elapsedRealtime = SystemClock.elapsedRealtime(); + final long elapsedRealtime = mInjector.elapsedRealtime(); final boolean previouslyIdle = isAppIdleFiltered(packageName, appId, userId, elapsedRealtime); @@ -470,7 +540,7 @@ public class AppStandbyController { } } - void addListener(UsageStatsManagerInternal.AppIdleStateChangeListener listener) { + void addListener(AppIdleStateChangeListener listener) { synchronized (mAppIdleLock) { if (!mPackageAccessListeners.contains(listener)) { mPackageAccessListeners.add(listener); @@ -478,7 +548,7 @@ public class AppStandbyController { } } - void removeListener(UsageStatsManagerInternal.AppIdleStateChangeListener listener) { + void removeListener(AppIdleStateChangeListener listener) { synchronized (mAppIdleLock) { mPackageAccessListeners.remove(listener); } @@ -501,75 +571,79 @@ public class AppStandbyController { return false; } if (shouldObfuscateInstantApps && - mPackageManagerInternal.isPackageEphemeral(userId, packageName)) { + mInjector.isPackageEphemeral(userId, packageName)) { return false; } return isAppIdleFiltered(packageName, getAppId(packageName), userId, elapsedRealtime); } - /** - * Checks if an app has been idle for a while and filters out apps that are excluded. - * It returns false if the current system state allows all apps to be considered active. - * This happens if the device is plugged in or temporarily allowed to make exceptions. - * Called by interface impls. - */ - boolean isAppIdleFiltered(String packageName, int appId, int userId, - long elapsedRealtime) { + /** Returns true if this app should be whitelisted for some reason, to never go into standby */ + boolean isAppSpecial(String packageName, int appId, int userId) { if (packageName == null) return false; // If not enabled at all, of course nobody is ever idle. if (!mAppIdleEnabled) { - return false; + return true; } if (appId < Process.FIRST_APPLICATION_UID) { // System uids never go idle. - return false; + return true; } 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; + return true; } if (mSystemServicesReady) { 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 // for controlling who participates vs. doze mode. - if (mDeviceIdleController.isPowerSaveWhitelistExceptIdleApp(packageName)) { - return false; + if (mInjector.isPowerSaveWhitelistExceptIdleApp(packageName)) { + return true; } } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } if (isActiveDeviceAdmin(packageName, userId)) { - return false; + return true; } if (isActiveNetworkScorer(packageName)) { - return false; + return true; } if (mAppWidgetManager != null - && mAppWidgetManager.isBoundWidgetPackage(packageName, userId)) { - return false; + && mInjector.isBoundWidgetPackage(mAppWidgetManager, packageName, userId)) { + return true; } if (isDeviceProvisioningPackage(packageName)) { - return false; + return true; } } - if (!isAppIdleUnfiltered(packageName, userId, elapsedRealtime)) { - return false; + // Check this last, as it can be the most expensive check + if (isCarrierApp(packageName)) { + return true; } - // Check this last, as it is the most expensive check - // TODO: Optimize this by fetching the carrier privileged apps ahead of time - if (isCarrierApp(packageName)) { + return false; + } + + /** + * Checks if an app has been idle for a while and filters out apps that are excluded. + * It returns false if the current system state allows all apps to be considered active. + * This happens if the device is plugged in or temporarily allowed to make exceptions. + * Called by interface impls. + */ + boolean isAppIdleFiltered(String packageName, int appId, int userId, + long elapsedRealtime) { + if (isAppSpecial(packageName, appId, userId)) { return false; + } else { + return isAppIdleUnfiltered(packageName, userId, elapsedRealtime); } - - return true; } int[] getIdleUidsForUser(int userId) { @@ -577,7 +651,7 @@ public class AppStandbyController { return new int[0]; } - final long elapsedRealtime = SystemClock.elapsedRealtime(); + final long elapsedRealtime = mInjector.elapsedRealtime(); List<ApplicationInfo> apps; try { @@ -613,7 +687,7 @@ public class AppStandbyController { } } if (DEBUG) { - Slog.d(TAG, "getIdleUids took " + (SystemClock.elapsedRealtime() - elapsedRealtime)); + Slog.d(TAG, "getIdleUids took " + (mInjector.elapsedRealtime() - elapsedRealtime)); } int numIdle = 0; for (int i = uidStates.size() - 1; i >= 0; i--) { @@ -643,6 +717,21 @@ public class AppStandbyController { .sendToTarget(); } + @StandbyBuckets int getAppStandbyBucket(String packageName, int userId, + long elapsedRealtime, boolean shouldObfuscateInstantApps) { + if (shouldObfuscateInstantApps && + mInjector.isPackageEphemeral(userId, packageName)) { + return AppStandby.STANDBY_BUCKET_ACTIVE; + } + + return mAppIdleHistory.getAppStandbyBucket(packageName, userId, elapsedRealtime); + } + + void setAppStandbyBucket(String packageName, int userId, @StandbyBuckets int newBucket, + String reason, long elapsedRealtime) { + mAppIdleHistory.setAppStandbyBucket(packageName, userId, elapsedRealtime, newBucket, reason); + } + private boolean isActiveDeviceAdmin(String packageName, int userId) { DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); if (dpm == null) return false; @@ -662,7 +751,7 @@ public class AppStandbyController { private boolean isCarrierApp(String packageName) { synchronized (mAppIdleLock) { if (!mHaveCarrierPrivilegedApps) { - fetchCarrierPrivilegedAppsLA(); + fetchCarrierPrivilegedAppsLocked(); } if (mCarrierPrivilegedApps != null) { return mCarrierPrivilegedApps.contains(packageName); @@ -682,7 +771,7 @@ public class AppStandbyController { } @GuardedBy("mAppIdleLock") - private void fetchCarrierPrivilegedAppsLA() { + private void fetchCarrierPrivilegedAppsLocked() { TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class); mCarrierPrivilegedApps = telephonyManager.getPackagesWithCarrierPrivileges(); @@ -693,20 +782,19 @@ public class AppStandbyController { } private boolean isActiveNetworkScorer(String packageName) { - NetworkScoreManager nsm = (NetworkScoreManager) mContext.getSystemService( - Context.NETWORK_SCORE_SERVICE); - return packageName != null && packageName.equals(nsm.getActiveScorerPackage()); + String activeScorer = mInjector.getActiveNetworkScorer(); + return packageName != null && packageName.equals(activeScorer); } void informListeners(String packageName, int userId, boolean isIdle) { - for (UsageStatsManagerInternal.AppIdleStateChangeListener listener : mPackageAccessListeners) { + for (AppIdleStateChangeListener listener : mPackageAccessListeners) { listener.onAppIdleStateChanged(packageName, userId, isIdle); } } void informParoleStateChanged() { final boolean paroled = isParoledOrCharging(); - for (UsageStatsManagerInternal.AppIdleStateChangeListener listener : mPackageAccessListeners) { + for (AppIdleStateChangeListener listener : mPackageAccessListeners) { listener.onParoleStateChanged(paroled); } } @@ -726,8 +814,7 @@ public class AppStandbyController { } boolean isDisplayOn() { - return mDisplayManager - .getDisplay(Display.DEFAULT_DISPLAY).getState() == Display.STATE_ON; + return mInjector.isDefaultDisplayOn(); } void clearAppIdleForPackage(String packageName, int userId) { @@ -755,7 +842,7 @@ public class AppStandbyController { void initializeDefaultsForSystemApps(int userId) { Slog.d(TAG, "Initializing defaults for system apps on user " + userId); - final long elapsedRealtime = SystemClock.elapsedRealtime(); + final long elapsedRealtime = mInjector.elapsedRealtime(); List<PackageInfo> packages = mPackageManager.getInstalledPackagesAsUser( PackageManager.MATCH_DISABLED_COMPONENTS, userId); @@ -786,9 +873,9 @@ public class AppStandbyController { } } - void dumpUser(IndentingPrintWriter idpw, int userId) { + void dumpUser(IndentingPrintWriter idpw, int userId, String pkg) { synchronized (mAppIdleLock) { - mAppIdleHistory.dump(idpw, userId); + mAppIdleHistory.dump(idpw, userId, pkg); } } @@ -828,6 +915,116 @@ public class AppStandbyController { pw.print(" mLastAppIdleParoledTime="); TimeUtils.formatDuration(mLastAppIdleParoledTime, pw); pw.println(); + pw.print("mScreenThresholds="); pw.println(Arrays.toString(mAppStandbyScreenThresholds)); + pw.print("mElapsedThresholds="); pw.println(Arrays.toString(mAppStandbyElapsedThresholds)); + } + + /** + * Injector for interaction with external code. Override methods to provide a mock + * implementation for tests. + * onBootPhase() must be called with at least the PHASE_SYSTEM_SERVICES_READY + */ + static class Injector { + + private final Context mContext; + private final Looper mLooper; + private IDeviceIdleController mDeviceIdleController; + private IBatteryStats mBatteryStats; + private PackageManagerInternal mPackageManagerInternal; + private DisplayManager mDisplayManager; + int mBootPhase; + + Injector(Context context, Looper looper) { + mContext = context; + mLooper = looper; + } + + Context getContext() { + return mContext; + } + + Looper getLooper() { + return mLooper; + } + + void onBootPhase(int phase) { + if (phase == PHASE_SYSTEM_SERVICES_READY) { + mDeviceIdleController = IDeviceIdleController.Stub.asInterface( + ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER)); + mBatteryStats = IBatteryStats.Stub.asInterface( + ServiceManager.getService(BatteryStats.SERVICE_NAME)); + mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); + mDisplayManager = (DisplayManager) mContext.getSystemService( + Context.DISPLAY_SERVICE); + } + mBootPhase = phase; + } + + int getBootPhase() { + return mBootPhase; + } + + /** + * Returns the elapsed realtime since the device started. Override this + * to control the clock. + * @return elapsed realtime + */ + long elapsedRealtime() { + return SystemClock.elapsedRealtime(); + } + + long currentTimeMillis() { + return System.currentTimeMillis(); + } + + boolean isAppIdleEnabled() { + return mContext.getResources().getBoolean( + com.android.internal.R.bool.config_enableAutoPowerModes); + } + + boolean isCharging() { + return mContext.getSystemService(BatteryManager.class).isCharging(); + } + + boolean isPowerSaveWhitelistExceptIdleApp(String packageName) throws RemoteException { + return mDeviceIdleController.isPowerSaveWhitelistExceptIdleApp(packageName); + } + + File getDataSystemDirectory() { + return Environment.getDataSystemDirectory(); + } + + void noteEvent(int event, String packageName, int uid) throws RemoteException { + mBatteryStats.noteEvent(event, packageName, uid); + } + + boolean isPackageEphemeral(int userId, String packageName) { + return mPackageManagerInternal.isPackageEphemeral(userId, packageName); + } + + int[] getRunningUserIds() throws RemoteException { + return ActivityManager.getService().getRunningUserIds(); + } + + boolean isDefaultDisplayOn() { + return mDisplayManager + .getDisplay(Display.DEFAULT_DISPLAY).getState() == Display.STATE_ON; + } + + void registerDisplayListener(DisplayManager.DisplayListener listener, Handler handler) { + mDisplayManager.registerDisplayListener(listener, handler); + } + + String getActiveNetworkScorer() { + NetworkScoreManager nsm = (NetworkScoreManager) mContext.getSystemService( + Context.NETWORK_SCORE_SERVICE); + return nsm.getActiveScorerPackage(); + } + + public boolean isBoundWidgetPackage(AppWidgetManager appWidgetManager, String packageName, + int userId) { + return appWidgetManager.isBoundWidgetPackage(packageName, userId); + } } class AppStandbyHandler extends Handler { @@ -839,6 +1036,10 @@ public class AppStandbyController { @Override public void handleMessage(Message msg) { switch (msg.what) { + case MSG_INFORM_LISTENERS: + informListeners((String) msg.obj, msg.arg1, msg.arg2 == 1); + break; + case MSG_FORCE_IDLE_STATE: forceIdleState((String) msg.obj, msg.arg1, msg.arg2 == 1); break; @@ -911,7 +1112,7 @@ public class AppStandbyController { if (displayId == Display.DEFAULT_DISPLAY) { final boolean displayOn = isDisplayOn(); synchronized (mAppIdleLock) { - mAppIdleHistory.updateDisplay(displayOn, SystemClock.elapsedRealtime()); + mAppIdleHistory.updateDisplay(displayOn, mInjector.elapsedRealtime()); } } } @@ -931,6 +1132,8 @@ public class AppStandbyController { private static final String KEY_WALLCLOCK_THRESHOLD = "wallclock_threshold"; private static final String KEY_PAROLE_INTERVAL = "parole_interval"; private static final String KEY_PAROLE_DURATION = "parole_duration"; + private static final String KEY_SCREEN_TIME_THRESHOLDS = "screen_thresholds"; + private static final String KEY_ELAPSED_TIME_THRESHOLDS = "elapsed_thresholds"; private final KeyValueListParser mParser = new KeyValueListParser(','); @@ -969,7 +1172,7 @@ public class AppStandbyController { COMPRESS_TIME ? ONE_MINUTE * 8 : 2L * 24 * 60 * ONE_MINUTE); // 2 days mCheckIdleIntervalMillis = Math.min(mAppIdleScreenThresholdMillis / 4, - COMPRESS_TIME ? ONE_MINUTE : 8 * 60 * ONE_MINUTE); // 8 hours + COMPRESS_TIME ? ONE_MINUTE : 4 * 60 * ONE_MINUTE); // 4 hours // Default: 24 hours between paroles mAppIdleParoleIntervalMillis = mParser.getLong(KEY_PAROLE_INTERVAL, @@ -979,9 +1182,35 @@ public class AppStandbyController { COMPRESS_TIME ? ONE_MINUTE : 10 * ONE_MINUTE); // 10 minutes mAppIdleHistory.setThresholds(mAppIdleWallclockThresholdMillis, mAppIdleScreenThresholdMillis); + + String screenThresholdsValue = mParser.getString(KEY_SCREEN_TIME_THRESHOLDS, null); + mAppStandbyScreenThresholds = parseLongArray(screenThresholdsValue, + SCREEN_TIME_THRESHOLDS); + + String elapsedThresholdsValue = mParser.getString(KEY_ELAPSED_TIME_THRESHOLDS, null); + mAppStandbyElapsedThresholds = parseLongArray(elapsedThresholdsValue, + ELAPSED_TIME_THRESHOLDS); } } - } + long[] parseLongArray(String values, long[] defaults) { + if (values == null) return defaults; + if (values.isEmpty()) { + // Reset to defaults + return defaults; + } else { + String[] thresholds = values.split("/"); + if (thresholds.length == THRESHOLD_BUCKETS.length) { + long[] array = new long[THRESHOLD_BUCKETS.length]; + for (int i = 0; i < THRESHOLD_BUCKETS.length; i++) { + array[i] = Long.parseLong(thresholds[i]); + } + return array; + } else { + return defaults; + } + } + } + } } diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index afafea19afd0..44e6a6cb7459 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -20,6 +20,7 @@ import android.Manifest; import android.app.ActivityManager; import android.app.AppOpsManager; import android.app.IUidObserver; +import android.app.usage.AppStandby; import android.app.usage.ConfigurationStats; import android.app.usage.IUsageStatsManager; import android.app.usage.UsageEvents; @@ -469,8 +470,32 @@ public class UsageStatsService extends SystemService implements void dump(String[] args, PrintWriter pw) { synchronized (mLock) { IndentingPrintWriter idpw = new IndentingPrintWriter(pw, " "); - ArraySet<String> argSet = new ArraySet<>(); - argSet.addAll(Arrays.asList(args)); + + boolean checkin = false; + boolean history = false; + String pkg = null; + + if (args != null) { + for (int i = 0; i < args.length; i++) { + String arg = args[i]; + if ("--checkin".equals(arg)) { + checkin = true; + } else if ("--history".equals(arg)) { + history = true; + } else if ("history".equals(arg)) { + history = true; + break; + } else if ("flush".equals(arg)) { + flushToDiskLocked(); + pw.println("Flushed stats to disk"); + return; + } else { + // Anything else is a pkg to filter + pkg = arg; + break; + } + } + } final int userCount = mUserState.size(); for (int i = 0; i < userCount; i++) { @@ -478,26 +503,23 @@ public class UsageStatsService extends SystemService implements idpw.printPair("user", userId); idpw.println(); idpw.increaseIndent(); - if (argSet.contains("--checkin")) { + if (checkin) { mUserState.valueAt(i).checkin(idpw); } else { - mUserState.valueAt(i).dump(idpw); + mUserState.valueAt(i).dump(idpw, pkg); idpw.println(); - if (args.length > 0) { - if ("history".equals(args[0])) { - mAppStandby.dumpHistory(idpw, userId); - } else if ("flush".equals(args[0])) { - flushToDiskLocked(); - pw.println("Flushed stats to disk"); - } + if (history) { + mAppStandby.dumpHistory(idpw, userId); } } - mAppStandby.dumpUser(idpw, userId); + mAppStandby.dumpUser(idpw, userId, pkg); idpw.decreaseIndent(); } - pw.println(); - mAppStandby.dumpState(args, pw); + if (pkg == null) { + pw.println(); + mAppStandby.dumpState(args, pw); + } } } @@ -654,6 +676,55 @@ public class UsageStatsService extends SystemService implements } @Override + public int getAppStandbyBucket(String packageName, String callingPackage, int userId) { + if (!hasPermission(callingPackage)) { + throw new SecurityException("Don't have permission to query app standby bucket"); + } + + final int callingUid = Binder.getCallingUid(); + try { + userId = ActivityManager.getService().handleIncomingUser( + Binder.getCallingPid(), callingUid, userId, false, true, + "getAppStandbyBucket", null); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + final boolean obfuscateInstantApps = shouldObfuscateInstantAppsForCaller(callingUid, + userId); + final long token = Binder.clearCallingIdentity(); + try { + return mAppStandby.getAppStandbyBucket(packageName, userId, + SystemClock.elapsedRealtime(), obfuscateInstantApps); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public void setAppStandbyBucket(String packageName, + int bucket, int userId) { + getContext().enforceCallingPermission(Manifest.permission.CHANGE_APP_IDLE_STATE, + "No permission to change app standby state"); + + final int callingUid = Binder.getCallingUid(); + try { + userId = ActivityManager.getService().handleIncomingUser( + Binder.getCallingPid(), callingUid, userId, false, true, + "setAppStandbyBucket", null); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + final long token = Binder.clearCallingIdentity(); + try { + mAppStandby.setAppStandbyBucket(packageName, userId, bucket, + AppStandby.REASON_PREDICTED + ":" + callingUid, + SystemClock.elapsedRealtime()); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override public void whitelistAppTemporarily(String packageName, long duration, int userId) throws RemoteException { StringBuilder reason = new StringBuilder(32); diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java index 0abbb82a68a1..0b1059045b5b 100644 --- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java @@ -474,19 +474,19 @@ class UserUsageStatsService { mDatabase.checkinDailyFiles(new UsageStatsDatabase.CheckinAction() { @Override public boolean checkin(IntervalStats stats) { - printIntervalStats(pw, stats, false); + printIntervalStats(pw, stats, false, null); return true; } }); } - void dump(IndentingPrintWriter pw) { + void dump(IndentingPrintWriter pw, String pkg) { // This is not a check-in, only dump in-memory stats. for (int interval = 0; interval < mCurrentStats.length; interval++) { pw.print("In-memory "); pw.print(intervalToString(interval)); pw.println(" stats"); - printIntervalStats(pw, mCurrentStats[interval], true); + printIntervalStats(pw, mCurrentStats[interval], true, pkg); } } @@ -505,7 +505,7 @@ class UserUsageStatsService { } void printIntervalStats(IndentingPrintWriter pw, IntervalStats stats, - boolean prettyDates) { + boolean prettyDates, String pkg) { if (prettyDates) { pw.printPair("timeRange", "\"" + DateUtils.formatDateRange(mContext, stats.beginTime, stats.endTime, sDateFormatFlags) + "\""); @@ -521,6 +521,9 @@ class UserUsageStatsService { final int pkgCount = pkgStats.size(); for (int i = 0; i < pkgCount; i++) { final UsageStats usageStats = pkgStats.valueAt(i); + if (pkg != null && !pkg.equals(usageStats.mPackageName)) { + continue; + } pw.printPair("package", usageStats.mPackageName); pw.printPair("totalTime", formatElapsedTime(usageStats.mTotalTimeInForeground, prettyDates)); @@ -533,6 +536,9 @@ class UserUsageStatsService { pw.println("ChooserCounts"); pw.increaseIndent(); for (UsageStats usageStats : pkgStats.values()) { + if (pkg != null && !pkg.equals(usageStats.mPackageName)) { + continue; + } pw.printPair("package", usageStats.mPackageName); if (usageStats.mChooserCounts != null) { final int chooserCountSize = usageStats.mChooserCounts.size(); @@ -555,19 +561,22 @@ class UserUsageStatsService { } pw.decreaseIndent(); - pw.println("configurations"); - pw.increaseIndent(); - final ArrayMap<Configuration, ConfigurationStats> configStats = stats.configurations; - final int configCount = configStats.size(); - for (int i = 0; i < configCount; i++) { - final ConfigurationStats config = configStats.valueAt(i); - pw.printPair("config", Configuration.resourceQualifierString(config.mConfiguration)); - pw.printPair("totalTime", formatElapsedTime(config.mTotalTimeActive, prettyDates)); - pw.printPair("lastTime", formatDateTime(config.mLastTimeActive, prettyDates)); - pw.printPair("count", config.mActivationCount); - pw.println(); + if (pkg == null) { + pw.println("configurations"); + pw.increaseIndent(); + final ArrayMap<Configuration, ConfigurationStats> configStats = stats.configurations; + final int configCount = configStats.size(); + for (int i = 0; i < configCount; i++) { + final ConfigurationStats config = configStats.valueAt(i); + pw.printPair("config", Configuration.resourceQualifierString( + config.mConfiguration)); + pw.printPair("totalTime", formatElapsedTime(config.mTotalTimeActive, prettyDates)); + pw.printPair("lastTime", formatDateTime(config.mLastTimeActive, prettyDates)); + pw.printPair("count", config.mActivationCount); + pw.println(); + } + pw.decreaseIndent(); } - pw.decreaseIndent(); pw.println("events"); pw.increaseIndent(); @@ -575,6 +584,9 @@ class UserUsageStatsService { final int eventCount = events != null ? events.size() : 0; for (int i = 0; i < eventCount; i++) { final UsageEvents.Event event = events.valueAt(i); + if (pkg != null && !pkg.equals(event.mPackage)) { + continue; + } pw.printPair("time", formatDateTime(event.mTimeStamp, prettyDates)); pw.printPair("type", eventToString(event.mEventType)); pw.printPair("package", event.mPackage); diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index de980b2ffec2..47b0f79b29fc 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -1419,6 +1419,17 @@ public class CarrierConfigManager { "support_3gpp_call_forwarding_while_roaming_bool"; /** + * Boolean indicating whether to display voicemail number as default call forwarding number in + * call forwarding settings. + * If true, display vm number when cf number is null. + * If false, display the cf number from network. + * By default this value is false. + * @hide + */ + public static final String KEY_DISPLAY_VOICEMAIL_NUMBER_AS_DEFAULT_CALL_FORWARDING_NUMBER_BOOL = + "display_voicemail_number_as_default_call_forwarding_number"; + + /** * When {@code true}, the user will be notified when they attempt to place an international call * when the call is placed using wifi calling. * @hide @@ -1604,6 +1615,13 @@ public class CarrierConfigManager { public static final String KEY_SKIP_CF_FAIL_TO_DISABLE_DIALOG_BOOL = "skip_cf_fail_to_disable_dialog_bool"; + /** + * List of the FAC (feature access codes) to dial as a normal call. + * @hide + */ + public static final String KEY_FEATURE_ACCESS_CODES_STRING_ARRAY = + "feature_access_codes_string_array"; + /** The default value for every variable. */ private final static PersistableBundle sDefaults; @@ -1860,6 +1878,8 @@ public class CarrierConfigManager { sDefaults.putInt(KEY_EMERGENCY_NOTIFICATION_DELAY_INT, -1); sDefaults.putBoolean(KEY_ALLOW_USSD_REQUESTS_VIA_TELEPHONY_MANAGER_BOOL, true); sDefaults.putBoolean(KEY_SUPPORT_3GPP_CALL_FORWARDING_WHILE_ROAMING_BOOL, true); + sDefaults.putBoolean(KEY_DISPLAY_VOICEMAIL_NUMBER_AS_DEFAULT_CALL_FORWARDING_NUMBER_BOOL, + false); sDefaults.putBoolean(KEY_NOTIFY_INTERNATIONAL_CALL_ON_WFC_BOOL, false); sDefaults.putStringArray(KEY_CALL_FORWARDING_BLOCKS_WHILE_ROAMING_STRING_ARRAY, null); @@ -1874,6 +1894,7 @@ public class CarrierConfigManager { sDefaults.putStringArray(KEY_ROAMING_OPERATOR_STRING_ARRAY, null); sDefaults.putBoolean(KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL, false); sDefaults.putBoolean(KEY_DISABLE_CHARGE_INDICATION_BOOL, false); + sDefaults.putStringArray(KEY_FEATURE_ACCESS_CODES_STRING_ARRAY, null); } /** diff --git a/telephony/java/android/telephony/DisconnectCause.java b/telephony/java/android/telephony/DisconnectCause.java index 98fb65343485..c3a2ceb1a344 100644 --- a/telephony/java/android/telephony/DisconnectCause.java +++ b/telephony/java/android/telephony/DisconnectCause.java @@ -273,6 +273,13 @@ public class DisconnectCause { * {@hide} */ public static final int EMERGENCY_PERM_FAILURE = 64; + + /** + * This cause is used to report a normal event only when no other cause in the normal class + * applies. + * {@hide} + */ + public static final int NORMAL_UNSPECIFIED = 65; //********************************************************************************************* // When adding a disconnect type: // 1) Update toString() with the newly added disconnect type. @@ -413,6 +420,8 @@ public class DisconnectCause { return "EMERGENCY_TEMP_FAILURE"; case EMERGENCY_PERM_FAILURE: return "EMERGENCY_PERM_FAILURE"; + case NORMAL_UNSPECIFIED: + return "NORMAL_UNSPECIFIED"; default: return "INVALID: " + cause; } diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java index 6029995f2468..98195ada16f0 100644 --- a/telephony/java/android/telephony/SmsManager.java +++ b/telephony/java/android/telephony/SmsManager.java @@ -390,20 +390,23 @@ public final class SmsManager { * Inject an SMS PDU into the android application framework. * * <p>Requires permission: {@link android.Manifest.permission#MODIFY_PHONE_STATE} or carrier - * privileges. @see android.telephony.TelephonyManager#hasCarrierPrivileges + * privileges per {@link android.telephony.TelephonyManager#hasCarrierPrivileges}. * * @param pdu is the byte array of pdu to be injected into android application framework - * @param format is the format of SMS pdu (3gpp or 3gpp2) + * @param format is the format of SMS pdu ({@link SmsMessage#FORMAT_3GPP} or + * {@link SmsMessage#FORMAT_3GPP2}) * @param receivedIntent if not NULL this <code>PendingIntent</code> is * broadcast when the message is successfully received by the * android application framework, or failed. This intent is broadcasted at * the same time an SMS received from radio is acknowledged back. - * The result code will be <code>RESULT_SMS_HANDLED</code> for success, or - * <code>RESULT_SMS_GENERIC_ERROR</code> for error. + * The result code will be {@link android.provider.Telephony.Sms.Intents#RESULT_SMS_HANDLED} + * for success, or {@link android.provider.Telephony.Sms.Intents#RESULT_SMS_GENERIC_ERROR} for + * error. * - * @throws IllegalArgumentException if format is not one of 3gpp and 3gpp2. + * @throws IllegalArgumentException if the format is invalid. */ - public void injectSmsPdu(byte[] pdu, String format, PendingIntent receivedIntent) { + public void injectSmsPdu( + byte[] pdu, @SmsMessage.Format String format, PendingIntent receivedIntent) { if (!format.equals(SmsMessage.FORMAT_3GPP) && !format.equals(SmsMessage.FORMAT_3GPP2)) { // Format must be either 3gpp or 3gpp2. throw new IllegalArgumentException( diff --git a/telephony/java/android/telephony/SmsMessage.java b/telephony/java/android/telephony/SmsMessage.java index dcdda8685e02..df41233559a1 100644 --- a/telephony/java/android/telephony/SmsMessage.java +++ b/telephony/java/android/telephony/SmsMessage.java @@ -16,24 +16,25 @@ package android.telephony; -import android.os.Binder; -import android.os.Parcel; +import static android.telephony.TelephonyManager.PHONE_TYPE_CDMA; + +import android.annotation.StringDef; import android.content.res.Resources; +import android.os.Binder; import android.text.TextUtils; import com.android.internal.telephony.GsmAlphabet; import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails; +import com.android.internal.telephony.Sms7BitEncodingTranslator; import com.android.internal.telephony.SmsConstants; import com.android.internal.telephony.SmsMessageBase; import com.android.internal.telephony.SmsMessageBase.SubmitPduBase; -import com.android.internal.telephony.Sms7BitEncodingTranslator; -import java.lang.Math; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; -import static android.telephony.TelephonyManager.PHONE_TYPE_CDMA; - /** * A Short Message Service message. @@ -81,15 +82,18 @@ public class SmsMessage { */ public static final int MAX_USER_DATA_SEPTETS_WITH_HEADER = 153; + /** @hide */ + @StringDef({FORMAT_3GPP, FORMAT_3GPP2}) + @Retention(RetentionPolicy.SOURCE) + public @interface Format {} + /** * Indicates a 3GPP format SMS message. - * @hide pending API council approval */ public static final String FORMAT_3GPP = "3gpp"; /** * Indicates a 3GPP2 format SMS message. - * @hide pending API council approval */ public static final String FORMAT_3GPP2 = "3gpp2"; diff --git a/test-runner/Android.mk b/test-runner/Android.mk index 060a51895474..29a95e65dbf3 100644 --- a/test-runner/Android.mk +++ b/test-runner/Android.mk @@ -106,9 +106,11 @@ LOCAL_JAVA_LIBRARIES := \ android.test.mock.stubs \ LOCAL_SOURCE_FILES_ALL_GENERATED := true +LOCAL_SDK_VERSION := current # Make sure to run droiddoc first to generate the stub source files. LOCAL_ADDITIONAL_DEPENDENCIES := $(android_test_runner_api_gen_stamp) +android_test_runner_api_gen_stamp := include $(BUILD_STATIC_JAVA_LIBRARY) @@ -203,6 +205,7 @@ LOCAL_SOURCE_FILES_ALL_GENERATED := true # Make sure to run droiddoc first to generate the stub source files. LOCAL_ADDITIONAL_DEPENDENCIES := $(android_test_mock_gen_stamp) +android_test_mock_gen_stamp := include $(BUILD_STATIC_JAVA_LIBRARY) @@ -246,6 +249,7 @@ update-android-test-mock-api: $(ANDROID_TEST_MOCK_OUTPUT_API_FILE) | $(ACP) include $(CLEAR_VARS) LOCAL_MODULE := android.test.mock.sdk +LOCAL_SDK_VERSION := current LOCAL_STATIC_JAVA_LIBRARIES := android.test.mock.stubs diff --git a/tests/net/java/android/net/util/VersionedBroadcastListenerTest.java b/tests/net/java/android/net/util/VersionedBroadcastListenerTest.java new file mode 100644 index 000000000000..39f59f1c0d69 --- /dev/null +++ b/tests/net/java/android/net/util/VersionedBroadcastListenerTest.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2017 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.net.util; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.reset; + +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Handler; +import android.os.Looper; +import android.os.UserHandle; + +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import com.android.internal.util.test.BroadcastInterceptingContext; + +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.runner.RunWith; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class VersionedBroadcastListenerTest { + private static final String TAG = VersionedBroadcastListenerTest.class.getSimpleName(); + private static final String ACTION_TEST = "action.test.happy.broadcasts"; + + @Mock private Context mContext; + private BroadcastInterceptingContext mServiceContext; + private Handler mHandler; + private VersionedBroadcastListener mListener; + private int mCallbackCount; + + private void doCallback() { mCallbackCount++; } + + private class MockContext extends BroadcastInterceptingContext { + MockContext(Context base) { + super(base); + } + } + + @BeforeClass + public static void setUpBeforeClass() throws Exception { + if (Looper.myLooper() == null) { + Looper.prepare(); + } + } + + @Before public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + reset(mContext); + mServiceContext = new MockContext(mContext); + mHandler = new Handler(Looper.myLooper()); + mCallbackCount = 0; + final IntentFilter filter = new IntentFilter(); + filter.addAction(ACTION_TEST); + mListener = new VersionedBroadcastListener( + TAG, mServiceContext, mHandler, filter, (Intent intent) -> doCallback()); + } + + @After public void tearDown() throws Exception { + if (mListener != null) { + mListener.stopListening(); + mListener = null; + } + } + + private void sendBroadcast() { + final Intent intent = new Intent(ACTION_TEST); + mServiceContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL); + } + + @Test + public void testBasicListening() { + assertEquals(0, mCallbackCount); + mListener.startListening(); + for (int i = 0; i < 5; i++) { + sendBroadcast(); + assertEquals(i+1, mCallbackCount); + } + mListener.stopListening(); + } + + @Test + public void testBroadcastsBeforeStartAreIgnored() { + assertEquals(0, mCallbackCount); + for (int i = 0; i < 5; i++) { + sendBroadcast(); + assertEquals(0, mCallbackCount); + } + + mListener.startListening(); + sendBroadcast(); + assertEquals(1, mCallbackCount); + } + + @Test + public void testBroadcastsAfterStopAreIgnored() { + mListener.startListening(); + sendBroadcast(); + assertEquals(1, mCallbackCount); + mListener.stopListening(); + + for (int i = 0; i < 5; i++) { + sendBroadcast(); + assertEquals(1, mCallbackCount); + } + } +} diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index c2cb66d5e60a..27a29b68e12d 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -104,6 +104,8 @@ import android.util.LogPrinter; import com.android.internal.util.WakeupMessage; import com.android.internal.util.test.BroadcastInterceptingContext; import com.android.internal.util.test.FakeSettingsProvider; +import com.android.server.connectivity.DefaultNetworkMetrics; +import com.android.server.connectivity.IpConnectivityMetrics; import com.android.server.connectivity.MockableSystemProperties; import com.android.server.connectivity.NetworkAgentInfo; import com.android.server.connectivity.NetworkMonitor; @@ -156,6 +158,9 @@ public class ConnectivityServiceTest { private MockNetworkAgent mEthernetNetworkAgent; private Context mContext; + @Mock IpConnectivityMetrics.Logger mMetricsService; + @Mock DefaultNetworkMetrics mDefaultNetworkMetrics; + // This class exists to test bindProcessToNetwork and getBoundNetworkForProcess. These methods // do not go through ConnectivityService but talk to netd directly, so they don't automatically // reflect the state of our test ConnectivityService. @@ -805,6 +810,11 @@ public class ConnectivityServiceTest { return Context.ETHERNET_SERVICE.equals(name); } + @Override + protected IpConnectivityMetrics.Logger metricsLogger() { + return mMetricsService; + } + public WrappedNetworkMonitor getLastCreatedWrappedNetworkMonitor() { return mLastCreatedNetworkMonitor; } @@ -833,6 +843,9 @@ public class ConnectivityServiceTest { public void setUp() throws Exception { mContext = InstrumentationRegistry.getContext(); + MockitoAnnotations.initMocks(this); + when(mMetricsService.defaultNetworkMetrics()).thenReturn(mDefaultNetworkMetrics); + // InstrumentationTestRunner prepares a looper, but AndroidJUnitRunner does not. // http://b/25897652 . if (Looper.myLooper() == null) { diff --git a/tests/net/java/com/android/server/IpSecServiceTest.java b/tests/net/java/com/android/server/IpSecServiceTest.java index 83ee361aa2e3..7b0703851441 100644 --- a/tests/net/java/com/android/server/IpSecServiceTest.java +++ b/tests/net/java/com/android/server/IpSecServiceTest.java @@ -21,6 +21,7 @@ import static android.system.OsConstants.EADDRINUSE; import static android.system.OsConstants.IPPROTO_UDP; import static android.system.OsConstants.SOCK_DGRAM; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; @@ -174,6 +175,7 @@ public class IpSecServiceTest { mIpSecService.openUdpEncapsulationSocket(0, new Binder()); assertNotNull(udpEncapResp); assertEquals(IpSecManager.Status.OK, udpEncapResp.status); + assertNotEquals(0, udpEncapResp.port); mIpSecService.closeUdpEncapsulationSocket(udpEncapResp.resourceId); udpEncapResp.fileDescriptor.close(); } diff --git a/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java b/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java index 262417620ca2..ad6ebf933776 100644 --- a/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java +++ b/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java @@ -198,21 +198,20 @@ public class IpConnectivityEventBuilderTest { @Test public void testDefaultNetworkEventSerialization() { - ConnectivityMetricsEvent ev = describeIpEvent( - aType(DefaultNetworkEvent.class), - anInt(102), - anIntArray(1, 2, 3), - anInt(101), - aBool(true), - aBool(false)); + DefaultNetworkEvent ev = new DefaultNetworkEvent(); + ev.netId = 102; + ev.prevNetId = 101; + ev.transportTypes = new int[]{1, 2, 3}; + ev.prevIPv4 = true; + ev.prevIPv6 = true; String want = String.join("\n", "dropped_events: 0", "events <", " if_name: \"\"", " link_layer: 0", - " network_id: 0", - " time_ms: 1", + " network_id: 102", + " time_ms: 0", " transports: 0", " default_network_event <", " default_network_duration_ms: 0", @@ -226,7 +225,7 @@ public class IpConnectivityEventBuilderTest { " previous_network_id <", " network_id: 101", " >", - " previous_network_ip_support: 1", + " previous_network_ip_support: 3", " transport_types: 1", " transport_types: 2", " transport_types: 3", @@ -234,7 +233,7 @@ public class IpConnectivityEventBuilderTest { ">", "version: 2\n"); - verifySerialization(want, ev); + verifySerialization(want, IpConnectivityEventBuilder.toProto(ev)); } @Test diff --git a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java index a395c480f57a..6c1decc3b3b8 100644 --- a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java +++ b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java @@ -18,6 +18,7 @@ package com.android.server.connectivity; import static android.net.metrics.INetdEventListener.EVENT_GETADDRINFO; import static android.net.metrics.INetdEventListener.EVENT_GETHOSTBYNAME; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -30,6 +31,10 @@ import android.content.Context; import android.net.ConnectivityManager; import android.net.ConnectivityMetricsEvent; import android.net.IIpConnectivityMetrics; +import android.net.IpPrefix; +import android.net.LinkAddress; +import android.net.LinkProperties; +import android.net.RouteInfo; import android.net.Network; import android.net.NetworkCapabilities; import android.net.metrics.ApfProgramEvent; @@ -41,18 +46,22 @@ import android.net.metrics.IpManagerEvent; import android.net.metrics.IpReachabilityEvent; import android.net.metrics.RaEvent; import android.net.metrics.ValidationProbeEvent; -import android.system.OsConstants; import android.os.Parcelable; import android.support.test.runner.AndroidJUnit4; +import android.system.OsConstants; import android.test.suitebuilder.annotation.SmallTest; import android.util.Base64; + +import com.android.internal.util.BitUtils; import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass; + import java.io.PrintWriter; import java.io.StringWriter; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; + import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -162,6 +171,144 @@ public class IpConnectivityMetricsTest { } @Test + public void testDefaultNetworkEvents() throws Exception { + final long cell = BitUtils.packBits(new int[]{NetworkCapabilities.TRANSPORT_CELLULAR}); + final long wifi = BitUtils.packBits(new int[]{NetworkCapabilities.TRANSPORT_WIFI}); + + NetworkAgentInfo[][] defaultNetworks = { + // nothing -> cell + {null, makeNai(100, 10, false, true, cell)}, + // cell -> wifi + {makeNai(100, 50, true, true, cell), makeNai(101, 20, true, false, wifi)}, + // wifi -> nothing + {makeNai(101, 60, true, false, wifi), null}, + // nothing -> cell + {null, makeNai(102, 10, true, true, cell)}, + // cell -> wifi + {makeNai(102, 50, true, true, cell), makeNai(103, 20, true, false, wifi)}, + }; + + for (NetworkAgentInfo[] pair : defaultNetworks) { + mService.mDefaultNetworkMetrics.logDefaultNetworkEvent(pair[1], pair[0]); + } + + String want = String.join("\n", + "dropped_events: 0", + "events <", + " if_name: \"\"", + " link_layer: 0", + " network_id: 100", + " time_ms: 0", + " transports: 0", + " default_network_event <", + " default_network_duration_ms: 0", + " final_score: 0", + " initial_score: 0", + " ip_support: 0", + " network_id <", + " network_id: 100", + " >", + " no_default_network_duration_ms: 0", + " previous_network_id <", + " network_id: 0", + " >", + " previous_network_ip_support: 0", + " transport_types: 0", + " >", + ">", + "events <", + " if_name: \"\"", + " link_layer: 0", + " network_id: 101", + " time_ms: 0", + " transports: 0", + " default_network_event <", + " default_network_duration_ms: 0", + " final_score: 0", + " initial_score: 0", + " ip_support: 0", + " network_id <", + " network_id: 101", + " >", + " no_default_network_duration_ms: 0", + " previous_network_id <", + " network_id: 100", + " >", + " previous_network_ip_support: 3", + " transport_types: 1", + " >", + ">", + "events <", + " if_name: \"\"", + " link_layer: 0", + " network_id: 0", + " time_ms: 0", + " transports: 0", + " default_network_event <", + " default_network_duration_ms: 0", + " final_score: 0", + " initial_score: 0", + " ip_support: 0", + " network_id <", + " network_id: 0", + " >", + " no_default_network_duration_ms: 0", + " previous_network_id <", + " network_id: 101", + " >", + " previous_network_ip_support: 1", + " >", + ">", + "events <", + " if_name: \"\"", + " link_layer: 0", + " network_id: 102", + " time_ms: 0", + " transports: 0", + " default_network_event <", + " default_network_duration_ms: 0", + " final_score: 0", + " initial_score: 0", + " ip_support: 0", + " network_id <", + " network_id: 102", + " >", + " no_default_network_duration_ms: 0", + " previous_network_id <", + " network_id: 0", + " >", + " previous_network_ip_support: 0", + " transport_types: 0", + " >", + ">", + "events <", + " if_name: \"\"", + " link_layer: 0", + " network_id: 103", + " time_ms: 0", + " transports: 0", + " default_network_event <", + " default_network_duration_ms: 0", + " final_score: 0", + " initial_score: 0", + " ip_support: 0", + " network_id <", + " network_id: 103", + " >", + " no_default_network_duration_ms: 0", + " previous_network_id <", + " network_id: 102", + " >", + " previous_network_ip_support: 3", + " transport_types: 1", + " >", + ">", + "version: 2\n"); + + verifySerialization(want, getdump("flush")); + } + + @Test public void testEndToEndLogging() throws Exception { // TODO: instead of comparing textpb to textpb, parse textpb and compare proto to proto. IpConnectivityLog logger = new IpConnectivityLog(mService.impl); @@ -194,7 +341,6 @@ public class IpConnectivityMetricsTest { Parcelable[] events = { new IpReachabilityEvent(IpReachabilityEvent.NUD_FAILED), new DhcpClientEvent("SomeState", 192), - new DefaultNetworkEvent(102, new int[]{1,2,3}, 101, true, false), new IpManagerEvent(IpManagerEvent.PROVISIONING_OK, 5678), validationEv, apfStats, @@ -233,6 +379,13 @@ public class IpConnectivityMetricsTest { wakeupEvent("wlan0", 10008); wakeupEvent("rmnet0", 1000); + final long cell = BitUtils.packBits(new int[]{NetworkCapabilities.TRANSPORT_CELLULAR}); + final long wifi = BitUtils.packBits(new int[]{NetworkCapabilities.TRANSPORT_WIFI}); + NetworkAgentInfo cellNai = makeNai(100, 50, false, true, cell); + NetworkAgentInfo wifiNai = makeNai(101, 60, true, false, wifi); + mService.mDefaultNetworkMetrics.logDefaultNetworkEvent(cellNai, null); + mService.mDefaultNetworkMetrics.logDefaultNetworkEvent(wifiNai, cellNai); + String want = String.join("\n", "dropped_events: 0", "events <", @@ -264,30 +417,6 @@ public class IpConnectivityMetricsTest { " network_id: 0", " time_ms: 300", " transports: 0", - " default_network_event <", - " default_network_duration_ms: 0", - " final_score: 0", - " initial_score: 0", - " ip_support: 0", - " network_id <", - " network_id: 102", - " >", - " no_default_network_duration_ms: 0", - " previous_network_id <", - " network_id: 101", - " >", - " previous_network_ip_support: 1", - " transport_types: 1", - " transport_types: 2", - " transport_types: 3", - " >", - ">", - "events <", - " if_name: \"\"", - " link_layer: 4", - " network_id: 0", - " time_ms: 400", - " transports: 0", " ip_provisioning_event <", " event_type: 1", " if_name: \"\"", @@ -298,7 +427,7 @@ public class IpConnectivityMetricsTest { " if_name: \"\"", " link_layer: 4", " network_id: 0", - " time_ms: 500", + " time_ms: 400", " transports: 0", " validation_probe_event <", " latency_ms: 40730", @@ -310,7 +439,7 @@ public class IpConnectivityMetricsTest { " if_name: \"\"", " link_layer: 4", " network_id: 0", - " time_ms: 600", + " time_ms: 500", " transports: 0", " apf_statistics <", " dropped_ras: 2", @@ -331,7 +460,7 @@ public class IpConnectivityMetricsTest { " if_name: \"\"", " link_layer: 4", " network_id: 0", - " time_ms: 700", + " time_ms: 600", " transports: 0", " ra_event <", " dnssl_lifetime: -1", @@ -344,6 +473,50 @@ public class IpConnectivityMetricsTest { ">", "events <", " if_name: \"\"", + " link_layer: 0", + " network_id: 100", + " time_ms: 0", + " transports: 0", + " default_network_event <", + " default_network_duration_ms: 0", + " final_score: 0", + " initial_score: 0", + " ip_support: 0", + " network_id <", + " network_id: 100", + " >", + " no_default_network_duration_ms: 0", + " previous_network_id <", + " network_id: 0", + " >", + " previous_network_ip_support: 0", + " transport_types: 0", + " >", + ">", + "events <", + " if_name: \"\"", + " link_layer: 0", + " network_id: 101", + " time_ms: 0", + " transports: 0", + " default_network_event <", + " default_network_duration_ms: 0", + " final_score: 0", + " initial_score: 0", + " ip_support: 0", + " network_id <", + " network_id: 101", + " >", + " no_default_network_duration_ms: 0", + " previous_network_id <", + " network_id: 100", + " >", + " previous_network_ip_support: 2", + " transport_types: 1", + " >", + ">", + "events <", + " if_name: \"\"", " link_layer: 4", " network_id: 100", " time_ms: 0", @@ -471,6 +644,26 @@ public class IpConnectivityMetricsTest { mNetdListener.onWakeupEvent(prefix, uid, uid, 0); } + NetworkAgentInfo makeNai(int netId, int score, boolean ipv4, boolean ipv6, long transports) { + NetworkAgentInfo nai = mock(NetworkAgentInfo.class); + when(nai.network()).thenReturn(new Network(netId)); + when(nai.getCurrentScore()).thenReturn(score); + nai.linkProperties = new LinkProperties(); + nai.networkCapabilities = new NetworkCapabilities(); + for (int t : BitUtils.unpackBits(transports)) { + nai.networkCapabilities.addTransportType(t); + } + if (ipv4) { + nai.linkProperties.addLinkAddress(new LinkAddress("192.0.2.12/24")); + nai.linkProperties.addRoute(new RouteInfo(new IpPrefix("0.0.0.0/0"))); + } + if (ipv6) { + nai.linkProperties.addLinkAddress(new LinkAddress("2001:db8:dead:beef:f00::a0/64")); + nai.linkProperties.addRoute(new RouteInfo(new IpPrefix("::/0"))); + } + return nai; + } + List<ConnectivityMetricsEvent> verifyEvents(int n, int timeoutMs) throws Exception { ArgumentCaptor<ConnectivityMetricsEvent> captor = ArgumentCaptor.forClass(ConnectivityMetricsEvent.class); diff --git a/tests/net/java/com/android/server/connectivity/tethering/SimChangeListenerTest.java b/tests/net/java/com/android/server/connectivity/tethering/SimChangeListenerTest.java index b5d333b8b230..f58ea7e9375f 100644 --- a/tests/net/java/com/android/server/connectivity/tethering/SimChangeListenerTest.java +++ b/tests/net/java/com/android/server/connectivity/tethering/SimChangeListenerTest.java @@ -48,8 +48,6 @@ import org.mockito.MockitoAnnotations; @RunWith(AndroidJUnit4.class) @SmallTest public class SimChangeListenerTest { - private static final int EVENT_UNM_UPDATE = 1; - @Mock private Context mContext; private BroadcastInterceptingContext mServiceContext; private Handler mHandler; diff --git a/tools/locked_region_code_injection/Android.bp b/tools/locked_region_code_injection/Android.bp new file mode 100644 index 000000000000..6dd6059dcc4f --- /dev/null +++ b/tools/locked_region_code_injection/Android.bp @@ -0,0 +1,12 @@ +java_library_host { + name: "lockedregioncodeinjection", + manifest: "manifest.txt", + srcs: ["src/**/*.java"], + static_libs: [ + "asm-6.0", + "asm-commons-6.0", + "asm-tree-6.0", + "asm-analysis-6.0", + "guava-21.0", + ], +} diff --git a/tools/locked_region_code_injection/Android.mk b/tools/locked_region_code_injection/Android.mk deleted file mode 100644 index 3f6515154d42..000000000000 --- a/tools/locked_region_code_injection/Android.mk +++ /dev/null @@ -1,15 +0,0 @@ -LOCAL_PATH:= $(call my-dir) - -include $(CLEAR_VARS) - -LOCAL_JAR_MANIFEST := manifest.txt -LOCAL_MODULE := lockedregioncodeinjection -LOCAL_SRC_FILES := $(call all-java-files-under,src) -LOCAL_STATIC_JAVA_LIBRARIES := \ - asm-6.0 \ - asm-commons-6.0 \ - asm-tree-6.0 \ - asm-analysis-6.0 \ - guava-21.0 \ - -include $(BUILD_HOST_JAVA_LIBRARY) diff --git a/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockFindingClassVisitor.java b/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockFindingClassVisitor.java index a60f2a2019d2..ee0e36c9f76e 100644 --- a/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockFindingClassVisitor.java +++ b/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockFindingClassVisitor.java @@ -76,7 +76,7 @@ class LockFindingClassVisitor extends ClassVisitor { private MethodVisitor chain; public LockFindingMethodVisitor(String owner, MethodNode mn, MethodVisitor chain) { - super(Opcodes.ASM6, mn); + super(Utils.ASM_VERSION, mn); assert owner != null; this.owner = owner; this.chain = chain; diff --git a/tools/locked_region_code_injection/src/lockedregioncodeinjection/Utils.java b/tools/locked_region_code_injection/src/lockedregioncodeinjection/Utils.java index d2a2e7b90f2b..219c2b3a2fec 100644 --- a/tools/locked_region_code_injection/src/lockedregioncodeinjection/Utils.java +++ b/tools/locked_region_code_injection/src/lockedregioncodeinjection/Utils.java @@ -19,7 +19,7 @@ import org.objectweb.asm.Opcodes; public class Utils { - public static final int ASM_VERSION = Opcodes.ASM5; + public static final int ASM_VERSION = Opcodes.ASM6; /** * Reads a comma separated configuration similar to the Jack definition. diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl index a3a1054f869e..551e4df1c816 100644 --- a/wifi/java/android/net/wifi/IWifiManager.aidl +++ b/wifi/java/android/net/wifi/IWifiManager.aidl @@ -62,6 +62,8 @@ interface IWifiManager WifiConfiguration getMatchingWifiConfig(in ScanResult scanResult); + List<WifiConfiguration> getAllMatchingWifiConfigs(in ScanResult scanResult); + List<OsuProvider> getMatchingOsuProviders(in ScanResult scanResult); int addOrUpdateNetwork(in WifiConfiguration config); @@ -126,8 +128,6 @@ interface IWifiManager void releaseMulticastLock(); - void setWifiApEnabled(in WifiConfiguration wifiConfig, boolean enable); - void updateInterfaceIpState(String ifaceName, int mode); boolean startSoftAp(in WifiConfiguration wifiConfig); diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index b08b4b7c71b6..c2959d5e7ab7 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -68,6 +68,7 @@ import java.util.concurrent.CountDownLatch; * leaks within the calling process. * <p> * It deals with several categories of items: + * </p> * <ul> * <li>The list of configured networks. The list can be viewed and updated, and * attributes of individual entries can be modified.</li> @@ -79,9 +80,11 @@ import java.util.concurrent.CountDownLatch; * <li>It defines the names of various Intent actions that are broadcast upon * any sort of change in Wi-Fi state. * </ul> + * <p> * This is the API to use when performing Wi-Fi specific operations. To perform * operations that pertain to network connectivity at an abstract level, use * {@link android.net.ConnectivityManager}. + * </p> */ @SystemService(Context.WIFI_SERVICE) public class WifiManager { @@ -1029,6 +1032,26 @@ public class WifiManager { } /** + * Return all matching WifiConfigurations for this ScanResult. + * + * An empty list will be returned when no configurations are installed or if no configurations + * match the ScanResult. + * + * @param scanResult scanResult that represents the BSSID + * @return A list of {@link WifiConfiguration} + * @throws UnsupportedOperationException if Passpoint is not enabled on the device. + * @hide + */ + public List<WifiConfiguration> getAllMatchingWifiConfigs(ScanResult scanResult) { + try { + return mService.getAllMatchingWifiConfigs(scanResult); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + + /** * Returns a list of Hotspot 2.0 OSU (Online Sign-Up) providers associated with the given AP. * * An empty list will be returned if no match is found. @@ -1555,7 +1578,21 @@ public class WifiManager { * Request a scan for access points. Returns immediately. The availability * of the results is made known later by means of an asynchronous event sent * on completion of the scan. - * @return {@code true} if the operation succeeded, i.e., the scan was initiated + * <p> + * To initiate a Wi-Fi scan, declare the + * {@link android.Manifest.permission#CHANGE_WIFI_STATE} + * permission in the manifest, and perform these steps: + * </p> + * <ol style="1"> + * <li>Invoke the following method: + * {@code ((WifiManager) getSystemService(WIFI_SERVICE)).startScan()}</li> + * <li> + * Register a BroadcastReceiver to listen to + * {@code SCAN_RESULTS_AVAILABLE_ACTION}.</li> + * <li>When a broadcast is received, call: + * {@code ((WifiManager) getSystemService(WIFI_SERVICE)).getScanResults()}</li> + * </ol> + * @return {@code true} if the operation succeeded, i.e., the scan was initiated. */ public boolean startScan() { return startScan(null); @@ -1857,32 +1894,6 @@ public class WifiManager { } /** - * This call is deprecated and removed. It is no longer used to - * start WiFi Tethering. Please use {@link ConnectivityManager#startTethering(int, boolean, - * ConnectivityManager#OnStartTetheringCallback)} if - * the caller has proper permissions. Callers can also use the LocalOnlyHotspot feature for a - * hotspot capable of communicating with co-located devices {@link - * WifiManager#startLocalOnlyHotspot(LocalOnlyHotspotCallback)}. - * - * @param wifiConfig SSID, security and channel details as - * part of WifiConfiguration - * @return {@code false} - * - * @hide - * @deprecated This API is nolonger supported. - * @removed - */ - @SystemApi - @Deprecated - @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) - public boolean setWifiApEnabled(WifiConfiguration wifiConfig, boolean enabled) { - String packageName = mContext.getOpPackageName(); - - Log.w(TAG, packageName + " attempted call to setWifiApEnabled: enabled = " + enabled); - return false; - } - - /** * Call allowing ConnectivityService to update WifiService with interface mode changes. * * The possible modes include: {@link IFACE_IP_MODE_TETHERED}, diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java index b235ccc7a89e..ee6f12b775a8 100644 --- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java +++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java @@ -777,14 +777,4 @@ public class WifiManagerTest { mWifiManager.unregisterLocalOnlyHotspotObserver(); verify(mWifiService).stopWatchLocalOnlyHotspot(); } - - /** - * Verify that calls to setWifiApEnabled return false. - */ - @Test - public void testSetWifiApEnabledReturnsFalse() throws Exception { - assertFalse(mWifiManager.setWifiApEnabled(null, true)); - assertFalse(mWifiManager.setWifiApEnabled(null, false)); - verify(mWifiService, never()).setWifiApEnabled(any(), anyBoolean()); - } } |