diff options
298 files changed, 8029 insertions, 6185 deletions
diff --git a/Android.bp b/Android.bp index b9b1bd87e80d..5b8e6e1b8d32 100644 --- a/Android.bp +++ b/Android.bp @@ -375,6 +375,7 @@ java_defaults { "core/java/android/view/IDisplayFoldListener.aidl", "core/java/android/view/IGraphicsStats.aidl", "core/java/android/view/IGraphicsStatsCallback.aidl", + "core/java/android/view/IInputMonitorHost.aidl", "core/java/android/view/IInputFilter.aidl", "core/java/android/view/IInputFilterHost.aidl", "core/java/android/view/IOnKeyguardExitResult.aidl", diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java index 2fdba0af2c1b..c121bd9caa57 100644 --- a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java +++ b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java @@ -27,6 +27,7 @@ import android.content.pm.UserInfo; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; +import android.util.Log; import androidx.test.InstrumentationRegistry; import androidx.test.filters.LargeTest; @@ -130,6 +131,47 @@ public class UserLifecycleTests { } } + /** Tests switching to an already-created, but no-longer-running, user. */ + @Test + public void switchUser_stopped() throws Exception { + while (mRunner.keepRunning()) { + mRunner.pauseTiming(); + final int startUser = mAm.getCurrentUser(); + final int testUser = initializeNewUserAndSwitchBack(/* stopNewUser */ true); + final CountDownLatch latch = new CountDownLatch(1); + registerBroadcastReceiver(Intent.ACTION_USER_UNLOCKED, latch, testUser); + mRunner.resumeTiming(); + + mAm.switchUser(testUser); + boolean success = latch.await(TIMEOUT_IN_SECOND, TimeUnit.SECONDS); + + mRunner.pauseTiming(); + attestTrue("Failed to achieve 2nd ACTION_USER_UNLOCKED for user " + testUser, success); + switchUser(startUser); + removeUser(testUser); + mRunner.resumeTiming(); + } + } + + /** Tests switching to an already-created already-running non-owner user. */ + @Test + public void switchUser_running() throws Exception { + while (mRunner.keepRunning()) { + mRunner.pauseTiming(); + final int startUser = mAm.getCurrentUser(); + final int testUser = initializeNewUserAndSwitchBack(/* stopNewUser */ false); + mRunner.resumeTiming(); + + switchUser(testUser); + + mRunner.pauseTiming(); + attestTrue("Failed to switch to user " + testUser, mAm.isUserRunning(testUser)); + switchUser(startUser); + removeUser(testUser); + mRunner.resumeTiming(); + } + } + @Test public void stopUser() throws Exception { while (mRunner.keepRunning()) { @@ -188,6 +230,34 @@ public class UserLifecycleTests { } } + /** Tests starting an already-created, but no-longer-running, profile. */ + @Test + public void managedProfileUnlock_stopped() throws Exception { + while (mRunner.keepRunning()) { + mRunner.pauseTiming(); + final UserInfo userInfo = mUm.createProfileForUser("TestUser", + UserInfo.FLAG_MANAGED_PROFILE, mAm.getCurrentUser()); + // Start the profile initially, then stop it. Similar to setQuietModeEnabled. + final CountDownLatch latch1 = new CountDownLatch(1); + registerBroadcastReceiver(Intent.ACTION_USER_UNLOCKED, latch1, userInfo.id); + mIam.startUserInBackground(userInfo.id); + latch1.await(TIMEOUT_IN_SECOND, TimeUnit.SECONDS); + stopUser(userInfo.id, true); + + // Now we restart the profile. + final CountDownLatch latch2 = new CountDownLatch(1); + registerBroadcastReceiver(Intent.ACTION_USER_UNLOCKED, latch2, userInfo.id); + mRunner.resumeTiming(); + + mIam.startUserInBackground(userInfo.id); + latch2.await(TIMEOUT_IN_SECOND, TimeUnit.SECONDS); + + mRunner.pauseTiming(); + removeUser(userInfo.id); + mRunner.resumeTiming(); + } + } + @Test public void ephemeralUserStopped() throws Exception { while (mRunner.keepRunning()) { @@ -262,6 +332,35 @@ public class UserLifecycleTests { latch.await(TIMEOUT_IN_SECOND, TimeUnit.SECONDS); } + /** + * Creates a user and waits for its ACTION_USER_UNLOCKED. + * Then switches to back to the original user and waits for its switchUser() to finish. + * + * @param stopNewUser whether to stop the new user after switching to otherUser. + * @return userId of the newly created user. + */ + private int initializeNewUserAndSwitchBack(boolean stopNewUser) throws Exception { + final int origUser = mAm.getCurrentUser(); + // First, create and switch to testUser, waiting for its ACTION_USER_UNLOCKED + final int testUser = mUm.createUser("TestUser", 0).id; + final CountDownLatch latch1 = new CountDownLatch(1); + registerBroadcastReceiver(Intent.ACTION_USER_UNLOCKED, latch1, testUser); + mAm.switchUser(testUser); + attestTrue("Failed to achieve initial ACTION_USER_UNLOCKED for user " + testUser, + latch1.await(TIMEOUT_IN_SECOND, TimeUnit.SECONDS)); + + // Second, switch back to origUser, waiting merely for switchUser() to finish + switchUser(origUser); + attestTrue("Didn't switch back to user, " + origUser, origUser == mAm.getCurrentUser()); + + if (stopNewUser) { + stopUser(testUser, true); + attestFalse("Failed to stop user " + testUser, mAm.isUserRunning(testUser)); + } + + return testUser; + } + private void registerUserSwitchObserver(final CountDownLatch switchLatch, final CountDownLatch bootCompleteLatch, final int userId) throws Exception { ActivityManager.getService().registerUserSwitchObserver( @@ -313,4 +412,14 @@ public class UserLifecycleTests { mUsersToRemove.add(userId); } } + + private void attestTrue(String message, boolean attestion) { + if (!attestion) { + Log.w(TAG, message); + } + } + + private void attestFalse(String message, boolean attestion) { + attestTrue(message, !attestion); + } } diff --git a/api/current.txt b/api/current.txt index 1394ba33dddd..f78dfde30c3e 100644 --- a/api/current.txt +++ b/api/current.txt @@ -11452,6 +11452,7 @@ package android.content.pm { method public long getSize(); method public int getStagedSessionErrorCode(); method @NonNull public String getStagedSessionErrorMessage(); + method public long getUpdatedMillis(); method @NonNull public android.os.UserHandle getUser(); method public boolean isActive(); method public boolean isCommitted(); @@ -34275,6 +34276,7 @@ package android.os { ctor public Binder(@Nullable String); method public void attachInterface(@Nullable android.os.IInterface, @Nullable String); method public static final long clearCallingIdentity(); + method public static final long clearCallingWorkSource(); method public void dump(@NonNull java.io.FileDescriptor, @Nullable String[]); method protected void dump(@NonNull java.io.FileDescriptor, @NonNull java.io.PrintWriter, @Nullable String[]); method public void dumpAsync(@NonNull java.io.FileDescriptor, @Nullable String[]); @@ -34283,6 +34285,7 @@ package android.os { method public static final int getCallingUid(); method public static final int getCallingUidOrThrow(); method @NonNull public static final android.os.UserHandle getCallingUserHandle(); + method public static final int getCallingWorkSourceUid(); method @Nullable public String getInterfaceDescriptor(); method public boolean isBinderAlive(); method public static final void joinThreadPool(); @@ -34291,6 +34294,8 @@ package android.os { method public boolean pingBinder(); method @Nullable public android.os.IInterface queryLocalInterface(@NonNull String); method public static final void restoreCallingIdentity(long); + method public static final void restoreCallingWorkSource(long); + method public static final long setCallingWorkSourceUid(int); method public final boolean transact(int, @NonNull android.os.Parcel, @Nullable android.os.Parcel, int) throws android.os.RemoteException; method public boolean unlinkToDeath(@NonNull android.os.IBinder.DeathRecipient, int); } @@ -37069,12 +37074,6 @@ package android.provider { field public static final String CACHED_NUMBER_TYPE = "numbertype"; field public static final String CACHED_PHOTO_ID = "photo_id"; field public static final String CACHED_PHOTO_URI = "photo_uri"; - field public static final String CALL_ID_APP_NAME = "call_id_app_name"; - field public static final String CALL_ID_DESCRIPTION = "call_id_description"; - field public static final String CALL_ID_DETAILS = "call_id_details"; - field public static final String CALL_ID_NAME = "call_id_name"; - field public static final String CALL_ID_NUISANCE_CONFIDENCE = "call_id_nuisance_confidence"; - field public static final String CALL_ID_PACKAGE_NAME = "call_id_package_name"; field public static final String CALL_SCREENING_APP_NAME = "call_screening_app_name"; field public static final String CALL_SCREENING_COMPONENT_NAME = "call_screening_component_name"; field public static final android.net.Uri CONTENT_FILTER_URI; @@ -43257,7 +43256,6 @@ package android.telecom { method public android.telecom.PhoneAccountHandle getAccountHandle(); method public int getCallCapabilities(); method public int getCallDirection(); - method @Nullable public android.telecom.CallIdentification getCallIdentification(); method public int getCallProperties(); method public String getCallerDisplayName(); method public int getCallerDisplayNamePresentation(); @@ -43339,34 +43337,6 @@ package android.telecom { field public static final int ROUTE_WIRED_OR_EARPIECE = 5; // 0x5 } - public final class CallIdentification implements android.os.Parcelable { - method public int describeContents(); - method @NonNull public CharSequence getCallScreeningAppName(); - method @NonNull public String getCallScreeningPackageName(); - method @Nullable public CharSequence getDescription(); - method @Nullable public CharSequence getDetails(); - method @Nullable public CharSequence getName(); - method public int getNuisanceConfidence(); - method @Nullable public android.graphics.drawable.Icon getPhoto(); - method public void writeToParcel(android.os.Parcel, int); - field public static final int CONFIDENCE_LIKELY_NOT_NUISANCE = -1; // 0xffffffff - field public static final int CONFIDENCE_LIKELY_NUISANCE = 1; // 0x1 - field public static final int CONFIDENCE_NOT_NUISANCE = -2; // 0xfffffffe - field public static final int CONFIDENCE_NUISANCE = 2; // 0x2 - field public static final int CONFIDENCE_UNKNOWN = 0; // 0x0 - field @NonNull public static final android.os.Parcelable.Creator<android.telecom.CallIdentification> CREATOR; - } - - public static final class CallIdentification.Builder { - ctor public CallIdentification.Builder(); - method @NonNull public android.telecom.CallIdentification build(); - method @NonNull public android.telecom.CallIdentification.Builder setDescription(@Nullable CharSequence); - method @NonNull public android.telecom.CallIdentification.Builder setDetails(@Nullable CharSequence); - method @NonNull public android.telecom.CallIdentification.Builder setName(@Nullable CharSequence); - method @NonNull public android.telecom.CallIdentification.Builder setNuisanceConfidence(int); - method @NonNull public android.telecom.CallIdentification.Builder setPhoto(@Nullable android.graphics.drawable.Icon); - } - public abstract class CallRedirectionService extends android.app.Service { ctor public CallRedirectionService(); method public final void cancelCall(); @@ -43382,17 +43352,7 @@ package android.telecom { ctor public CallScreeningService(); method public android.os.IBinder onBind(android.content.Intent); method public abstract void onScreenCall(@NonNull android.telecom.Call.Details); - method public final void provideCallIdentification(@NonNull android.telecom.Call.Details, @NonNull android.telecom.CallIdentification); method public final void respondToCall(@NonNull android.telecom.Call.Details, @NonNull android.telecom.CallScreeningService.CallResponse); - field public static final String ACTION_NUISANCE_CALL_STATUS_CHANGED = "android.telecom.action.NUISANCE_CALL_STATUS_CHANGED"; - field public static final int CALL_DURATION_LONG = 4; // 0x4 - field public static final int CALL_DURATION_MEDIUM = 3; // 0x3 - field public static final int CALL_DURATION_SHORT = 2; // 0x2 - field public static final int CALL_DURATION_VERY_SHORT = 1; // 0x1 - field public static final String EXTRA_CALL_DURATION = "android.telecom.extra.CALL_DURATION"; - field public static final String EXTRA_CALL_HANDLE = "android.telecom.extra.CALL_HANDLE"; - field public static final String EXTRA_CALL_TYPE = "android.telecom.extra.CALL_TYPE"; - field public static final String EXTRA_IS_NUISANCE = "android.telecom.extra.IS_NUISANCE"; field public static final String SERVICE_INTERFACE = "android.telecom.CallScreeningService"; } @@ -44001,7 +43961,6 @@ package android.telecom { method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public boolean isVoiceMailNumber(android.telecom.PhoneAccountHandle, String); method @RequiresPermission(anyOf={android.Manifest.permission.CALL_PHONE, android.Manifest.permission.MANAGE_OWN_CALLS}) public void placeCall(android.net.Uri, android.os.Bundle); method public void registerPhoneAccount(android.telecom.PhoneAccount); - method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public void reportNuisanceCallStatus(@NonNull android.net.Uri, boolean); method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public void showInCallScreen(boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void silenceRinger(); method public void unregisterPhoneAccount(android.telecom.PhoneAccountHandle); @@ -45150,6 +45109,7 @@ package android.telephony { method public boolean canChangeDtmfToneLength(); method @Nullable public android.telephony.TelephonyManager createForPhoneAccountHandle(android.telecom.PhoneAccountHandle); method public android.telephony.TelephonyManager createForSubscriptionId(int); + method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public boolean doesSwitchMultiSimConfigTriggerReboot(); method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public java.util.List<android.telephony.CellInfo> getAllCellInfo(); method public int getCallState(); method public int getCardIdForDefaultEuicc(); diff --git a/api/system-current.txt b/api/system-current.txt index 9609cc0b4265..3f74596999cb 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -1462,7 +1462,7 @@ package android.content.om { method @Nullable public String getCategory(); method @NonNull public String getPackageName(); method @Nullable public String getTargetOverlayableName(); - method @Nullable public String getTargetPackageName(); + method @NonNull public String getTargetPackageName(); method public int getUserId(); method public boolean isEnabled(); method public void writeToParcel(android.os.Parcel, int); @@ -5143,10 +5143,6 @@ package android.os { } public class Binder implements android.os.IBinder { - method public static final long clearCallingWorkSource(); - method public static final int getCallingWorkSourceUid(); - method public static final void restoreCallingWorkSource(long); - method public static final long setCallingWorkSourceUid(int); method public static void setProxyTransactListener(@Nullable android.os.Binder.ProxyTransactListener); } @@ -8067,7 +8063,6 @@ package android.telephony { method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isOffhook(); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isPotentialEmergencyNumber(@NonNull String); method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isRadioOn(); - method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isRebootRequiredForModemConfigChange(); method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isRinging(); method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isVideoCallingEnabled(); method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public boolean isVisualVoicemailEnabled(android.telecom.PhoneAccountHandle); diff --git a/api/test-current.txt b/api/test-current.txt index 1e24ff953b2a..67a26f39ebbc 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -716,6 +716,11 @@ package android.content.pm { package android.content.res { + public final class AssetManager implements java.lang.AutoCloseable { + method @NonNull public String[] getApkPaths(); + method @Nullable public java.util.Map<java.lang.String,java.lang.String> getOverlayableMap(String); + } + public final class Configuration implements java.lang.Comparable<android.content.res.Configuration> android.os.Parcelable { field public int assetsSeq; field public final android.app.WindowConfiguration windowConfiguration; diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java index 98ab6665958c..680ccfc57ddf 100644 --- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java +++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java @@ -112,6 +112,11 @@ public class Bmgr { return; } + if ("activated".equals(op)) { + doActivated(userId); + return; + } + if (!isBackupActive(userId)) { return; } @@ -200,6 +205,21 @@ public class Bmgr { return true; } + private String activatedToString(boolean activated) { + return activated ? "activated" : "deactivated"; + } + + private void doActivated(@UserIdInt int userId) { + try { + System.out.println("Backup Manager currently " + + activatedToString(mBmgr.isBackupServiceActive(userId))); + } catch (RemoteException e) { + System.err.println(e.toString()); + System.err.println(BMGR_NOT_RUNNING_ERR); + } + + } + private String enableToString(boolean enabled) { return enabled ? "enabled" : "disabled"; } @@ -907,6 +927,7 @@ public class Bmgr { System.err.println(" bmgr cancel backups"); System.err.println(" bmgr init TRANSPORT..."); System.err.println(" bmgr activate BOOL"); + System.err.println(" bmgr activated"); System.err.println(""); System.err.println("The '--user' option specifies the user on which the operation is run."); System.err.println("It must be the first argument before the operation."); @@ -978,6 +999,9 @@ public class Bmgr { System.err.println("If the argument is 'true' it will be activated, otherwise it will be"); System.err.println("deactivated. When deactivated, the service will not be running and no"); System.err.println("operations can be performed until activation."); + System.err.println(""); + System.err.println("The 'activated' command reports the current activated/deactivated"); + System.err.println("state of the backup mechanism."); } private static class BackupMonitor extends IBackupManagerMonitor.Stub { diff --git a/cmds/idmap2/libidmap2/Idmap.cpp b/cmds/idmap2/libidmap2/Idmap.cpp index 6d5fe7b3446a..49470b4f4e95 100644 --- a/cmds/idmap2/libidmap2/Idmap.cpp +++ b/cmds/idmap2/libidmap2/Idmap.cpp @@ -269,12 +269,10 @@ Result<std::unique_ptr<const Idmap>> Idmap::FromBinaryStream(std::istream& strea std::string ConcatPolicies(const std::vector<std::string>& policies) { std::string message; for (const std::string& policy : policies) { - if (message.empty()) { - message.append(policy); - } else { - message.append(policy); + if (!message.empty()) { message.append("|"); } + message.append(policy); } return message; diff --git a/cmds/incidentd/src/Broadcaster.cpp b/cmds/incidentd/src/Broadcaster.cpp index 39e5393e1f81..63464f2d58c5 100644 --- a/cmds/incidentd/src/Broadcaster.cpp +++ b/cmds/incidentd/src/Broadcaster.cpp @@ -22,6 +22,7 @@ #include <android/os/DropBoxManager.h> #include <binder/IServiceManager.h> +#include <thread> namespace android { namespace os { @@ -391,13 +392,20 @@ status_t Broadcaster::send_to_dropbox(const sp<ReportFile>& file, return NO_ERROR; } - // Start a thread to write the data to dropbox. - int readFd = -1; - err = file->startFilteringData(&readFd, args); - if (err != NO_ERROR) { - return err; + int fds[2]; + if (pipe(fds) != 0) { + ALOGW("Error opening pipe to filter incident report: %s", file->getDataFileName().c_str()); + return NO_ERROR; } + int readFd = fds[0]; + int writeFd = fds[1]; + + // spawn a thread to write the data. Release the writeFd ownership to the thread. + thread th([file, writeFd, args]() { file->startFilteringData(writeFd, args); }); + + th.detach(); + // Takes ownership of readFd. Status status = dropbox->addFile(String16("incident"), readFd, 0); if (!status.isOk()) { diff --git a/cmds/incidentd/src/IncidentService.cpp b/cmds/incidentd/src/IncidentService.cpp index 4ba31b45e81c..b4021d1b2f76 100644 --- a/cmds/incidentd/src/IncidentService.cpp +++ b/cmds/incidentd/src/IncidentService.cpp @@ -32,6 +32,7 @@ #include <log/log.h> #include <private/android_filesystem_config.h> #include <utils/Looper.h> +#include <thread> #include <unistd.h> @@ -117,7 +118,8 @@ static Status checkIncidentPermissions(const IncidentReportArgs& args) { } static string build_uri(const string& pkg, const string& cls, const string& id) { - return "build_uri not implemented " + pkg + "/" + cls + "/" + id; + return "content://android.os.IncidentManager/pending?pkg=" + + pkg + "&receiver=" + cls + "&r=" + id; } // ================================================================================ @@ -358,17 +360,21 @@ Status IncidentService::getIncidentReport(const String16& pkg16, const String16& IncidentReportArgs args; sp<ReportFile> file = mWorkDirectory->getReport(pkg, cls, id, &args); if (file != nullptr) { - int fd; - err = file->startFilteringData(&fd, args); - if (err != 0) { - ALOGW("Error reading data file that we think should exist: %s", - file->getDataFileName().c_str()); + // Create pipe + int fds[2]; + if (pipe(fds) != 0) { + ALOGW("Error opening pipe to filter incident report: %s", + file->getDataFileName().c_str()); return Status::ok(); } - result->setTimestampNs(file->getTimestampNs()); result->setPrivacyPolicy(file->getEnvelope().privacy_policy()); - result->takeFileDescriptor(fd); + result->takeFileDescriptor(fds[0]); + int writeFd = fds[1]; + // spawn a thread to write the data. Release the writeFd ownership to the thread. + thread th([file, writeFd, args]() { file->startFilteringData(writeFd, args); }); + + th.detach(); } return Status::ok(); diff --git a/cmds/incidentd/src/Reporter.cpp b/cmds/incidentd/src/Reporter.cpp index e773e74bbf15..218c1b27fdcc 100644 --- a/cmds/incidentd/src/Reporter.cpp +++ b/cmds/incidentd/src/Reporter.cpp @@ -551,7 +551,7 @@ void Reporter::runReport(size_t* reportByteSize) { buf++) { // If there was an error now, there will be an error later and we will remove // it from the list then. - write_header_section(request->getFd(), *buf); + write_header_section(request->getFd(), buf->data(), buf->size()); } }); diff --git a/cmds/incidentd/src/WorkDirectory.cpp b/cmds/incidentd/src/WorkDirectory.cpp index aa376ddee082..ae640c635803 100644 --- a/cmds/incidentd/src/WorkDirectory.cpp +++ b/cmds/incidentd/src/WorkDirectory.cpp @@ -18,6 +18,7 @@ #include "WorkDirectory.h" +#include "proto_util.h" #include "PrivacyFilter.h" #include <google/protobuf/io/zero_copy_stream_impl.h> @@ -64,6 +65,9 @@ static const string EXTENSION_DATA(".data"); */ const ComponentName DROPBOX_SENTINEL("android", "DROPBOX"); +/** metadata field id in IncidentProto */ +const int FIELD_ID_INCIDENT_METADATA = 2; + /** * Read a protobuf from disk into the message. */ @@ -386,65 +390,53 @@ void ReportFile::closeDataFile() { } } -status_t ReportFile::startFilteringData(int* fd, const IncidentReportArgs& args) { +status_t ReportFile::startFilteringData(int writeFd, const IncidentReportArgs& args) { // Open data file. int dataFd = open(mDataFileName.c_str(), O_RDONLY | O_CLOEXEC); if (dataFd < 0) { + ALOGW("Error opening incident report '%s' %s", getDataFileName().c_str(), strerror(-errno)); + close(writeFd); return -errno; } // Check that the size on disk is what we thought we wrote. struct stat st; if (fstat(dataFd, &st) != 0) { + ALOGW("Error running fstat incident report '%s' %s", getDataFileName().c_str(), + strerror(-errno)); + close(writeFd); return -errno; } if (st.st_size != mEnvelope.data_file_size()) { ALOGW("File size mismatch. Envelope says %" PRIi64 " bytes but data file is %" PRIi64 - " bytes: %s", (int64_t)mEnvelope.data_file_size(), st.st_size, - mDataFileName.c_str()); + " bytes: %s", + (int64_t)mEnvelope.data_file_size(), st.st_size, mDataFileName.c_str()); ALOGW("Removing incident report"); mWorkDirectory->remove(this); + close(writeFd); return BAD_VALUE; } - // Create pipe - int fds[2]; - if (pipe(fds) != 0) { - ALOGW("Error opening pipe to filter incident report: %s", getDataFileName().c_str()); - return -errno; - } - - *fd = fds[0]; - int writeFd = fds[1]; - - // Spawn off a thread to do the filtering and writing - thread th([this, dataFd, writeFd, args]() { - ALOGD("worker thread started dataFd=%d writeFd=%d", dataFd, writeFd); - status_t err; - - err = filter_and_write_report(writeFd, dataFd, mEnvelope.privacy_policy(), args); - close(writeFd); + status_t err; - if (err != NO_ERROR) { - ALOGW("Error writing incident report '%s' to dropbox: %s", getDataFileName().c_str(), - strerror(-err)); - // If there's an error here, there will also be an error returned from - // addFile, so we'll use that error to reschedule the send_to_dropbox. - // If the file is corrupted, we will put some logs in logcat, but won't - // actually return an error. - return; + for (const auto& report : mEnvelope.report()) { + for (const auto& header : report.header()) { + write_header_section(writeFd, + reinterpret_cast<const uint8_t*>(header.c_str()), header.size()); } - }); + } - // Better would be to join this thread after write is back, but there is no - // timeout parameter for that, which means we can't clean up if system server - // is stuck. Better is to leak the thread, which will eventually clean itself - // up after system server eventually dies, which it probably will. - th.detach(); + if (mEnvelope.has_metadata()) { + write_section(writeFd, FIELD_ID_INCIDENT_METADATA, mEnvelope.metadata()); + } + + err = filter_and_write_report(writeFd, dataFd, mEnvelope.privacy_policy(), args); + if (err != NO_ERROR) { + ALOGW("Error writing incident report '%s' to dropbox: %s", getDataFileName().c_str(), + strerror(-err)); + } - // If the thread fails to start, we should return an error, but the thread - // class doesn't give us a good way to determine that. Just pretend everything - // is ok. + close(writeFd); return NO_ERROR; } @@ -501,7 +493,7 @@ status_t ReportFile::load_envelope_impl(bool cleanup) { // ================================================================================ -// +// WorkDirectory::WorkDirectory() :mDirectory("/data/misc/incidents"), diff --git a/cmds/incidentd/src/WorkDirectory.h b/cmds/incidentd/src/WorkDirectory.h index e344371c3682..3c6a2f2b9617 100644 --- a/cmds/incidentd/src/WorkDirectory.h +++ b/cmds/incidentd/src/WorkDirectory.h @@ -135,14 +135,14 @@ public: void closeDataFile(); /** - * Spawn a thread to start writing and filtering data to a pipe, the read end of which - * will be returned in fd. This thread will be detached and run until somebody finishes - * reading from the fd or closes it. If there is an error, returns it and you will not - * get an fd. + * Use the privacy and section configuration from the args parameter to filter data, write + * to [writeFd] and take the ownership of [writeFd]. * - * Use the privacy and section configuraiton from the args parameter. + * Note: this call is blocking. When the writeFd is a pipe fd for IPC, caller should make sure + * it's called on a separate thread so that reader can start to read without waiting for writer + * to finish writing (which may not happen due to pipe buffer overflow). */ - status_t startFilteringData(int* fd, const IncidentReportArgs& args); + status_t startFilteringData(int writeFd, const IncidentReportArgs& args); /** * Get the name of the data file on disk. diff --git a/cmds/incidentd/src/proto_util.cpp b/cmds/incidentd/src/proto_util.cpp index be2f24f97d02..4e8ff71b3777 100644 --- a/cmds/incidentd/src/proto_util.cpp +++ b/cmds/incidentd/src/proto_util.cpp @@ -33,11 +33,10 @@ using google::protobuf::io::FileOutputStream; // special section ids const int FIELD_ID_INCIDENT_HEADER = 1; -status_t write_header_section(int fd, const vector<uint8_t>& buf) { +status_t write_header_section(int fd, const uint8_t* buf, size_t bufSize) { status_t err; - const size_t bufSize = buf.size(); - if (buf.empty()) { + if (bufSize == 0) { return NO_ERROR; } @@ -46,7 +45,7 @@ status_t write_header_section(int fd, const vector<uint8_t>& buf) { return err; } - err = WriteFully(fd, (uint8_t const*)buf.data(), bufSize); + err = WriteFully(fd, buf, bufSize); if (err != NO_ERROR) { return err; } diff --git a/cmds/incidentd/src/proto_util.h b/cmds/incidentd/src/proto_util.h index b9df6cbf296d..2c0ab4893ba5 100644 --- a/cmds/incidentd/src/proto_util.h +++ b/cmds/incidentd/src/proto_util.h @@ -32,7 +32,7 @@ using google::protobuf::MessageLite; /** * Write the IncidentHeaderProto section */ -status_t write_header_section(int fd, const vector<uint8_t>& buf); +status_t write_header_section(int fd, const uint8_t* buf, size_t bufSize); /** * Write the prologue for a section in the incident report diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h index 8de1881eb2b8..305a4ce24b49 100644 --- a/cmds/statsd/src/StatsLogProcessor.h +++ b/cmds/statsd/src/StatsLogProcessor.h @@ -66,7 +66,7 @@ public: void onDumpReport(const ConfigKey& key, const int64_t dumpTimeNs, const bool include_current_partial_bucket, const bool erase_data, - const DumpReportReason dumpReportReason, + const DumpReportReason dumpReportReason, const DumpLatency dumpLatency, vector<uint8_t>* outData); void onDumpReport(const ConfigKey& key, const int64_t dumpTimeNs, @@ -260,6 +260,9 @@ private: FRIEND_TEST(AlarmE2eTest, TestMultipleAlarms); FRIEND_TEST(ConfigTtlE2eTest, TestCountMetric); FRIEND_TEST(MetricActivationE2eTest, TestCountMetric); + FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithOneDeactivation); + FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithTwoDeactivations); + FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations); }; } // namespace statsd diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index dc4413f5457c..cbb78bf5732c 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -1406,7 +1406,7 @@ message BluetoothConnectionStateChanged { // Currently is last two bytes of a hash of a device level ID and // the mac address of the bluetooth device that is connected. // Deprecated: use obfuscated_id instead, this one is always 0 for Q+ - optional int32 OBSOLETE_obfuscated_id = 2 [deprecated = true]; + optional int32 obfuscated_id = 2 [deprecated = true]; // The profile that is connected. Eg. GATT, A2DP, HEADSET. // From android.bluetooth.BluetoothAdapter.java // Default: 0 when not used @@ -1417,7 +1417,7 @@ message BluetoothConnectionStateChanged { // Hash algorithm: HMAC-SHA256 // Size: 32 byte // Default: null or empty if the device identifier is not known - optional bytes obfuscated_id = 4 [(android.os.statsd.log_mode) = MODE_BYTES]; + optional bytes new_obfuscated_id = 4 [(android.os.statsd.log_mode) = MODE_BYTES]; } /** diff --git a/cmds/statsd/src/metrics/MetricProducer.cpp b/cmds/statsd/src/metrics/MetricProducer.cpp index 4cf5333947d3..5ed95edc0299 100644 --- a/cmds/statsd/src/metrics/MetricProducer.cpp +++ b/cmds/statsd/src/metrics/MetricProducer.cpp @@ -70,11 +70,11 @@ void MetricProducer::onMatchedLogEventLocked(const size_t matcherIndex, const Lo bool MetricProducer::evaluateActiveStateLocked(int64_t elapsedTimestampNs) { bool isActive = mEventActivationMap.empty(); for (auto& it : mEventActivationMap) { - if (it.second.state == ActivationState::kActive && - elapsedTimestampNs > it.second.ttl_ns + it.second.activation_ns) { - it.second.state = ActivationState::kNotActive; + if (it.second->state == ActivationState::kActive && + elapsedTimestampNs > it.second->ttl_ns + it.second->activation_ns) { + it.second->state = ActivationState::kNotActive; } - if (it.second.state == ActivationState::kActive) { + if (it.second->state == ActivationState::kActive) { isActive = true; } } @@ -92,7 +92,8 @@ void MetricProducer::flushIfExpire(int64_t elapsedTimestampNs) { } } -void MetricProducer::addActivation(int activationTrackerIndex, int64_t ttl_seconds) { +void MetricProducer::addActivation(int activationTrackerIndex, int64_t ttl_seconds, + int deactivationTrackerIndex) { std::lock_guard<std::mutex> lock(mMutex); // When a metric producer does not depend on any activation, its mIsActive is true. // Therefore, if this is the 1st activation, mIsActive will turn to false. Otherwise it does not @@ -100,7 +101,12 @@ void MetricProducer::addActivation(int activationTrackerIndex, int64_t ttl_secon if (mEventActivationMap.empty()) { mIsActive = false; } - mEventActivationMap[activationTrackerIndex].ttl_ns = ttl_seconds * NS_PER_SEC; + std::shared_ptr<Activation> activation = std::make_shared<Activation>(); + activation->ttl_ns = ttl_seconds * NS_PER_SEC; + mEventActivationMap.emplace(activationTrackerIndex, activation); + if (-1 != deactivationTrackerIndex) { + mEventDeactivationMap.emplace(deactivationTrackerIndex, activation); + } } void MetricProducer::activateLocked(int activationTrackerIndex, int64_t elapsedTimestampNs) { @@ -109,27 +115,35 @@ void MetricProducer::activateLocked(int activationTrackerIndex, int64_t elapsedT return; } if (mActivationType == MetricActivation::ACTIVATE_ON_BOOT && - it->second.state == ActivationState::kNotActive) { - it->second.state = ActivationState::kActiveOnBoot; + it->second->state == ActivationState::kNotActive) { + it->second->state = ActivationState::kActiveOnBoot; return; } - it->second.activation_ns = elapsedTimestampNs; - it->second.state = ActivationState::kActive; + it->second->activation_ns = elapsedTimestampNs; + it->second->state = ActivationState::kActive; mIsActive = true; } +void MetricProducer::cancelEventActivationLocked(int deactivationTrackerIndex) { + auto it = mEventDeactivationMap.find(deactivationTrackerIndex); + if (it == mEventDeactivationMap.end()) { + return; + } + it->second->state = ActivationState::kNotActive; +} + void MetricProducer::setActiveLocked(int64_t currentTimeNs, int64_t remainingTtlNs) { if (mEventActivationMap.size() == 0) { return; } for (auto& pair : mEventActivationMap) { auto& activation = pair.second; - if (activation.ttl_ns >= remainingTtlNs) { - activation.activation_ns = currentTimeNs + remainingTtlNs - activation.ttl_ns; - activation.state = kActive; + if (activation->ttl_ns >= remainingTtlNs) { + activation->activation_ns = currentTimeNs + remainingTtlNs - activation->ttl_ns; + activation->state = kActive; mIsActive = true; - VLOG("setting new activation time to %lld, %lld, %lld", - (long long)activation.activation_ns, (long long)currentTimeNs, + VLOG("setting new activation->time to %lld, %lld, %lld", + (long long)activation->activation_ns, (long long)currentTimeNs, (long long)remainingTtlNs); return; } @@ -140,8 +154,8 @@ void MetricProducer::setActiveLocked(int64_t currentTimeNs, int64_t remainingTtl int64_t MetricProducer::getRemainingTtlNsLocked(int64_t currentTimeNs) const { int64_t maxTtl = 0; for (const auto& activation : mEventActivationMap) { - if (activation.second.state == kActive) { - maxTtl = std::max(maxTtl, activation.second.ttl_ns + activation.second.activation_ns - + if (activation.second->state == kActive) { + maxTtl = std::max(maxTtl, activation.second->ttl_ns + activation.second->activation_ns - currentTimeNs); } } @@ -153,9 +167,9 @@ void MetricProducer::prepActiveForBootIfNecessaryLocked(int64_t currentTimeNs) { return; } for (auto& activation : mEventActivationMap) { - if (activation.second.state == kActiveOnBoot) { - activation.second.state = kActive; - activation.second.activation_ns = currentTimeNs; + if (activation.second->state == kActiveOnBoot) { + activation.second->state = kActive; + activation.second->activation_ns = currentTimeNs; mIsActive = true; } } diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h index 046f9963b351..70fbd4762344 100644 --- a/cmds/statsd/src/metrics/MetricProducer.h +++ b/cmds/statsd/src/metrics/MetricProducer.h @@ -228,6 +228,11 @@ public: activateLocked(activationTrackerIndex, elapsedTimestampNs); } + void cancelEventActivation(int deactivationTrackerIndex) { + std::lock_guard<std::mutex> lock(mMutex); + cancelEventActivationLocked(deactivationTrackerIndex); + } + bool isActive() const { std::lock_guard<std::mutex> lock(mMutex); return isActiveLocked(); @@ -238,7 +243,8 @@ public: prepActiveForBootIfNecessaryLocked(currentTimeNs); } - void addActivation(int activationTrackerIndex, int64_t ttl_seconds); + void addActivation(int activationTrackerIndex, int64_t ttl_seconds, + int deactivationTrackerIndex = -1); inline void setActivationType(const MetricActivation::ActivationType& activationType) { mActivationType = activationType; @@ -263,6 +269,7 @@ protected: bool evaluateActiveStateLocked(int64_t elapsedTimestampNs); void activateLocked(int activationTrackerIndex, int64_t elapsedTimestampNs); + void cancelEventActivationLocked(int deactivationTrackerIndex); inline bool isActiveLocked() const { return mIsActive; @@ -391,13 +398,19 @@ protected: }; // When the metric producer has multiple activations, these activations are ORed to determine // whether the metric producer is ready to generate metrics. - std::unordered_map<int, Activation> mEventActivationMap; + std::unordered_map<int, std::shared_ptr<Activation>> mEventActivationMap; + + // Maps index of atom matcher for deactivation to Activation struct. + std::unordered_map<int, std::shared_ptr<Activation>> mEventDeactivationMap; bool mIsActive; MetricActivation::ActivationType mActivationType; FRIEND_TEST(MetricActivationE2eTest, TestCountMetric); + FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithOneDeactivation); + FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithTwoDeactivations); + FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations); FRIEND_TEST(StatsLogProcessorTest, TestActiveConfigMetricDiskWriteRead); FRIEND_TEST(StatsLogProcessorTest, TestActivationOnBoot); diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp index 4b3bfd3c8b86..095f9dde6129 100644 --- a/cmds/statsd/src/metrics/MetricsManager.cpp +++ b/cmds/statsd/src/metrics/MetricsManager.cpp @@ -74,7 +74,8 @@ MetricsManager::MetricsManager(const ConfigKey& key, const StatsdConfig& config, timeBaseNs, currentTimeNs, mTagIds, mAllAtomMatchers, mAllConditionTrackers, mAllMetricProducers, mAllAnomalyTrackers, mAllPeriodicAlarmTrackers, mConditionToMetricMap, mTrackerToMetricMap, mTrackerToConditionMap, - mActivationAtomTrackerToMetricMap, mMetricIndexesWithActivation, mNoReportMetricIds); + mActivationAtomTrackerToMetricMap, mDeactivationAtomTrackerToMetricMap, + mMetricIndexesWithActivation, mNoReportMetricIds); mHashStringsInReport = config.hash_strings_in_metric_report(); mVersionStringsInReport = config.version_strings_in_metric_report(); @@ -255,7 +256,7 @@ void MetricsManager::onDumpReport(const int64_t dumpTimeStampNs, bool MetricsManager::checkLogCredentials(const LogEvent& event) { if (android::util::AtomsInfo::kWhitelistedAtoms.find(event.GetTagId()) != - android::util::AtomsInfo::kWhitelistedAtoms.end()) + android::util::AtomsInfo::kWhitelistedAtoms.end()) { return true; } @@ -344,24 +345,61 @@ void MetricsManager::onLogEvent(const LogEvent& event) { int64_t eventTimeNs = event.GetElapsedTimestampNs(); bool isActive = mIsAlwaysActive; - for (int metric : mMetricIndexesWithActivation) { - mAllMetricProducers[metric]->flushIfExpire(eventTimeNs); - isActive |= mAllMetricProducers[metric]->isActive(); + + // Set of metrics that are still active after flushing. + unordered_set<int> activeMetricsIndices; + + // Update state of all metrics w/ activation conditions as of eventTimeNs. + for (int metricIndex : mMetricIndexesWithActivation) { + const sp<MetricProducer>& metric = mAllMetricProducers[metricIndex]; + metric->flushIfExpire(eventTimeNs); + if (metric->isActive()) { + // If this metric w/ activation condition is still active after + // flushing, remember it. + activeMetricsIndices.insert(metricIndex); + } } - mIsActive = isActive; + mIsActive = isActive || !activeMetricsIndices.empty(); if (mTagIds.find(tagId) == mTagIds.end()) { - // not interesting... + // Not interesting... return; } vector<MatchingState> matcherCache(mAllAtomMatchers.size(), MatchingState::kNotComputed); + // Evaluate all atom matchers. for (auto& matcher : mAllAtomMatchers) { matcher->onLogEvent(event, mAllAtomMatchers, matcherCache); } + // Set of metrics that received an activation cancellation. + unordered_set<int> metricIndicesWithCanceledActivations; + + // Determine which metric activations received a cancellation and cancel them. + for (const auto& it : mDeactivationAtomTrackerToMetricMap) { + if (matcherCache[it.first] == MatchingState::kMatched) { + for (int metricIndex : it.second) { + mAllMetricProducers[metricIndex]->cancelEventActivation(it.first); + metricIndicesWithCanceledActivations.insert(metricIndex); + } + } + } + + // Determine whether any metrics are no longer active after cancelling metric activations. + for (const int metricIndex : metricIndicesWithCanceledActivations) { + const sp<MetricProducer>& metric = mAllMetricProducers[metricIndex]; + metric->flushIfExpire(eventTimeNs); + if (!metric->isActive()) { + activeMetricsIndices.erase(metricIndex); + } + } + + isActive |= !activeMetricsIndices.empty(); + + + // Determine which metric activations should be turned on and turn them on for (const auto& it : mActivationAtomTrackerToMetricMap) { if (matcherCache[it.first] == MatchingState::kMatched) { for (int metricIndex : it.second) { @@ -406,12 +444,12 @@ void MetricsManager::onLogEvent(const LogEvent& event) { if (pair != mConditionToMetricMap.end()) { auto& metricList = pair->second; for (auto metricIndex : metricList) { - // metric cares about non sliced condition, and it's changed. + // Metric cares about non sliced condition, and it's changed. // Push the new condition to it directly. if (!mAllMetricProducers[metricIndex]->isConditionSliced()) { mAllMetricProducers[metricIndex]->onConditionChanged(conditionCache[i], eventTimeNs); - // metric cares about sliced conditions, and it may have changed. Send + // Metric cares about sliced conditions, and it may have changed. Send // notification, and the metric can query the sliced conditions that are // interesting to it. } else { diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h index 390446068eeb..d317f8e00d82 100644 --- a/cmds/statsd/src/metrics/MetricsManager.h +++ b/cmds/statsd/src/metrics/MetricsManager.h @@ -225,18 +225,21 @@ private: // The following map is initialized from the statsd_config. - // maps from the index of the LogMatchingTracker to index of MetricProducer. + // Maps from the index of the LogMatchingTracker to index of MetricProducer. std::unordered_map<int, std::vector<int>> mTrackerToMetricMap; - // maps from LogMatchingTracker to ConditionTracker + // Maps from LogMatchingTracker to ConditionTracker std::unordered_map<int, std::vector<int>> mTrackerToConditionMap; - // maps from ConditionTracker to MetricProducer + // Maps from ConditionTracker to MetricProducer std::unordered_map<int, std::vector<int>> mConditionToMetricMap; - // maps from life span triggering event to MetricProducers. + // Maps from life span triggering event to MetricProducers. std::unordered_map<int, std::vector<int>> mActivationAtomTrackerToMetricMap; + // Maps deactivation triggering event to MetricProducers. + std::unordered_map<int, std::vector<int>> mDeactivationAtomTrackerToMetricMap; + std::vector<int> mMetricIndexesWithActivation; void initLogSourceWhiteList(); @@ -281,6 +284,9 @@ private: FRIEND_TEST(AlarmE2eTest, TestMultipleAlarms); FRIEND_TEST(ConfigTtlE2eTest, TestCountMetric); FRIEND_TEST(MetricActivationE2eTest, TestCountMetric); + FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithOneDeactivation); + FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithTwoDeactivations); + FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations); FRIEND_TEST(StatsLogProcessorTest, TestActiveConfigMetricDiskWriteRead); FRIEND_TEST(StatsLogProcessorTest, TestActivationOnBoot); diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp index 463b5a097585..082382cbdb0d 100644 --- a/cmds/statsd/src/metrics/metrics_manager_util.cpp +++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp @@ -711,6 +711,7 @@ bool initMetricActivations(const ConfigKey& key, const StatsdConfig& config, const unordered_map<int64_t, int> &metricProducerMap, vector<sp<MetricProducer>>& allMetricProducers, unordered_map<int, std::vector<int>>& activationAtomTrackerToMetricMap, + unordered_map<int, std::vector<int>>& deactivationAtomTrackerToMetricMap, vector<int>& metricsWithActivation) { for (int i = 0; i < config.metric_activation_size(); ++i) { const MetricActivation& metric_activation = config.metric_activation(i); @@ -725,8 +726,8 @@ bool initMetricActivations(const ConfigKey& key, const StatsdConfig& config, ALOGE("Invalid metric tracker index."); return false; } - allMetricProducers[metricTrackerIndex]->setActivationType( - metric_activation.activation_type()); + const sp<MetricProducer>& metric = allMetricProducers[metricTrackerIndex]; + metric->setActivationType(metric_activation.activation_type()); metricsWithActivation.push_back(metricTrackerIndex); for (int j = 0; j < metric_activation.event_activation_size(); ++j) { const EventActivation& activation = metric_activation.event_activation(j); @@ -738,8 +739,22 @@ bool initMetricActivations(const ConfigKey& key, const StatsdConfig& config, const int atomMatcherIndex = logTrackerIt->second; activationAtomTrackerToMetricMap[atomMatcherIndex].push_back( metricTrackerIndex); - allMetricProducers[metricTrackerIndex]->addActivation( - atomMatcherIndex, activation.ttl_seconds()); + + if (activation.has_deactivation_atom_matcher_id()) { + auto deactivationAtomMatcherIt = + logEventTrackerMap.find(activation.deactivation_atom_matcher_id()); + if (deactivationAtomMatcherIt == logEventTrackerMap.end()) { + ALOGE("Atom matcher not found for event deactivation."); + return false; + } + const int deactivationMatcherIndex = deactivationAtomMatcherIt->second; + deactivationAtomTrackerToMetricMap[deactivationMatcherIndex] + .push_back(metricTrackerIndex); + metric->addActivation(atomMatcherIndex, activation.ttl_seconds(), + deactivationMatcherIndex); + } else { + metric->addActivation(atomMatcherIndex, activation.ttl_seconds()); + } } } return true; @@ -759,6 +774,7 @@ bool initStatsdConfig(const ConfigKey& key, const StatsdConfig& config, UidMap& unordered_map<int, std::vector<int>>& trackerToMetricMap, unordered_map<int, std::vector<int>>& trackerToConditionMap, unordered_map<int, std::vector<int>>& activationAtomTrackerToMetricMap, + unordered_map<int, std::vector<int>>& deactivationAtomTrackerToMetricMap, vector<int>& metricsWithActivation, std::set<int64_t>& noReportMetricIds) { unordered_map<int64_t, int> logTrackerMap; @@ -795,7 +811,8 @@ bool initStatsdConfig(const ConfigKey& key, const StatsdConfig& config, UidMap& return false; } if (!initMetricActivations(key, config, currentTimeNs, logTrackerMap, metricProducerMap, - allMetricProducers, activationAtomTrackerToMetricMap, metricsWithActivation)) { + allMetricProducers, activationAtomTrackerToMetricMap, + deactivationAtomTrackerToMetricMap, metricsWithActivation)) { ALOGE("initMetricActivations failed"); return false; } diff --git a/cmds/statsd/src/metrics/metrics_manager_util.h b/cmds/statsd/src/metrics/metrics_manager_util.h index 9ffcedae4962..028231ff908c 100644 --- a/cmds/statsd/src/metrics/metrics_manager_util.h +++ b/cmds/statsd/src/metrics/metrics_manager_util.h @@ -108,8 +108,9 @@ bool initStatsdConfig(const ConfigKey& key, const StatsdConfig& config, UidMap& std::unordered_map<int, std::vector<int>>& conditionToMetricMap, std::unordered_map<int, std::vector<int>>& trackerToMetricMap, std::unordered_map<int, std::vector<int>>& trackerToConditionMap, - unordered_map<int, std::vector<int>>& lifeSpanEventTrackerToMetricMap, - vector<int>& metricsWithLifeSpan, + unordered_map<int, std::vector<int>>& activationAtomTrackerToMetricMap, + unordered_map<int, std::vector<int>>& deactivationAtomTrackerToMetricMap, + vector<int>& metricsWithActivation, std::set<int64_t>& noReportMetricIds); bool isStateTracker(const SimplePredicate& simplePredicate, std::vector<Matcher>* primaryKeys); diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto index 0e91f527083a..257e65ee423d 100644 --- a/cmds/statsd/src/statsd_config.proto +++ b/cmds/statsd/src/statsd_config.proto @@ -380,6 +380,7 @@ message Subscription { message EventActivation { optional int64 atom_matcher_id = 1; optional int64 ttl_seconds = 2; + optional int64 deactivation_atom_matcher_id = 3; } message MetricActivation { diff --git a/cmds/statsd/tests/MetricsManager_test.cpp b/cmds/statsd/tests/MetricsManager_test.cpp index f8184d8aa14c..71adc5789d92 100644 --- a/cmds/statsd/tests/MetricsManager_test.cpp +++ b/cmds/statsd/tests/MetricsManager_test.cpp @@ -282,8 +282,9 @@ TEST(MetricsManagerTest, TestGoodConfig) { unordered_map<int, std::vector<int>> conditionToMetricMap; unordered_map<int, std::vector<int>> trackerToMetricMap; unordered_map<int, std::vector<int>> trackerToConditionMap; - unordered_map<int, std::vector<int>> lifeSpanEventTrackerToMetricMap; - vector<int> metricsWithLifeSpan; + unordered_map<int, std::vector<int>> activationAtomTrackerToMetricMap; + unordered_map<int, std::vector<int>> deactivationAtomTrackerToMetricMap; + vector<int> metricsWithActivation; std::set<int64_t> noReportMetricIds; EXPECT_TRUE(initStatsdConfig(kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor, @@ -291,8 +292,8 @@ TEST(MetricsManagerTest, TestGoodConfig) { allAtomMatchers, allConditionTrackers, allMetricProducers, allAnomalyTrackers, allAlarmTrackers, conditionToMetricMap, trackerToMetricMap, trackerToConditionMap, - lifeSpanEventTrackerToMetricMap, metricsWithLifeSpan, - noReportMetricIds)); + activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, + metricsWithActivation, noReportMetricIds)); EXPECT_EQ(1u, allMetricProducers.size()); EXPECT_EQ(1u, allAnomalyTrackers.size()); EXPECT_EQ(1u, noReportMetricIds.size()); @@ -313,8 +314,9 @@ TEST(MetricsManagerTest, TestDimensionMetricsWithMultiTags) { unordered_map<int, std::vector<int>> conditionToMetricMap; unordered_map<int, std::vector<int>> trackerToMetricMap; unordered_map<int, std::vector<int>> trackerToConditionMap; - unordered_map<int, std::vector<int>> lifeSpanEventTrackerToMetricMap; - vector<int> metricsWithLifeSpan; + unordered_map<int, std::vector<int>> activationAtomTrackerToMetricMap; + unordered_map<int, std::vector<int>> deactivationAtomTrackerToMetricMap; + vector<int> metricsWithActivation; std::set<int64_t> noReportMetricIds; EXPECT_FALSE(initStatsdConfig(kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor, @@ -322,8 +324,8 @@ TEST(MetricsManagerTest, TestDimensionMetricsWithMultiTags) { allAtomMatchers, allConditionTrackers, allMetricProducers, allAnomalyTrackers, allAlarmTrackers, conditionToMetricMap, trackerToMetricMap, trackerToConditionMap, - lifeSpanEventTrackerToMetricMap, metricsWithLifeSpan, - noReportMetricIds)); + activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, + metricsWithActivation, noReportMetricIds)); } TEST(MetricsManagerTest, TestCircleLogMatcherDependency) { @@ -341,8 +343,9 @@ TEST(MetricsManagerTest, TestCircleLogMatcherDependency) { unordered_map<int, std::vector<int>> conditionToMetricMap; unordered_map<int, std::vector<int>> trackerToMetricMap; unordered_map<int, std::vector<int>> trackerToConditionMap; - unordered_map<int, std::vector<int>> lifeSpanEventTrackerToMetricMap; - vector<int> metricsWithLifeSpan; + unordered_map<int, std::vector<int>> activationAtomTrackerToMetricMap; + unordered_map<int, std::vector<int>> deactivationAtomTrackerToMetricMap; + vector<int> metricsWithActivation; std::set<int64_t> noReportMetricIds; EXPECT_FALSE(initStatsdConfig(kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor, @@ -350,8 +353,8 @@ TEST(MetricsManagerTest, TestCircleLogMatcherDependency) { allAtomMatchers, allConditionTrackers, allMetricProducers, allAnomalyTrackers, allAlarmTrackers, conditionToMetricMap, trackerToMetricMap, trackerToConditionMap, - lifeSpanEventTrackerToMetricMap, metricsWithLifeSpan, - noReportMetricIds)); + activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, + metricsWithActivation, noReportMetricIds)); } TEST(MetricsManagerTest, TestMissingMatchers) { @@ -369,16 +372,17 @@ TEST(MetricsManagerTest, TestMissingMatchers) { unordered_map<int, std::vector<int>> conditionToMetricMap; unordered_map<int, std::vector<int>> trackerToMetricMap; unordered_map<int, std::vector<int>> trackerToConditionMap; - unordered_map<int, std::vector<int>> lifeSpanEventTrackerToMetricMap; - vector<int> metricsWithLifeSpan; + unordered_map<int, std::vector<int>> activationAtomTrackerToMetricMap; + unordered_map<int, std::vector<int>> deactivationAtomTrackerToMetricMap; + vector<int> metricsWithActivation; std::set<int64_t> noReportMetricIds; EXPECT_FALSE(initStatsdConfig(kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor, timeBaseSec, timeBaseSec, allTagIds, allAtomMatchers, allConditionTrackers, allMetricProducers, allAnomalyTrackers, allAlarmTrackers, conditionToMetricMap, trackerToMetricMap, trackerToConditionMap, - lifeSpanEventTrackerToMetricMap, metricsWithLifeSpan, - noReportMetricIds)); + activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, + metricsWithActivation, noReportMetricIds)); } TEST(MetricsManagerTest, TestMissingPredicate) { @@ -396,16 +400,17 @@ TEST(MetricsManagerTest, TestMissingPredicate) { unordered_map<int, std::vector<int>> conditionToMetricMap; unordered_map<int, std::vector<int>> trackerToMetricMap; unordered_map<int, std::vector<int>> trackerToConditionMap; - unordered_map<int, std::vector<int>> lifeSpanEventTrackerToMetricMap; - vector<int> metricsWithLifeSpan; + unordered_map<int, std::vector<int>> activationAtomTrackerToMetricMap; + unordered_map<int, std::vector<int>> deactivationAtomTrackerToMetricMap; + vector<int> metricsWithActivation; std::set<int64_t> noReportMetricIds; EXPECT_FALSE(initStatsdConfig(kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor, timeBaseSec, timeBaseSec, allTagIds, allAtomMatchers, allConditionTrackers, allMetricProducers, allAnomalyTrackers, allAlarmTrackers, conditionToMetricMap, trackerToMetricMap, trackerToConditionMap, - lifeSpanEventTrackerToMetricMap, metricsWithLifeSpan, - noReportMetricIds)); + activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, + metricsWithActivation, noReportMetricIds)); } TEST(MetricsManagerTest, TestCirclePredicateDependency) { @@ -423,8 +428,9 @@ TEST(MetricsManagerTest, TestCirclePredicateDependency) { unordered_map<int, std::vector<int>> conditionToMetricMap; unordered_map<int, std::vector<int>> trackerToMetricMap; unordered_map<int, std::vector<int>> trackerToConditionMap; - unordered_map<int, std::vector<int>> lifeSpanEventTrackerToMetricMap; - vector<int> metricsWithLifeSpan; + unordered_map<int, std::vector<int>> activationAtomTrackerToMetricMap; + unordered_map<int, std::vector<int>> deactivationAtomTrackerToMetricMap; + vector<int> metricsWithActivation; std::set<int64_t> noReportMetricIds; EXPECT_FALSE(initStatsdConfig(kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor, @@ -432,8 +438,8 @@ TEST(MetricsManagerTest, TestCirclePredicateDependency) { allAtomMatchers, allConditionTrackers, allMetricProducers, allAnomalyTrackers, allAlarmTrackers, conditionToMetricMap, trackerToMetricMap, trackerToConditionMap, - lifeSpanEventTrackerToMetricMap, metricsWithLifeSpan, - noReportMetricIds)); + activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, + metricsWithActivation, noReportMetricIds)); } TEST(MetricsManagerTest, testAlertWithUnknownMetric) { @@ -451,8 +457,9 @@ TEST(MetricsManagerTest, testAlertWithUnknownMetric) { unordered_map<int, std::vector<int>> conditionToMetricMap; unordered_map<int, std::vector<int>> trackerToMetricMap; unordered_map<int, std::vector<int>> trackerToConditionMap; - unordered_map<int, std::vector<int>> lifeSpanEventTrackerToMetricMap; - vector<int> metricsWithLifeSpan; + unordered_map<int, std::vector<int>> activationAtomTrackerToMetricMap; + unordered_map<int, std::vector<int>> deactivationAtomTrackerToMetricMap; + vector<int> metricsWithActivation; std::set<int64_t> noReportMetricIds; EXPECT_FALSE(initStatsdConfig(kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor, @@ -460,8 +467,8 @@ TEST(MetricsManagerTest, testAlertWithUnknownMetric) { allAtomMatchers, allConditionTrackers, allMetricProducers, allAnomalyTrackers, allAlarmTrackers, conditionToMetricMap, trackerToMetricMap, trackerToConditionMap, - lifeSpanEventTrackerToMetricMap, metricsWithLifeSpan, - noReportMetricIds)); + activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, + metricsWithActivation, noReportMetricIds)); } #else diff --git a/cmds/statsd/tests/StatsLogProcessor_test.cpp b/cmds/statsd/tests/StatsLogProcessor_test.cpp index 88aa1800c75a..91e282a7f796 100644 --- a/cmds/statsd/tests/StatsLogProcessor_test.cpp +++ b/cmds/statsd/tests/StatsLogProcessor_test.cpp @@ -610,26 +610,26 @@ TEST(StatsLogProcessorTest, TestActiveConfigMetricDiskWriteRead) { // Assert that all 3 metrics with activation are inactive and that the ttls were properly set. EXPECT_FALSE(metricProducer1003->isActive()); const auto& activation1003 = metricProducer1003->mEventActivationMap.begin()->second; - EXPECT_EQ(100 * NS_PER_SEC, activation1003.ttl_ns); - EXPECT_EQ(0, activation1003.activation_ns); + EXPECT_EQ(100 * NS_PER_SEC, activation1003->ttl_ns); + EXPECT_EQ(0, activation1003->activation_ns); EXPECT_FALSE(metricProducer1005->isActive()); const auto& activation1005 = metricProducer1005->mEventActivationMap.begin()->second; - EXPECT_EQ(100 * NS_PER_SEC, activation1005.ttl_ns); - EXPECT_EQ(0, activation1005.activation_ns); + EXPECT_EQ(100 * NS_PER_SEC, activation1005->ttl_ns); + EXPECT_EQ(0, activation1005->activation_ns); EXPECT_FALSE(metricProducer1006->isActive()); const auto& activation1006 = metricProducer1006->mEventActivationMap.begin()->second; - EXPECT_EQ(200 * NS_PER_SEC, activation1006.ttl_ns); - EXPECT_EQ(0, activation1006.activation_ns); + EXPECT_EQ(200 * NS_PER_SEC, activation1006->ttl_ns); + EXPECT_EQ(0, activation1006->activation_ns); processor2->LoadMetricsActivationFromDisk(); // After loading activations from disk, assert that all 3 metrics are active. EXPECT_TRUE(metricProducer1003->isActive()); - EXPECT_EQ(timeBase2 + ttl3 - activation1003.ttl_ns, activation1003.activation_ns); + EXPECT_EQ(timeBase2 + ttl3 - activation1003->ttl_ns, activation1003->activation_ns); EXPECT_TRUE(metricProducer1005->isActive()); - EXPECT_EQ(timeBase2 + ttl5 - activation1005.ttl_ns, activation1005.activation_ns); + EXPECT_EQ(timeBase2 + ttl5 - activation1005->ttl_ns, activation1005->activation_ns); EXPECT_TRUE(metricProducer1006->isActive()); - EXPECT_EQ(timeBase2 + ttl6 - activation1006.ttl_ns, activation1003.activation_ns); + EXPECT_EQ(timeBase2 + ttl6 - activation1006->ttl_ns, activation1003->activation_ns); // Make sure no more broadcasts have happened. EXPECT_EQ(broadcastCount, 1); @@ -696,17 +696,17 @@ TEST(StatsLogProcessorTest, TestActivationOnBoot) { EXPECT_TRUE(metricProducer2->isActive()); const auto& activation1 = metricProducer1->mEventActivationMap.begin()->second; - EXPECT_EQ(100 * NS_PER_SEC, activation1.ttl_ns); - EXPECT_EQ(0, activation1.activation_ns); - EXPECT_EQ(kNotActive, activation1.state); + EXPECT_EQ(100 * NS_PER_SEC, activation1->ttl_ns); + EXPECT_EQ(0, activation1->activation_ns); + EXPECT_EQ(kNotActive, activation1->state); std::vector<AttributionNodeInternal> attributions1 = {CreateAttribution(111, "App1")}; auto event = CreateAcquireWakelockEvent(attributions1, "wl1", 100 + timeBase1); processor->OnLogEvent(event.get()); EXPECT_FALSE(metricProducer1->isActive()); - EXPECT_EQ(0, activation1.activation_ns); - EXPECT_EQ(kActiveOnBoot, activation1.state); + EXPECT_EQ(0, activation1->activation_ns); + EXPECT_EQ(kActiveOnBoot, activation1->state); int64_t shutDownTime = timeBase1 + 100 * NS_PER_SEC; @@ -746,14 +746,14 @@ TEST(StatsLogProcessorTest, TestActivationOnBoot) { EXPECT_TRUE(metricProducer1002->isActive()); const auto& activation1001 = metricProducer1001->mEventActivationMap.begin()->second; - EXPECT_EQ(100 * NS_PER_SEC, activation1001.ttl_ns); - EXPECT_EQ(0, activation1001.activation_ns); - EXPECT_EQ(kNotActive, activation1001.state); + EXPECT_EQ(100 * NS_PER_SEC, activation1001->ttl_ns); + EXPECT_EQ(0, activation1001->activation_ns); + EXPECT_EQ(kNotActive, activation1001->state); processor2->LoadMetricsActivationFromDisk(); EXPECT_TRUE(metricProducer1001->isActive()); - EXPECT_EQ(timeBase2 + ttl1 - activation1001.ttl_ns, activation1001.activation_ns); + EXPECT_EQ(timeBase2 + ttl1 - activation1001->ttl_ns, activation1001->activation_ns); } #else diff --git a/cmds/statsd/tests/e2e/MetricActivation_e2e_test.cpp b/cmds/statsd/tests/e2e/MetricActivation_e2e_test.cpp index 7fb43f8aa60e..bf52bb04e71d 100644 --- a/cmds/statsd/tests/e2e/MetricActivation_e2e_test.cpp +++ b/cmds/statsd/tests/e2e/MetricActivation_e2e_test.cpp @@ -31,13 +31,85 @@ namespace { StatsdConfig CreateStatsdConfig() { StatsdConfig config; config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + auto saverModeMatcher = CreateBatterySaverModeStartAtomMatcher(); + auto crashMatcher = CreateProcessCrashAtomMatcher(); + auto screenOnMatcher = CreateScreenTurnedOnAtomMatcher(); + + *config.add_atom_matcher() = saverModeMatcher; + *config.add_atom_matcher() = crashMatcher; + *config.add_atom_matcher() = screenOnMatcher; + + int64_t metricId = 123456; + auto countMetric = config.add_count_metric(); + countMetric->set_id(metricId); + countMetric->set_what(crashMatcher.id()); + countMetric->set_bucket(FIVE_MINUTES); + countMetric->mutable_dimensions_in_what()->set_field( + android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED); + countMetric->mutable_dimensions_in_what()->add_child()->set_field(1); // uid field + + auto metric_activation1 = config.add_metric_activation(); + metric_activation1->set_metric_id(metricId); + auto event_activation1 = metric_activation1->add_event_activation(); + event_activation1->set_atom_matcher_id(saverModeMatcher.id()); + event_activation1->set_ttl_seconds(60 * 6); // 6 minutes + auto event_activation2 = metric_activation1->add_event_activation(); + event_activation2->set_atom_matcher_id(screenOnMatcher.id()); + event_activation2->set_ttl_seconds(60 * 2); // 2 minutes + + return config; +} + +StatsdConfig CreateStatsdConfigWithOneDeactivation() { + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + auto saverModeMatcher = CreateBatterySaverModeStartAtomMatcher(); auto crashMatcher = CreateProcessCrashAtomMatcher(); auto screenOnMatcher = CreateScreenTurnedOnAtomMatcher(); + auto brightnessChangedMatcher = CreateScreenBrightnessChangedAtomMatcher(); + + *config.add_atom_matcher() = saverModeMatcher; + *config.add_atom_matcher() = crashMatcher; + *config.add_atom_matcher() = screenOnMatcher; + *config.add_atom_matcher() = brightnessChangedMatcher; + + int64_t metricId = 123456; + auto countMetric = config.add_count_metric(); + countMetric->set_id(metricId); + countMetric->set_what(crashMatcher.id()); + countMetric->set_bucket(FIVE_MINUTES); + countMetric->mutable_dimensions_in_what()->set_field( + android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED); + countMetric->mutable_dimensions_in_what()->add_child()->set_field(1); // uid field + + auto metric_activation1 = config.add_metric_activation(); + metric_activation1->set_metric_id(metricId); + auto event_activation1 = metric_activation1->add_event_activation(); + event_activation1->set_atom_matcher_id(saverModeMatcher.id()); + event_activation1->set_ttl_seconds(60 * 6); // 6 minutes + event_activation1->set_deactivation_atom_matcher_id(brightnessChangedMatcher.id()); + auto event_activation2 = metric_activation1->add_event_activation(); + event_activation2->set_atom_matcher_id(screenOnMatcher.id()); + event_activation2->set_ttl_seconds(60 * 2); // 2 minutes + + return config; +} + +StatsdConfig CreateStatsdConfigWithTwoDeactivations() { + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. auto saverModeMatcher = CreateBatterySaverModeStartAtomMatcher(); + auto crashMatcher = CreateProcessCrashAtomMatcher(); + auto screenOnMatcher = CreateScreenTurnedOnAtomMatcher(); + auto brightnessChangedMatcher = CreateScreenBrightnessChangedAtomMatcher(); + auto brightnessChangedMatcher2 = CreateScreenBrightnessChangedAtomMatcher(); + brightnessChangedMatcher2.set_id(StringToId("ScreenBrightnessChanged2")); *config.add_atom_matcher() = saverModeMatcher; *config.add_atom_matcher() = crashMatcher; *config.add_atom_matcher() = screenOnMatcher; + *config.add_atom_matcher() = brightnessChangedMatcher; + *config.add_atom_matcher() = brightnessChangedMatcher2; int64_t metricId = 123456; auto countMetric = config.add_count_metric(); @@ -53,9 +125,72 @@ StatsdConfig CreateStatsdConfig() { auto event_activation1 = metric_activation1->add_event_activation(); event_activation1->set_atom_matcher_id(saverModeMatcher.id()); event_activation1->set_ttl_seconds(60 * 6); // 6 minutes + event_activation1->set_deactivation_atom_matcher_id(brightnessChangedMatcher.id()); auto event_activation2 = metric_activation1->add_event_activation(); event_activation2->set_atom_matcher_id(screenOnMatcher.id()); event_activation2->set_ttl_seconds(60 * 2); // 2 minutes + event_activation2->set_deactivation_atom_matcher_id(brightnessChangedMatcher2.id()); + + return config; +} + +StatsdConfig CreateStatsdConfigWithTwoMetricsTwoDeactivations() { + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + auto saverModeMatcher = CreateBatterySaverModeStartAtomMatcher(); + auto crashMatcher = CreateProcessCrashAtomMatcher(); + auto foregroundMatcher = CreateMoveToForegroundAtomMatcher(); + auto screenOnMatcher = CreateScreenTurnedOnAtomMatcher(); + auto brightnessChangedMatcher = CreateScreenBrightnessChangedAtomMatcher(); + auto brightnessChangedMatcher2 = CreateScreenBrightnessChangedAtomMatcher(); + brightnessChangedMatcher2.set_id(StringToId("ScreenBrightnessChanged2")); + + *config.add_atom_matcher() = saverModeMatcher; + *config.add_atom_matcher() = crashMatcher; + *config.add_atom_matcher() = screenOnMatcher; + *config.add_atom_matcher() = brightnessChangedMatcher; + *config.add_atom_matcher() = brightnessChangedMatcher2; + *config.add_atom_matcher() = foregroundMatcher; + + int64_t metricId = 123456; + auto countMetric = config.add_count_metric(); + countMetric->set_id(metricId); + countMetric->set_what(crashMatcher.id()); + countMetric->set_bucket(FIVE_MINUTES); + countMetric->mutable_dimensions_in_what()->set_field( + android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED); + countMetric->mutable_dimensions_in_what()->add_child()->set_field(1); // uid field + + int64_t metricId2 = 234567; + countMetric = config.add_count_metric(); + countMetric->set_id(metricId2); + countMetric->set_what(foregroundMatcher.id()); + countMetric->set_bucket(FIVE_MINUTES); + countMetric->mutable_dimensions_in_what()->set_field( + android::util::ACTIVITY_FOREGROUND_STATE_CHANGED); + countMetric->mutable_dimensions_in_what()->add_child()->set_field(1); // uid field + + auto metric_activation1 = config.add_metric_activation(); + metric_activation1->set_metric_id(metricId); + auto event_activation1 = metric_activation1->add_event_activation(); + event_activation1->set_atom_matcher_id(saverModeMatcher.id()); + event_activation1->set_ttl_seconds(60 * 6); // 6 minutes + event_activation1->set_deactivation_atom_matcher_id(brightnessChangedMatcher.id()); + auto event_activation2 = metric_activation1->add_event_activation(); + event_activation2->set_atom_matcher_id(screenOnMatcher.id()); + event_activation2->set_ttl_seconds(60 * 2); // 2 minutes + event_activation2->set_deactivation_atom_matcher_id(brightnessChangedMatcher2.id()); + + metric_activation1 = config.add_metric_activation(); + metric_activation1->set_metric_id(metricId2); + event_activation1 = metric_activation1->add_event_activation(); + event_activation1->set_atom_matcher_id(saverModeMatcher.id()); + event_activation1->set_ttl_seconds(60 * 6); // 6 minutes + event_activation1->set_deactivation_atom_matcher_id(brightnessChangedMatcher.id()); + event_activation2 = metric_activation1->add_event_activation(); + event_activation2->set_atom_matcher_id(screenOnMatcher.id()); + event_activation2->set_ttl_seconds(60 * 2); // 2 minutes + event_activation2->set_deactivation_atom_matcher_id(brightnessChangedMatcher2.id()); return config; } @@ -65,8 +200,9 @@ StatsdConfig CreateStatsdConfig() { TEST(MetricActivationE2eTest, TestCountMetric) { auto config = CreateStatsdConfig(); - int64_t bucketStartTimeNs = 10000000000; - int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000; + int64_t bucketStartTimeNs = NS_PER_SEC * 10; // 10 secs + int64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000LL * 1000LL; int uid = 12345; int64_t cfgId = 98765; @@ -108,12 +244,12 @@ TEST(MetricActivationE2eTest, TestCountMetric) { EXPECT_EQ(eventActivationMap.size(), 2u); EXPECT_TRUE(eventActivationMap.find(0) != eventActivationMap.end()); EXPECT_TRUE(eventActivationMap.find(2) != eventActivationMap.end()); - EXPECT_EQ(eventActivationMap[0].state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap[0].activation_ns, 0); - EXPECT_EQ(eventActivationMap[0].ttl_ns, 60 * 6 * NS_PER_SEC); - EXPECT_EQ(eventActivationMap[2].state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap[2].activation_ns, 0); - EXPECT_EQ(eventActivationMap[2].ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[0]->activation_ns, 0); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[2]->activation_ns, 0); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); std::unique_ptr<LogEvent> event; @@ -131,12 +267,12 @@ TEST(MetricActivationE2eTest, TestCountMetric) { EXPECT_EQ(broadcastCount, 1); EXPECT_EQ(activeConfigsBroadcast.size(), 1); EXPECT_EQ(activeConfigsBroadcast[0], cfgId); - EXPECT_EQ(eventActivationMap[0].state, ActivationState::kActive); - EXPECT_EQ(eventActivationMap[0].activation_ns, bucketStartTimeNs + 10); - EXPECT_EQ(eventActivationMap[0].ttl_ns, 60 * 6 * NS_PER_SEC); - EXPECT_EQ(eventActivationMap[2].state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap[2].activation_ns, 0); - EXPECT_EQ(eventActivationMap[2].ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + 10); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[2]->activation_ns, 0); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); // First processed event. event = CreateAppCrashEvent(222, bucketStartTimeNs + 15); @@ -148,12 +284,12 @@ TEST(MetricActivationE2eTest, TestCountMetric) { processor.OnLogEvent(event.get()); EXPECT_TRUE(metricsManager->isActive()); EXPECT_TRUE(metricProducer->mIsActive); - EXPECT_EQ(eventActivationMap[0].state, ActivationState::kActive); - EXPECT_EQ(eventActivationMap[0].activation_ns, bucketStartTimeNs + 10); - EXPECT_EQ(eventActivationMap[0].ttl_ns, 60 * 6 * NS_PER_SEC); - EXPECT_EQ(eventActivationMap[2].state, ActivationState::kActive); - EXPECT_EQ(eventActivationMap[2].activation_ns, bucketStartTimeNs + 20); - EXPECT_EQ(eventActivationMap[2].ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + 10); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + 20); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); // 2nd processed event. // The activation by screen_on event expires, but the one by battery save mode is still active. @@ -161,12 +297,12 @@ TEST(MetricActivationE2eTest, TestCountMetric) { processor.OnLogEvent(event.get()); EXPECT_TRUE(metricsManager->isActive()); EXPECT_TRUE(metricProducer->mIsActive); - EXPECT_EQ(eventActivationMap[0].state, ActivationState::kActive); - EXPECT_EQ(eventActivationMap[0].activation_ns, bucketStartTimeNs + 10); - EXPECT_EQ(eventActivationMap[0].ttl_ns, 60 * 6 * NS_PER_SEC); - EXPECT_EQ(eventActivationMap[2].state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap[2].activation_ns, bucketStartTimeNs + 20); - EXPECT_EQ(eventActivationMap[2].ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + 10); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + 20); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); // No new broadcast since the config should still be active. EXPECT_EQ(broadcastCount, 1); @@ -182,14 +318,14 @@ TEST(MetricActivationE2eTest, TestCountMetric) { // New broadcast since the config is no longer active. EXPECT_EQ(broadcastCount, 2); EXPECT_EQ(activeConfigsBroadcast.size(), 0); - EXPECT_EQ(eventActivationMap[0].state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap[0].activation_ns, bucketStartTimeNs + 10); - EXPECT_EQ(eventActivationMap[0].ttl_ns, 60 * 6 * NS_PER_SEC); - EXPECT_EQ(eventActivationMap[2].state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap[2].activation_ns, bucketStartTimeNs + 20); - EXPECT_EQ(eventActivationMap[2].ttl_ns, 60 * 2 * NS_PER_SEC); - - // Re-activate. + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + 10); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + 20); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + + // Re-activate metric via screen on. event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); processor.OnLogEvent(event.get()); @@ -198,13 +334,14 @@ TEST(MetricActivationE2eTest, TestCountMetric) { EXPECT_EQ(broadcastCount, 3); EXPECT_EQ(activeConfigsBroadcast.size(), 1); EXPECT_EQ(activeConfigsBroadcast[0], cfgId); - EXPECT_EQ(eventActivationMap[0].state, ActivationState::kNotActive); - EXPECT_EQ(eventActivationMap[0].activation_ns, bucketStartTimeNs + 10); - EXPECT_EQ(eventActivationMap[0].ttl_ns, 60 * 6 * NS_PER_SEC); - EXPECT_EQ(eventActivationMap[2].state, ActivationState::kActive); - EXPECT_EQ(eventActivationMap[2].activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); - EXPECT_EQ(eventActivationMap[2].ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + 10); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + // 4th processed event. event = CreateAppCrashEvent(666, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1); processor.OnLogEvent(event.get()); @@ -272,9 +409,1192 @@ TEST(MetricActivationE2eTest, TestCountMetric) { data.bucket_info(0).start_bucket_elapsed_nanos()); EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); +} + +TEST(MetricActivationE2eTest, TestCountMetricWithOneDeactivation) { + auto config = CreateStatsdConfigWithOneDeactivation(); + + int64_t bucketStartTimeNs = NS_PER_SEC * 10; // 10 secs + int64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000LL * 1000LL; + + int uid = 12345; + int64_t cfgId = 98765; + ConfigKey cfgKey(uid, cfgId); + + sp<UidMap> m = new UidMap(); + sp<StatsPullerManager> pullerManager = new StatsPullerManager(); + sp<AlarmMonitor> anomalyAlarmMonitor; + sp<AlarmMonitor> subscriberAlarmMonitor; + vector<int64_t> activeConfigsBroadcast; + + long timeBase1 = 1; + int broadcastCount = 0; + StatsLogProcessor processor(m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, + bucketStartTimeNs, [](const ConfigKey& key) { return true; }, + [&uid, &broadcastCount, &activeConfigsBroadcast](const int& broadcastUid, + const vector<int64_t>& activeConfigs) { + broadcastCount++; + EXPECT_EQ(broadcastUid, uid); + activeConfigsBroadcast.clear(); + activeConfigsBroadcast.insert(activeConfigsBroadcast.end(), + activeConfigs.begin(), activeConfigs.end()); + return true; + }); + + processor.OnConfigUpdated(bucketStartTimeNs, cfgKey, config); + + EXPECT_EQ(processor.mMetricsManagers.size(), 1u); + sp<MetricsManager> metricsManager = processor.mMetricsManagers.begin()->second; + EXPECT_TRUE(metricsManager->isConfigValid()); + EXPECT_EQ(metricsManager->mAllMetricProducers.size(), 1); + sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0]; + auto& eventActivationMap = metricProducer->mEventActivationMap; + auto& eventDeactivationMap = metricProducer->mEventDeactivationMap; + + EXPECT_FALSE(metricsManager->isActive()); + EXPECT_FALSE(metricProducer->mIsActive); + // Two activations: one is triggered by battery saver mode (tracker index 0), the other is + // triggered by screen on event (tracker index 2). + EXPECT_EQ(eventActivationMap.size(), 2u); + EXPECT_TRUE(eventActivationMap.find(0) != eventActivationMap.end()); + EXPECT_TRUE(eventActivationMap.find(2) != eventActivationMap.end()); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[0]->activation_ns, 0); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[2]->activation_ns, 0); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap.size(), 1u); + EXPECT_TRUE(eventDeactivationMap.find(3) != eventDeactivationMap.end()); + EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]); + + std::unique_ptr<LogEvent> event; + + event = CreateAppCrashEvent(111, bucketStartTimeNs + 5); + processor.OnLogEvent(event.get()); + EXPECT_FALSE(metricsManager->isActive()); + EXPECT_FALSE(metricProducer->mIsActive); + EXPECT_EQ(broadcastCount, 0); + + // Activated by battery save mode. + event = CreateBatterySaverOnEvent(bucketStartTimeNs + 10); + processor.OnLogEvent(event.get()); + EXPECT_TRUE(metricsManager->isActive()); + EXPECT_TRUE(metricProducer->mIsActive); + EXPECT_EQ(broadcastCount, 1); + EXPECT_EQ(activeConfigsBroadcast.size(), 1); + EXPECT_EQ(activeConfigsBroadcast[0], cfgId); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + 10); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[2]->activation_ns, 0); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]); + + // First processed event. + event = CreateAppCrashEvent(222, bucketStartTimeNs + 15); + processor.OnLogEvent(event.get()); + + // Activated by screen on event. + event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, + bucketStartTimeNs + 20); + processor.OnLogEvent(event.get()); + EXPECT_TRUE(metricsManager->isActive()); + EXPECT_TRUE(metricProducer->mIsActive); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + 10); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + 20); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]); + + // 2nd processed event. + // The activation by screen_on event expires, but the one by battery save mode is still active. + event = CreateAppCrashEvent(333, bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25); + processor.OnLogEvent(event.get()); + EXPECT_TRUE(metricsManager->isActive()); + EXPECT_TRUE(metricProducer->mIsActive); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + 10); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + 20); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]); + // No new broadcast since the config should still be active. + EXPECT_EQ(broadcastCount, 1); + + // 3rd processed event. + event = CreateAppCrashEvent(444, bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25); + processor.OnLogEvent(event.get()); + + // All activations expired. + event = CreateAppCrashEvent(555, bucketStartTimeNs + NS_PER_SEC * 60 * 8); + processor.OnLogEvent(event.get()); + EXPECT_FALSE(metricsManager->isActive()); + EXPECT_FALSE(metricProducer->mIsActive); + // New broadcast since the config is no longer active. + EXPECT_EQ(broadcastCount, 2); + EXPECT_EQ(activeConfigsBroadcast.size(), 0); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + 10); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + 20); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]); + + // Re-activate metric via screen on. + event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, + bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); + processor.OnLogEvent(event.get()); + EXPECT_TRUE(metricsManager->isActive()); + EXPECT_TRUE(metricProducer->mIsActive); + EXPECT_EQ(broadcastCount, 3); + EXPECT_EQ(activeConfigsBroadcast.size(), 1); + EXPECT_EQ(activeConfigsBroadcast[0], cfgId); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + 10); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]); + + // 4th processed event. + event = CreateAppCrashEvent(666, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1); + processor.OnLogEvent(event.get()); + + // Re-enable battery saver mode activation. + event = CreateBatterySaverOnEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); + processor.OnLogEvent(event.get()); + EXPECT_TRUE(metricsManager->isActive()); + EXPECT_TRUE(metricProducer->mIsActive); + EXPECT_EQ(broadcastCount, 3); + EXPECT_EQ(activeConfigsBroadcast.size(), 1); + EXPECT_EQ(activeConfigsBroadcast[0], cfgId); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]); + + // 5th processed event. + event = CreateAppCrashEvent(777, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 40); + processor.OnLogEvent(event.get()); + + // Cancel battery saver mode activation. + event = CreateScreenBrightnessChangedEvent(64, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 60); + processor.OnLogEvent(event.get()); + EXPECT_TRUE(metricsManager->isActive()); + EXPECT_TRUE(metricProducer->mIsActive); + EXPECT_EQ(broadcastCount, 3); + EXPECT_EQ(activeConfigsBroadcast.size(), 1); + EXPECT_EQ(activeConfigsBroadcast[0], cfgId); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]); + + // Screen-on activation expired. + event = CreateAppCrashEvent(888, bucketStartTimeNs + NS_PER_SEC * 60 * 13); + processor.OnLogEvent(event.get()); + EXPECT_FALSE(metricsManager->isActive()); + EXPECT_FALSE(metricProducer->mIsActive); + // New broadcast since the config is no longer active. + EXPECT_EQ(broadcastCount, 4); + EXPECT_EQ(activeConfigsBroadcast.size(), 0); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]); + + event = CreateAppCrashEvent(999, bucketStartTimeNs + NS_PER_SEC * 60 * 14 + 1); + processor.OnLogEvent(event.get()); + + // Re-enable battery saver mode activation. + event = CreateBatterySaverOnEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15); + processor.OnLogEvent(event.get()); + EXPECT_TRUE(metricsManager->isActive()); + EXPECT_TRUE(metricProducer->mIsActive); + EXPECT_EQ(broadcastCount, 5); + EXPECT_EQ(activeConfigsBroadcast.size(), 1); + EXPECT_EQ(activeConfigsBroadcast[0], cfgId); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]); + + // Cancel battery saver mode activation. + event = CreateScreenBrightnessChangedEvent(140, bucketStartTimeNs + NS_PER_SEC * 60 * 16); + processor.OnLogEvent(event.get()); + EXPECT_FALSE(metricsManager->isActive()); + EXPECT_FALSE(metricProducer->mIsActive); + EXPECT_EQ(broadcastCount, 6); + EXPECT_EQ(activeConfigsBroadcast.size(), 0); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]); + + ConfigMetricsReportList reports; + vector<uint8_t> buffer; + processor.onDumpReport(cfgKey, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 1, false, true, + ADB_DUMP, FAST, &buffer); + EXPECT_TRUE(buffer.size() > 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStartEndTimestamp(&reports); + EXPECT_EQ(1, reports.reports_size()); + EXPECT_EQ(1, reports.reports(0).metrics_size()); + EXPECT_EQ(5, reports.reports(0).metrics(0).count_metrics().data_size()); + + StatsLogReport::CountMetricDataWrapper countMetrics; + sortMetricDataByDimensionsValue( + reports.reports(0).metrics(0).count_metrics(), &countMetrics); + EXPECT_EQ(5, countMetrics.data_size()); + + auto data = countMetrics.data(0); + EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); + EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(1 /* uid field */, + data.dimensions_in_what().value_tuple().dimensions_value(0).field()); + EXPECT_EQ(222, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); + EXPECT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); + + data = countMetrics.data(1); + EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); + EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(1 /* uid field */, + data.dimensions_in_what().value_tuple().dimensions_value(0).field()); + EXPECT_EQ(333, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); + EXPECT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); + + data = countMetrics.data(2); + EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); + EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(1 /* uid field */, + data.dimensions_in_what().value_tuple().dimensions_value(0).field()); + EXPECT_EQ(444, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); + EXPECT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + // Partial bucket as metric is deactivated. + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + NS_PER_SEC * 60 * 8, + data.bucket_info(0).end_bucket_elapsed_nanos()); + data = countMetrics.data(3); + EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); + EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(1 /* uid field */, + data.dimensions_in_what().value_tuple().dimensions_value(0).field()); + EXPECT_EQ(666, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); + EXPECT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, + data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + NS_PER_SEC * 60 * 13, + data.bucket_info(0).end_bucket_elapsed_nanos()); + + data = countMetrics.data(4); + EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); + EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(1 /* uid field */, + data.dimensions_in_what().value_tuple().dimensions_value(0).field()); + EXPECT_EQ(777, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); + EXPECT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, + data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + NS_PER_SEC * 60 * 13, + data.bucket_info(0).end_bucket_elapsed_nanos()); } +TEST(MetricActivationE2eTest, TestCountMetricWithTwoDeactivations) { + auto config = CreateStatsdConfigWithTwoDeactivations(); + + int64_t bucketStartTimeNs = NS_PER_SEC * 10; // 10 secs + int64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000LL * 1000LL; + + int uid = 12345; + int64_t cfgId = 98765; + ConfigKey cfgKey(uid, cfgId); + + sp<UidMap> m = new UidMap(); + sp<StatsPullerManager> pullerManager = new StatsPullerManager(); + sp<AlarmMonitor> anomalyAlarmMonitor; + sp<AlarmMonitor> subscriberAlarmMonitor; + vector<int64_t> activeConfigsBroadcast; + + long timeBase1 = 1; + int broadcastCount = 0; + StatsLogProcessor processor(m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, + bucketStartTimeNs, [](const ConfigKey& key) { return true; }, + [&uid, &broadcastCount, &activeConfigsBroadcast](const int& broadcastUid, + const vector<int64_t>& activeConfigs) { + broadcastCount++; + EXPECT_EQ(broadcastUid, uid); + activeConfigsBroadcast.clear(); + activeConfigsBroadcast.insert(activeConfigsBroadcast.end(), + activeConfigs.begin(), activeConfigs.end()); + return true; + }); + + processor.OnConfigUpdated(bucketStartTimeNs, cfgKey, config); + + EXPECT_EQ(processor.mMetricsManagers.size(), 1u); + sp<MetricsManager> metricsManager = processor.mMetricsManagers.begin()->second; + EXPECT_TRUE(metricsManager->isConfigValid()); + EXPECT_EQ(metricsManager->mAllMetricProducers.size(), 1); + sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0]; + auto& eventActivationMap = metricProducer->mEventActivationMap; + auto& eventDeactivationMap = metricProducer->mEventDeactivationMap; + + EXPECT_FALSE(metricsManager->isActive()); + EXPECT_FALSE(metricProducer->mIsActive); + // Two activations: one is triggered by battery saver mode (tracker index 0), the other is + // triggered by screen on event (tracker index 2). + EXPECT_EQ(eventActivationMap.size(), 2u); + EXPECT_TRUE(eventActivationMap.find(0) != eventActivationMap.end()); + EXPECT_TRUE(eventActivationMap.find(2) != eventActivationMap.end()); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[0]->activation_ns, 0); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[2]->activation_ns, 0); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap.size(), 2u); + EXPECT_TRUE(eventDeactivationMap.find(3) != eventDeactivationMap.end()); + EXPECT_TRUE(eventDeactivationMap.find(4) != eventDeactivationMap.end()); + EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]); + + std::unique_ptr<LogEvent> event; + + event = CreateAppCrashEvent(111, bucketStartTimeNs + 5); + processor.OnLogEvent(event.get()); + EXPECT_FALSE(metricsManager->isActive()); + EXPECT_FALSE(metricProducer->mIsActive); + EXPECT_EQ(broadcastCount, 0); + + // Activated by battery save mode. + event = CreateBatterySaverOnEvent(bucketStartTimeNs + 10); + processor.OnLogEvent(event.get()); + EXPECT_TRUE(metricsManager->isActive()); + EXPECT_TRUE(metricProducer->mIsActive); + EXPECT_EQ(broadcastCount, 1); + EXPECT_EQ(activeConfigsBroadcast.size(), 1); + EXPECT_EQ(activeConfigsBroadcast[0], cfgId); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + 10); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[2]->activation_ns, 0); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]); + + // First processed event. + event = CreateAppCrashEvent(222, bucketStartTimeNs + 15); + processor.OnLogEvent(event.get()); + + // Activated by screen on event. + event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, + bucketStartTimeNs + 20); + processor.OnLogEvent(event.get()); + EXPECT_TRUE(metricsManager->isActive()); + EXPECT_TRUE(metricProducer->mIsActive); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + 10); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + 20); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]); + + // 2nd processed event. + // The activation by screen_on event expires, but the one by battery save mode is still active. + event = CreateAppCrashEvent(333, bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25); + processor.OnLogEvent(event.get()); + EXPECT_TRUE(metricsManager->isActive()); + EXPECT_TRUE(metricProducer->mIsActive); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + 10); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + 20); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]); + // No new broadcast since the config should still be active. + EXPECT_EQ(broadcastCount, 1); + + // 3rd processed event. + event = CreateAppCrashEvent(444, bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25); + processor.OnLogEvent(event.get()); + + // All activations expired. + event = CreateAppCrashEvent(555, bucketStartTimeNs + NS_PER_SEC * 60 * 8); + processor.OnLogEvent(event.get()); + EXPECT_FALSE(metricsManager->isActive()); + EXPECT_FALSE(metricProducer->mIsActive); + // New broadcast since the config is no longer active. + EXPECT_EQ(broadcastCount, 2); + EXPECT_EQ(activeConfigsBroadcast.size(), 0); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + 10); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + 20); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]); + + // Re-activate metric via screen on. + event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, + bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); + processor.OnLogEvent(event.get()); + EXPECT_TRUE(metricsManager->isActive()); + EXPECT_TRUE(metricProducer->mIsActive); + EXPECT_EQ(broadcastCount, 3); + EXPECT_EQ(activeConfigsBroadcast.size(), 1); + EXPECT_EQ(activeConfigsBroadcast[0], cfgId); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + 10); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]); + + // 4th processed event. + event = CreateAppCrashEvent(666, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1); + processor.OnLogEvent(event.get()); + + // Re-enable battery saver mode activation. + event = CreateBatterySaverOnEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); + processor.OnLogEvent(event.get()); + EXPECT_TRUE(metricsManager->isActive()); + EXPECT_TRUE(metricProducer->mIsActive); + EXPECT_EQ(broadcastCount, 3); + EXPECT_EQ(activeConfigsBroadcast.size(), 1); + EXPECT_EQ(activeConfigsBroadcast[0], cfgId); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]); + + // 5th processed event. + event = CreateAppCrashEvent(777, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 40); + processor.OnLogEvent(event.get()); + + // Cancel battery saver mode and screen on activation. + event = CreateScreenBrightnessChangedEvent(64, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 60); + processor.OnLogEvent(event.get()); + EXPECT_FALSE(metricsManager->isActive()); + EXPECT_FALSE(metricProducer->mIsActive); + // New broadcast since the config is no longer active. + EXPECT_EQ(broadcastCount, 4); + EXPECT_EQ(activeConfigsBroadcast.size(), 0); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]); + + // Screen-on activation expired. + event = CreateAppCrashEvent(888, bucketStartTimeNs + NS_PER_SEC * 60 * 13); + processor.OnLogEvent(event.get()); + EXPECT_FALSE(metricsManager->isActive()); + EXPECT_FALSE(metricProducer->mIsActive); + EXPECT_EQ(broadcastCount, 4); + EXPECT_EQ(activeConfigsBroadcast.size(), 0); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]); + + event = CreateAppCrashEvent(999, bucketStartTimeNs + NS_PER_SEC * 60 * 14 + 1); + processor.OnLogEvent(event.get()); + + // Re-enable battery saver mode activation. + event = CreateBatterySaverOnEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15); + processor.OnLogEvent(event.get()); + EXPECT_TRUE(metricsManager->isActive()); + EXPECT_TRUE(metricProducer->mIsActive); + EXPECT_EQ(broadcastCount, 5); + EXPECT_EQ(activeConfigsBroadcast.size(), 1); + EXPECT_EQ(activeConfigsBroadcast[0], cfgId); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]); + + // Cancel battery saver mode and screen on activation. + event = CreateScreenBrightnessChangedEvent(140, bucketStartTimeNs + NS_PER_SEC * 60 * 16); + processor.OnLogEvent(event.get()); + EXPECT_FALSE(metricsManager->isActive()); + EXPECT_FALSE(metricProducer->mIsActive); + EXPECT_EQ(broadcastCount, 6); + EXPECT_EQ(activeConfigsBroadcast.size(), 0); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]); + + ConfigMetricsReportList reports; + vector<uint8_t> buffer; + processor.onDumpReport(cfgKey, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 1, false, true, + ADB_DUMP, FAST, &buffer); + EXPECT_TRUE(buffer.size() > 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStartEndTimestamp(&reports); + EXPECT_EQ(1, reports.reports_size()); + EXPECT_EQ(1, reports.reports(0).metrics_size()); + EXPECT_EQ(5, reports.reports(0).metrics(0).count_metrics().data_size()); + + StatsLogReport::CountMetricDataWrapper countMetrics; + sortMetricDataByDimensionsValue( + reports.reports(0).metrics(0).count_metrics(), &countMetrics); + EXPECT_EQ(5, countMetrics.data_size()); + + auto data = countMetrics.data(0); + EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); + EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(1 /* uid field */, + data.dimensions_in_what().value_tuple().dimensions_value(0).field()); + EXPECT_EQ(222, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); + EXPECT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); + + data = countMetrics.data(1); + EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); + EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(1 /* uid field */, + data.dimensions_in_what().value_tuple().dimensions_value(0).field()); + EXPECT_EQ(333, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); + EXPECT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); + + data = countMetrics.data(2); + EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); + EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(1 /* uid field */, + data.dimensions_in_what().value_tuple().dimensions_value(0).field()); + EXPECT_EQ(444, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); + EXPECT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + // Partial bucket as metric is deactivated. + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + NS_PER_SEC * 60 * 8, + data.bucket_info(0).end_bucket_elapsed_nanos()); + + data = countMetrics.data(3); + EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); + EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(1 /* uid field */, + data.dimensions_in_what().value_tuple().dimensions_value(0).field()); + EXPECT_EQ(666, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); + EXPECT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, + data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + NS_PER_SEC * 60 * 11, + data.bucket_info(0).end_bucket_elapsed_nanos()); + + data = countMetrics.data(4); + EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); + EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(1 /* uid field */, + data.dimensions_in_what().value_tuple().dimensions_value(0).field()); + EXPECT_EQ(777, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); + EXPECT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, + data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + NS_PER_SEC * 60 * 11, + data.bucket_info(0).end_bucket_elapsed_nanos()); +} + +TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations) { + auto config = CreateStatsdConfigWithTwoMetricsTwoDeactivations(); + + int64_t bucketStartTimeNs = NS_PER_SEC * 10; // 10 secs + int64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000LL * 1000LL; + + int uid = 12345; + int64_t cfgId = 98765; + ConfigKey cfgKey(uid, cfgId); + + sp<UidMap> m = new UidMap(); + sp<StatsPullerManager> pullerManager = new StatsPullerManager(); + sp<AlarmMonitor> anomalyAlarmMonitor; + sp<AlarmMonitor> subscriberAlarmMonitor; + vector<int64_t> activeConfigsBroadcast; + + long timeBase1 = 1; + int broadcastCount = 0; + StatsLogProcessor processor(m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, + bucketStartTimeNs, [](const ConfigKey& key) { return true; }, + [&uid, &broadcastCount, &activeConfigsBroadcast](const int& broadcastUid, + const vector<int64_t>& activeConfigs) { + broadcastCount++; + EXPECT_EQ(broadcastUid, uid); + activeConfigsBroadcast.clear(); + activeConfigsBroadcast.insert(activeConfigsBroadcast.end(), + activeConfigs.begin(), activeConfigs.end()); + return true; + }); + + processor.OnConfigUpdated(bucketStartTimeNs, cfgKey, config); + + EXPECT_EQ(processor.mMetricsManagers.size(), 1u); + sp<MetricsManager> metricsManager = processor.mMetricsManagers.begin()->second; + EXPECT_TRUE(metricsManager->isConfigValid()); + EXPECT_EQ(metricsManager->mAllMetricProducers.size(), 2); + sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0]; + auto& eventActivationMap = metricProducer->mEventActivationMap; + auto& eventDeactivationMap = metricProducer->mEventDeactivationMap; + sp<MetricProducer> metricProducer2 = metricsManager->mAllMetricProducers[1]; + auto& eventActivationMap2 = metricProducer2->mEventActivationMap; + auto& eventDeactivationMap2 = metricProducer2->mEventDeactivationMap; + + EXPECT_FALSE(metricsManager->isActive()); + EXPECT_FALSE(metricProducer->mIsActive); + EXPECT_FALSE(metricProducer2->mIsActive); + // Two activations: one is triggered by battery saver mode (tracker index 0), the other is + // triggered by screen on event (tracker index 2). + EXPECT_EQ(eventActivationMap.size(), 2u); + EXPECT_TRUE(eventActivationMap.find(0) != eventActivationMap.end()); + EXPECT_TRUE(eventActivationMap.find(2) != eventActivationMap.end()); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[0]->activation_ns, 0); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[2]->activation_ns, 0); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap.size(), 2u); + EXPECT_TRUE(eventDeactivationMap.find(3) != eventDeactivationMap.end()); + EXPECT_TRUE(eventDeactivationMap.find(4) != eventDeactivationMap.end()); + EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]); + + EXPECT_EQ(eventActivationMap2.size(), 2u); + EXPECT_TRUE(eventActivationMap2.find(0) != eventActivationMap2.end()); + EXPECT_TRUE(eventActivationMap2.find(2) != eventActivationMap2.end()); + EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap2[0]->activation_ns, 0); + EXPECT_EQ(eventActivationMap2[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap2[2]->activation_ns, 0); + EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap2.size(), 2u); + EXPECT_TRUE(eventDeactivationMap2.find(3) != eventDeactivationMap2.end()); + EXPECT_TRUE(eventDeactivationMap2.find(4) != eventDeactivationMap2.end()); + EXPECT_EQ(eventDeactivationMap2[3], eventActivationMap2[0]); + EXPECT_EQ(eventDeactivationMap2[4], eventActivationMap2[2]); + + std::unique_ptr<LogEvent> event; + + event = CreateAppCrashEvent(111, bucketStartTimeNs + 5); + processor.OnLogEvent(event.get()); + event = CreateMoveToForegroundEvent(1111, bucketStartTimeNs + 5); + processor.OnLogEvent(event.get()); + EXPECT_FALSE(metricsManager->isActive()); + EXPECT_FALSE(metricProducer->mIsActive); + EXPECT_FALSE(metricProducer2->mIsActive); + EXPECT_EQ(broadcastCount, 0); + + // Activated by battery save mode. + event = CreateBatterySaverOnEvent(bucketStartTimeNs + 10); + processor.OnLogEvent(event.get()); + EXPECT_TRUE(metricsManager->isActive()); + EXPECT_EQ(broadcastCount, 1); + EXPECT_EQ(activeConfigsBroadcast.size(), 1); + EXPECT_EQ(activeConfigsBroadcast[0], cfgId); + EXPECT_TRUE(metricProducer->mIsActive); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + 10); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[2]->activation_ns, 0); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]); + EXPECT_TRUE(metricProducer2->mIsActive); + EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap2[0]->activation_ns, bucketStartTimeNs + 10); + EXPECT_EQ(eventActivationMap2[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap2[2]->activation_ns, 0); + EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap2[3], eventActivationMap2[0]); + EXPECT_EQ(eventDeactivationMap2[4], eventActivationMap2[2]); + + // First processed event. + event = CreateAppCrashEvent(222, bucketStartTimeNs + 15); + processor.OnLogEvent(event.get()); + event = CreateMoveToForegroundEvent(2222, bucketStartTimeNs + 15); + processor.OnLogEvent(event.get()); + + // Activated by screen on event. + event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, + bucketStartTimeNs + 20); + processor.OnLogEvent(event.get()); + EXPECT_TRUE(metricsManager->isActive()); + EXPECT_TRUE(metricProducer->mIsActive); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + 10); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + 20); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]); + EXPECT_TRUE(metricProducer2->mIsActive); + EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap2[0]->activation_ns, bucketStartTimeNs + 10); + EXPECT_EQ(eventActivationMap2[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap2[2]->activation_ns, bucketStartTimeNs + 20); + EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap2[3], eventActivationMap2[0]); + EXPECT_EQ(eventDeactivationMap2[4], eventActivationMap2[2]); + + // 2nd processed event. + // The activation by screen_on event expires, but the one by battery save mode is still active. + event = CreateAppCrashEvent(333, bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25); + processor.OnLogEvent(event.get()); + event = CreateMoveToForegroundEvent(3333, bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25); + processor.OnLogEvent(event.get()); + EXPECT_TRUE(metricsManager->isActive()); + EXPECT_TRUE(metricProducer->mIsActive); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + 10); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + 20); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]); + EXPECT_TRUE(metricProducer2->mIsActive); + EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap2[0]->activation_ns, bucketStartTimeNs + 10); + EXPECT_EQ(eventActivationMap2[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap2[2]->activation_ns, bucketStartTimeNs + 20); + EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap2[3], eventActivationMap2[0]); + EXPECT_EQ(eventDeactivationMap2[4], eventActivationMap2[2]); + // No new broadcast since the config should still be active. + EXPECT_EQ(broadcastCount, 1); + + // 3rd processed event. + event = CreateAppCrashEvent(444, bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25); + processor.OnLogEvent(event.get()); + event = CreateMoveToForegroundEvent(4444, bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25); + processor.OnLogEvent(event.get()); + + // All activations expired. + event = CreateAppCrashEvent(555, bucketStartTimeNs + NS_PER_SEC * 60 * 8); + processor.OnLogEvent(event.get()); + event = CreateMoveToForegroundEvent(5555, bucketStartTimeNs + NS_PER_SEC * 60 * 8); + processor.OnLogEvent(event.get()); + EXPECT_FALSE(metricsManager->isActive()); + // New broadcast since the config is no longer active. + EXPECT_EQ(broadcastCount, 2); + EXPECT_EQ(activeConfigsBroadcast.size(), 0); + EXPECT_FALSE(metricProducer->mIsActive); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + 10); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + 20); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]); + EXPECT_FALSE(metricProducer2->mIsActive); + EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap2[0]->activation_ns, bucketStartTimeNs + 10); + EXPECT_EQ(eventActivationMap2[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap2[2]->activation_ns, bucketStartTimeNs + 20); + EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap2[3], eventActivationMap2[0]); + EXPECT_EQ(eventDeactivationMap2[4], eventActivationMap2[2]); + + // Re-activate metric via screen on. + event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON, + bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); + processor.OnLogEvent(event.get()); + EXPECT_TRUE(metricsManager->isActive()); + EXPECT_EQ(broadcastCount, 3); + EXPECT_EQ(activeConfigsBroadcast.size(), 1); + EXPECT_EQ(activeConfigsBroadcast[0], cfgId); + EXPECT_TRUE(metricProducer->mIsActive); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + 10); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]); + EXPECT_TRUE(metricProducer2->mIsActive); + EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap2[0]->activation_ns, bucketStartTimeNs + 10); + EXPECT_EQ(eventActivationMap2[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap2[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); + EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap2[3], eventActivationMap2[0]); + EXPECT_EQ(eventDeactivationMap2[4], eventActivationMap2[2]); + + // 4th processed event. + event = CreateAppCrashEvent(666, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1); + processor.OnLogEvent(event.get()); + event = CreateMoveToForegroundEvent(6666, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1); + processor.OnLogEvent(event.get()); + + // Re-enable battery saver mode activation. + event = CreateBatterySaverOnEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); + processor.OnLogEvent(event.get()); + EXPECT_TRUE(metricsManager->isActive()); + EXPECT_EQ(broadcastCount, 3); + EXPECT_EQ(activeConfigsBroadcast.size(), 1); + EXPECT_EQ(activeConfigsBroadcast[0], cfgId); + EXPECT_TRUE(metricProducer->mIsActive); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]); + EXPECT_TRUE(metricProducer2->mIsActive); + EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap2[0]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); + EXPECT_EQ(eventActivationMap2[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap2[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); + EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap2[3], eventActivationMap2[0]); + EXPECT_EQ(eventDeactivationMap2[4], eventActivationMap2[2]); + + // 5th processed event. + event = CreateAppCrashEvent(777, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 40); + processor.OnLogEvent(event.get()); + event = CreateMoveToForegroundEvent(7777, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 40); + processor.OnLogEvent(event.get()); + + // Cancel battery saver mode and screen on activation. + event = CreateScreenBrightnessChangedEvent(64, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 60); + processor.OnLogEvent(event.get()); + EXPECT_FALSE(metricsManager->isActive()); + // New broadcast since the config is no longer active. + EXPECT_EQ(broadcastCount, 4); + EXPECT_EQ(activeConfigsBroadcast.size(), 0); + EXPECT_FALSE(metricProducer->mIsActive); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]); + EXPECT_FALSE(metricProducer2->mIsActive); + EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap2[0]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); + EXPECT_EQ(eventActivationMap2[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap2[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); + EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap2[3], eventActivationMap2[0]); + EXPECT_EQ(eventDeactivationMap2[4], eventActivationMap2[2]); + + // Screen-on activation expired. + event = CreateAppCrashEvent(888, bucketStartTimeNs + NS_PER_SEC * 60 * 13); + processor.OnLogEvent(event.get()); + event = CreateMoveToForegroundEvent(8888, bucketStartTimeNs + NS_PER_SEC * 60 * 13); + processor.OnLogEvent(event.get()); + EXPECT_FALSE(metricsManager->isActive()); + EXPECT_EQ(broadcastCount, 4); + EXPECT_EQ(activeConfigsBroadcast.size(), 0); + EXPECT_FALSE(metricProducer->mIsActive); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]); + EXPECT_FALSE(metricProducer2->mIsActive); + EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap2[0]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15); + EXPECT_EQ(eventActivationMap2[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap2[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); + EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap2[3], eventActivationMap2[0]); + EXPECT_EQ(eventDeactivationMap2[4], eventActivationMap2[2]); + + event = CreateAppCrashEvent(999, bucketStartTimeNs + NS_PER_SEC * 60 * 14 + 1); + processor.OnLogEvent(event.get()); + event = CreateMoveToForegroundEvent(9999, bucketStartTimeNs + NS_PER_SEC * 60 * 14 + 1); + processor.OnLogEvent(event.get()); + + // Re-enable battery saver mode activation. + event = CreateBatterySaverOnEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15); + processor.OnLogEvent(event.get()); + EXPECT_TRUE(metricsManager->isActive()); + EXPECT_EQ(broadcastCount, 5); + EXPECT_EQ(activeConfigsBroadcast.size(), 1); + EXPECT_EQ(activeConfigsBroadcast[0], cfgId); + EXPECT_TRUE(metricProducer->mIsActive); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]); + EXPECT_TRUE(metricProducer2->mIsActive); + EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kActive); + EXPECT_EQ(eventActivationMap2[0]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15); + EXPECT_EQ(eventActivationMap2[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap2[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); + EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap2[3], eventActivationMap2[0]); + EXPECT_EQ(eventDeactivationMap2[4], eventActivationMap2[2]); + + // Cancel battery saver mode and screen on activation. + event = CreateScreenBrightnessChangedEvent(140, bucketStartTimeNs + NS_PER_SEC * 60 * 16); + processor.OnLogEvent(event.get()); + EXPECT_FALSE(metricsManager->isActive()); + EXPECT_EQ(broadcastCount, 6); + EXPECT_EQ(activeConfigsBroadcast.size(), 0); + EXPECT_FALSE(metricProducer->mIsActive); + EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15); + EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); + EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]); + EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]); + EXPECT_FALSE(metricProducer2->mIsActive); + EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap2[0]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15); + EXPECT_EQ(eventActivationMap2[0]->ttl_ns, 60 * 6 * NS_PER_SEC); + EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kNotActive); + EXPECT_EQ(eventActivationMap2[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10); + EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC); + EXPECT_EQ(eventDeactivationMap2[3], eventActivationMap2[0]); + EXPECT_EQ(eventDeactivationMap2[4], eventActivationMap2[2]); + + ConfigMetricsReportList reports; + vector<uint8_t> buffer; + processor.onDumpReport(cfgKey, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 1, false, true, + ADB_DUMP, FAST, &buffer); + EXPECT_TRUE(buffer.size() > 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStartEndTimestamp(&reports); + EXPECT_EQ(1, reports.reports_size()); + EXPECT_EQ(2, reports.reports(0).metrics_size()); + EXPECT_EQ(5, reports.reports(0).metrics(0).count_metrics().data_size()); + EXPECT_EQ(5, reports.reports(0).metrics(1).count_metrics().data_size()); + + StatsLogReport::CountMetricDataWrapper countMetrics; + + sortMetricDataByDimensionsValue( + reports.reports(0).metrics(0).count_metrics(), &countMetrics); + EXPECT_EQ(5, countMetrics.data_size()); + + auto data = countMetrics.data(0); + EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); + EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(1 /* uid field */, + data.dimensions_in_what().value_tuple().dimensions_value(0).field()); + EXPECT_EQ(222, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); + EXPECT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); + + data = countMetrics.data(1); + EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); + EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(1 /* uid field */, + data.dimensions_in_what().value_tuple().dimensions_value(0).field()); + EXPECT_EQ(333, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); + EXPECT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); + + data = countMetrics.data(2); + EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); + EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(1 /* uid field */, + data.dimensions_in_what().value_tuple().dimensions_value(0).field()); + EXPECT_EQ(444, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); + EXPECT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + // Partial bucket as metric is deactivated. + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + NS_PER_SEC * 60 * 8, + data.bucket_info(0).end_bucket_elapsed_nanos()); + + data = countMetrics.data(3); + EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); + EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(1 /* uid field */, + data.dimensions_in_what().value_tuple().dimensions_value(0).field()); + EXPECT_EQ(666, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); + EXPECT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, + data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + NS_PER_SEC * 60 * 11, + data.bucket_info(0).end_bucket_elapsed_nanos()); + + data = countMetrics.data(4); + EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field()); + EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(1 /* uid field */, + data.dimensions_in_what().value_tuple().dimensions_value(0).field()); + EXPECT_EQ(777, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); + EXPECT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, + data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + NS_PER_SEC * 60 * 11, + data.bucket_info(0).end_bucket_elapsed_nanos()); + + + countMetrics.clear_data(); + sortMetricDataByDimensionsValue( + reports.reports(0).metrics(1).count_metrics(), &countMetrics); + EXPECT_EQ(5, countMetrics.data_size()); + + data = countMetrics.data(0); + EXPECT_EQ(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, data.dimensions_in_what().field()); + EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(1 /* uid field */, + data.dimensions_in_what().value_tuple().dimensions_value(0).field()); + EXPECT_EQ(2222, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); + EXPECT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); + + data = countMetrics.data(1); + EXPECT_EQ(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, data.dimensions_in_what().field()); + EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(1 /* uid field */, + data.dimensions_in_what().value_tuple().dimensions_value(0).field()); + EXPECT_EQ(3333, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); + EXPECT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); + + data = countMetrics.data(2); + EXPECT_EQ(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, data.dimensions_in_what().field()); + EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(1 /* uid field */, + data.dimensions_in_what().value_tuple().dimensions_value(0).field()); + EXPECT_EQ(4444, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); + EXPECT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + // Partial bucket as metric is deactivated. + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + NS_PER_SEC * 60 * 8, + data.bucket_info(0).end_bucket_elapsed_nanos()); + + data = countMetrics.data(3); + EXPECT_EQ(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, data.dimensions_in_what().field()); + EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(1 /* uid field */, + data.dimensions_in_what().value_tuple().dimensions_value(0).field()); + EXPECT_EQ(6666, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); + EXPECT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, + data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + NS_PER_SEC * 60 * 11, + data.bucket_info(0).end_bucket_elapsed_nanos()); + + data = countMetrics.data(4); + EXPECT_EQ(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, data.dimensions_in_what().field()); + EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(1 /* uid field */, + data.dimensions_in_what().value_tuple().dimensions_value(0).field()); + EXPECT_EQ(7777, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); + EXPECT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(1, data.bucket_info(0).count()); + EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, + data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + NS_PER_SEC * 60 * 11, + data.bucket_info(0).end_bucket_elapsed_nanos()); +} #else GTEST_LOG_(INFO) << "This test does nothing.\n"; diff --git a/core/java/android/app/ActivityTaskManager.java b/core/java/android/app/ActivityTaskManager.java index 7eab5dba5f82..4ef554dc64cc 100644 --- a/core/java/android/app/ActivityTaskManager.java +++ b/core/java/android/app/ActivityTaskManager.java @@ -19,6 +19,7 @@ package android.app; import android.annotation.RequiresPermission; import android.annotation.SystemService; import android.annotation.TestApi; +import android.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.pm.PackageManager; import android.content.res.Resources; @@ -146,6 +147,7 @@ public class ActivityTaskManager { return IActivityTaskManagerSingleton.get(); } + @UnsupportedAppUsage(trackingBug = 129726065) private static final Singleton<IActivityTaskManager> IActivityTaskManagerSingleton = new Singleton<IActivityTaskManager>() { @Override diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index b2b1e775e94a..38006dc5b943 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -5920,6 +5920,10 @@ public final class ActivityThread extends ClientTransactionHandler { UserHandle.myUserId()); VMRuntime.setProcessPackageName(data.appInfo.packageName); + // Pass data directory path to ART. This is used for caching information and + // should be set before any application code is loaded. + VMRuntime.setProcessDataDirectory(data.appInfo.dataDir); + if (mProfiler.profileFd != null) { mProfiler.startProfiling(); } diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 8a522656a13a..83c5e2031bb3 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -2147,8 +2147,8 @@ public class DevicePolicyManager { public static final int UPDATE_ERROR_INCORRECT_OS_VERSION = 2; /** - * Represents the update file being wrong, i.e. payloads are mismatched, wrong compressions - * method. + * Represents the update file being wrong; e.g. payloads are mismatched, or the wrong + * compression method is used. */ public static final int UPDATE_ERROR_UPDATE_FILE_INVALID = 3; diff --git a/core/java/android/content/LoggingContentInterface.java b/core/java/android/content/LoggingContentInterface.java index 6e12a57a14df..83c0c9112387 100644 --- a/core/java/android/content/LoggingContentInterface.java +++ b/core/java/android/content/LoggingContentInterface.java @@ -48,37 +48,43 @@ public class LoggingContentInterface implements ContentInterface { this.delegate = delegate; } - private void log(String method, Object res, Object... args) { - // First, force-unparcel any bundles so we can log them - for (Object arg : args) { - if (arg instanceof Bundle) { - ((Bundle) arg).size(); + private class Logger implements AutoCloseable { + private final StringBuilder sb = new StringBuilder(); + + public Logger(String method, Object... args) { + // First, force-unparcel any bundles so we can log them + for (Object arg : args) { + if (arg instanceof Bundle) { + ((Bundle) arg).size(); + } } + + sb.append("callingUid=").append(Binder.getCallingUid()).append(' '); + sb.append(method); + sb.append('(').append(deepToString(args)).append(')'); } - final StringBuilder sb = new StringBuilder(); - sb.append("callingUid=").append(Binder.getCallingUid()).append(' '); - sb.append(method); - sb.append('(').append(deepToString(args)).append(')'); - if (res instanceof Cursor) { - sb.append('\n'); - DatabaseUtils.dumpCursor((Cursor) res, sb); - } else { - sb.append(" = ").append(deepToString(res)); + private String deepToString(Object value) { + if (value != null && value.getClass().isArray()) { + return Arrays.deepToString((Object[]) value); + } else { + return String.valueOf(value); + } } - if (res instanceof Exception) { - Log.e(tag, sb.toString()); - } else { - Log.v(tag, sb.toString()); + public <T> T setResult(T res) { + if (res instanceof Cursor) { + sb.append('\n'); + DatabaseUtils.dumpCursor((Cursor) res, sb); + } else { + sb.append(" = ").append(deepToString(res)); + } + return res; } - } - private String deepToString(Object value) { - if (value != null && value.getClass().isArray()) { - return Arrays.deepToString((Object[]) value); - } else { - return String.valueOf(value); + @Override + public void close() { + Log.v(tag, sb.toString()); } } @@ -86,153 +92,153 @@ public class LoggingContentInterface implements ContentInterface { public @Nullable Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable Bundle queryArgs, @Nullable CancellationSignal cancellationSignal) throws RemoteException { - try { - final Cursor res = delegate.query(uri, projection, queryArgs, cancellationSignal); - log("query", res, uri, projection, queryArgs, cancellationSignal); - return res; - } catch (Exception res) { - log("query", res, uri, projection, queryArgs, cancellationSignal); - throw res; + try (Logger l = new Logger("query", uri, projection, queryArgs, cancellationSignal)) { + try { + return l.setResult(delegate.query(uri, projection, queryArgs, cancellationSignal)); + } catch (Exception res) { + l.setResult(res); + throw res; + } } } @Override public @Nullable String getType(@NonNull Uri uri) throws RemoteException { - try { - final String res = delegate.getType(uri); - log("getType", res, uri); - return res; - } catch (Exception res) { - log("getType", res, uri); - throw res; + try (Logger l = new Logger("getType", uri)) { + try { + return l.setResult(delegate.getType(uri)); + } catch (Exception res) { + l.setResult(res); + throw res; + } } } @Override public @Nullable String[] getStreamTypes(@NonNull Uri uri, @NonNull String mimeTypeFilter) throws RemoteException { - try { - final String[] res = delegate.getStreamTypes(uri, mimeTypeFilter); - log("getStreamTypes", res, uri, mimeTypeFilter); - return res; - } catch (Exception res) { - log("getStreamTypes", res, uri, mimeTypeFilter); - throw res; + try (Logger l = new Logger("getStreamTypes", uri, mimeTypeFilter)) { + try { + return l.setResult(delegate.getStreamTypes(uri, mimeTypeFilter)); + } catch (Exception res) { + l.setResult(res); + throw res; + } } } @Override public @Nullable Uri canonicalize(@NonNull Uri uri) throws RemoteException { - try { - final Uri res = delegate.canonicalize(uri); - log("canonicalize", res, uri); - return res; - } catch (Exception res) { - log("canonicalize", res, uri); - throw res; + try (Logger l = new Logger("canonicalize", uri)) { + try { + return l.setResult(delegate.canonicalize(uri)); + } catch (Exception res) { + l.setResult(res); + throw res; + } } } @Override public @Nullable Uri uncanonicalize(@NonNull Uri uri) throws RemoteException { - try { - final Uri res = delegate.uncanonicalize(uri); - log("uncanonicalize", res, uri); - return res; - } catch (Exception res) { - log("uncanonicalize", res, uri); - throw res; + try (Logger l = new Logger("uncanonicalize", uri)) { + try { + return l.setResult(delegate.uncanonicalize(uri)); + } catch (Exception res) { + l.setResult(res); + throw res; + } } } @Override public boolean refresh(@NonNull Uri uri, @Nullable Bundle args, @Nullable CancellationSignal cancellationSignal) throws RemoteException { - try { - final boolean res = delegate.refresh(uri, args, cancellationSignal); - log("refresh", res, uri, args, cancellationSignal); - return res; - } catch (Exception res) { - log("refresh", res, uri, args, cancellationSignal); - throw res; + try (Logger l = new Logger("refresh", uri, args, cancellationSignal)) { + try { + return l.setResult(delegate.refresh(uri, args, cancellationSignal)); + } catch (Exception res) { + l.setResult(res); + throw res; + } } } @Override public @Nullable Uri insert(@NonNull Uri uri, @Nullable ContentValues initialValues) throws RemoteException { - try { - final Uri res = delegate.insert(uri, initialValues); - log("insert", res, uri, initialValues); - return res; - } catch (Exception res) { - log("insert", res, uri, initialValues); - throw res; + try (Logger l = new Logger("insert", uri, initialValues)) { + try { + return l.setResult(delegate.insert(uri, initialValues)); + } catch (Exception res) { + l.setResult(res); + throw res; + } } } @Override public int bulkInsert(@NonNull Uri uri, @NonNull ContentValues[] initialValues) throws RemoteException { - try { - final int res = delegate.bulkInsert(uri, initialValues); - log("bulkInsert", res, uri, initialValues); - return res; - } catch (Exception res) { - log("bulkInsert", res, uri, initialValues); - throw res; + try (Logger l = new Logger("bulkInsert", uri, initialValues)) { + try { + return l.setResult(delegate.bulkInsert(uri, initialValues)); + } catch (Exception res) { + l.setResult(res); + throw res; + } } } @Override public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) throws RemoteException { - try { - final int res = delegate.delete(uri, selection, selectionArgs); - log("delete", res, uri, selection, selectionArgs); - return res; - } catch (Exception res) { - log("delete", res, uri, selection, selectionArgs); - throw res; + try (Logger l = new Logger("delete", uri, selection, selectionArgs)) { + try { + return l.setResult(delegate.delete(uri, selection, selectionArgs)); + } catch (Exception res) { + l.setResult(res); + throw res; + } } } @Override public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) throws RemoteException { - try { - final int res = delegate.update(uri, values, selection, selectionArgs); - log("update", res, uri, values, selection, selectionArgs); - return res; - } catch (Exception res) { - log("update", res, uri, values, selection, selectionArgs); - throw res; + try (Logger l = new Logger("update", uri, values, selection, selectionArgs)) { + try { + return l.setResult(delegate.update(uri, values, selection, selectionArgs)); + } catch (Exception res) { + l.setResult(res); + throw res; + } } } @Override public @Nullable ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode, @Nullable CancellationSignal signal) throws RemoteException, FileNotFoundException { - try { - final ParcelFileDescriptor res = delegate.openFile(uri, mode, signal); - log("openFile", res, uri, mode, signal); - return res; - } catch (Exception res) { - log("openFile", res, uri, mode, signal); - throw res; + try (Logger l = new Logger("openFile", uri, mode, signal)) { + try { + return l.setResult(delegate.openFile(uri, mode, signal)); + } catch (Exception res) { + l.setResult(res); + throw res; + } } } @Override public @Nullable AssetFileDescriptor openAssetFile(@NonNull Uri uri, @NonNull String mode, @Nullable CancellationSignal signal) throws RemoteException, FileNotFoundException { - try { - final AssetFileDescriptor res = delegate.openAssetFile(uri, mode, signal); - log("openAssetFile", res, uri, mode, signal); - return res; - } catch (Exception res) { - log("openAssetFile", res, uri, mode, signal); - throw res; + try (Logger l = new Logger("openAssetFile", uri, mode, signal)) { + try { + return l.setResult(delegate.openAssetFile(uri, mode, signal)); + } catch (Exception res) { + l.setResult(res); + throw res; + } } } @@ -240,13 +246,13 @@ public class LoggingContentInterface implements ContentInterface { public @Nullable AssetFileDescriptor openTypedAssetFile(@NonNull Uri uri, @NonNull String mimeTypeFilter, @Nullable Bundle opts, @Nullable CancellationSignal signal) throws RemoteException, FileNotFoundException { - try { - final AssetFileDescriptor res = delegate.openTypedAssetFile(uri, mimeTypeFilter, opts, signal); - log("openTypedAssetFile", res, uri, mimeTypeFilter, opts, signal); - return res; - } catch (Exception res) { - log("openTypedAssetFile", res, uri, mimeTypeFilter, opts, signal); - throw res; + try (Logger l = new Logger("openTypedAssetFile", uri, mimeTypeFilter, opts, signal)) { + try { + return l.setResult(delegate.openTypedAssetFile(uri, mimeTypeFilter, opts, signal)); + } catch (Exception res) { + l.setResult(res); + throw res; + } } } @@ -254,26 +260,26 @@ public class LoggingContentInterface implements ContentInterface { public @NonNull ContentProviderResult[] applyBatch(@NonNull String authority, @NonNull ArrayList<ContentProviderOperation> operations) throws RemoteException, OperationApplicationException { - try { - final ContentProviderResult[] res = delegate.applyBatch(authority, operations); - log("applyBatch", res, authority, operations); - return res; - } catch (Exception res) { - log("applyBatch", res, authority, operations); - throw res; + try (Logger l = new Logger("applyBatch", authority, operations)) { + try { + return l.setResult(delegate.applyBatch(authority, operations)); + } catch (Exception res) { + l.setResult(res); + throw res; + } } } @Override public @Nullable Bundle call(@NonNull String authority, @NonNull String method, @Nullable String arg, @Nullable Bundle extras) throws RemoteException { - try { - final Bundle res = delegate.call(authority, method, arg, extras); - log("call", res, authority, method, arg, extras); - return res; - } catch (Exception res) { - log("call", res, authority, method, arg, extras); - throw res; + try (Logger l = new Logger("call", authority, method, arg, extras)) { + try { + return l.setResult(delegate.call(authority, method, arg, extras)); + } catch (Exception res) { + l.setResult(res); + throw res; + } } } } diff --git a/core/java/android/content/om/OverlayInfo.java b/core/java/android/content/om/OverlayInfo.java index 91424f44d5d0..fc79a425e861 100644 --- a/core/java/android/content/om/OverlayInfo.java +++ b/core/java/android/content/om/OverlayInfo.java @@ -255,7 +255,7 @@ public final class OverlayInfo implements Parcelable { * @hide */ @SystemApi - @Nullable + @NonNull public String getTargetPackageName() { return targetPackageName; } diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index 5328dda03893..deb181f4fd0a 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -687,6 +687,13 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { */ public static final int PRIVATE_FLAG_ALLOW_EXTERNAL_STORAGE_SANDBOX = 1 << 29; + /** + * Value for {@link #privateFlags}: whether this app is pre-installed on the + * ODM partition of the system image. + * @hide + */ + public static final int PRIVATE_FLAG_ODM = 1 << 30; + /** @hide */ @IntDef(flag = true, prefix = { "PRIVATE_FLAG_" }, value = { PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE, @@ -717,6 +724,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { PRIVATE_FLAG_ALLOW_CLEAR_USER_DATA_ON_FAILED_RESTORE, PRIVATE_FLAG_ALLOW_AUDIO_PLAYBACK_CAPTURE, PRIVATE_FLAG_ALLOW_EXTERNAL_STORAGE_SANDBOX, + PRIVATE_FLAG_ODM, }) @Retention(RetentionPolicy.SOURCE) public @interface ApplicationInfoPrivateFlags {} @@ -1093,6 +1101,18 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { public String appComponentFactory; /** + * Resource id of {@link com.android.internal.R.styleable.AndroidManifestProvider_icon} + * @hide + */ + public int iconRes; + + /** + * Resource id of {@link com.android.internal.R.styleable.AndroidManifestProvider_roundIcon} + * @hide + */ + public int roundIconRes; + + /** * The category of this app. Categories are used to cluster multiple apps * together into meaningful groups, such as when summarizing battery, * network, or disk usage. Apps should only define this value when they fit @@ -1571,6 +1591,8 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { classLoaderName = orig.classLoaderName; splitClassLoaderNames = orig.splitClassLoaderNames; appComponentFactory = orig.appComponentFactory; + iconRes = orig.iconRes; + roundIconRes = orig.roundIconRes; compileSdkVersion = orig.compileSdkVersion; compileSdkVersionCodename = orig.compileSdkVersionCodename; mHiddenApiPolicy = orig.mHiddenApiPolicy; @@ -1650,6 +1672,8 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { dest.writeInt(compileSdkVersion); dest.writeString(compileSdkVersionCodename); dest.writeString(appComponentFactory); + dest.writeInt(iconRes); + dest.writeInt(roundIconRes); dest.writeInt(mHiddenApiPolicy); dest.writeInt(hiddenUntilInstalled ? 1 : 0); dest.writeString(zygotePreloadName); @@ -1724,6 +1748,8 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { compileSdkVersion = source.readInt(); compileSdkVersionCodename = source.readString(); appComponentFactory = source.readString(); + iconRes = source.readInt(); + roundIconRes = source.readInt(); mHiddenApiPolicy = source.readInt(); hiddenUntilInstalled = source.readInt() != 0; zygotePreloadName = source.readString(); @@ -1970,6 +1996,11 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { } /** @hide */ + public boolean isOdm() { + return (privateFlags & ApplicationInfo.PRIVATE_FLAG_ODM) != 0; + } + + /** @hide */ public boolean isPartiallyDirectBootAware() { return (privateFlags & ApplicationInfo.PRIVATE_FLAG_PARTIALLY_DIRECT_BOOT_AWARE) != 0; } diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index a96316b3d36e..ec2e8ca26060 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -17,6 +17,7 @@ package android.content.pm; import android.Manifest; +import android.annotation.CurrentTimeMillisLong; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -1817,6 +1818,9 @@ public class PackageInstaller { public boolean isCommitted; /** {@hide} */ + public long updatedMillis; + + /** {@hide} */ @UnsupportedAppUsage public SessionInfo() { } @@ -2237,6 +2241,15 @@ public class PackageInstaller { return isCommitted; } + /** + * The timestamp of the last update that occurred to the session, including changing of + * states in case of staged sessions. + */ + @CurrentTimeMillisLong + public long getUpdatedMillis() { + return updatedMillis; + } + @Override public int describeContents() { return 0; diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 93bc6d7eb583..8981000714dd 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -49,6 +49,8 @@ import android.annotation.StringRes; import android.annotation.TestApi; import android.annotation.UnsupportedAppUsage; import android.app.ActivityTaskManager; +import android.app.ActivityThread; +import android.app.ResourcesManager; import android.content.ComponentName; import android.content.Intent; import android.content.IntentFilter; @@ -69,6 +71,7 @@ import android.os.FileUtils; import android.os.Parcel; import android.os.Parcelable; import android.os.PatternMatcher; +import android.os.RemoteException; import android.os.SystemClock; import android.os.SystemProperties; import android.os.Trace; @@ -92,6 +95,7 @@ import android.util.Slog; import android.util.SparseArray; import android.util.TypedValue; import android.util.apk.ApkSignatureVerifier; +import android.view.Display; import android.view.Gravity; import com.android.internal.R; @@ -320,6 +324,8 @@ public class PackageParser { private int mParseError = PackageManager.INSTALL_SUCCEEDED; private static boolean sCompatibilityModeEnabled = true; + private static boolean sUseRoundIcon = false; + private static final int PARSE_DEFAULT_INSTALL_LOCATION = PackageInfo.INSTALL_LOCATION_UNSPECIFIED; private static final int PARSE_DEFAULT_TARGET_SANDBOX = 1; @@ -3437,6 +3443,11 @@ public class PackageParser { TypedArray sa = res.obtainAttributes(parser, com.android.internal.R.styleable.AndroidManifestApplication); + ai.iconRes = sa.getResourceId( + com.android.internal.R.styleable.AndroidManifestApplication_icon, 0); + ai.roundIconRes = sa.getResourceId( + com.android.internal.R.styleable.AndroidManifestApplication_roundIcon, 0); + if (!parsePackageItemInfo(owner, ai, outError, "<application>", sa, false /*nameRequired*/, com.android.internal.R.styleable.AndroidManifestApplication_name, @@ -4240,9 +4251,7 @@ public class PackageParser { } } - final boolean useRoundIcon = - Resources.getSystem().getBoolean(com.android.internal.R.bool.config_useRoundIcon); - int roundIconVal = useRoundIcon ? sa.getResourceId(roundIconRes, 0) : 0; + int roundIconVal = sUseRoundIcon ? sa.getResourceId(roundIconRes, 0) : 0; if (roundIconVal != 0) { outInfo.icon = roundIconVal; outInfo.nonLocalizedLabel = null; @@ -5750,9 +5759,7 @@ public class PackageParser { outInfo.nonLocalizedLabel = v.coerceToString(); } - final boolean useRoundIcon = - Resources.getSystem().getBoolean(com.android.internal.R.bool.config_useRoundIcon); - int roundIconVal = useRoundIcon ? sa.getResourceId( + int roundIconVal = sUseRoundIcon ? sa.getResourceId( com.android.internal.R.styleable.AndroidManifestIntentFilter_roundIcon, 0) : 0; if (roundIconVal != 0) { outInfo.icon = roundIconVal; @@ -6920,6 +6927,11 @@ public class PackageParser { } /** @hide */ + public boolean isOdm() { + return applicationInfo.isOdm(); + } + + /** @hide */ public boolean isPrivileged() { return applicationInfo.isPrivilegedApp(); } @@ -7739,6 +7751,7 @@ public class PackageParser { } ai.seInfoUser = SELinuxUtil.assignSeinfoUser(state); ai.resourceDirs = state.overlayPaths; + ai.icon = (sUseRoundIcon && ai.roundIconRes != 0) ? ai.roundIconRes : ai.iconRes; } @UnsupportedAppUsage @@ -8344,6 +8357,39 @@ public class PackageParser { sCompatibilityModeEnabled = compatibilityModeEnabled; } + /** + * @hide + */ + public static void readConfigUseRoundIcon(Resources r) { + if (r != null) { + sUseRoundIcon = r.getBoolean(com.android.internal.R.bool.config_useRoundIcon); + return; + } + + ApplicationInfo androidAppInfo; + try { + androidAppInfo = ActivityThread.getPackageManager().getApplicationInfo( + "android", 0 /* flags */, + UserHandle.myUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + Resources systemResources = Resources.getSystem(); + + // Create in-flight as this overlayable resource is only used when config changes + Resources overlayableRes = ResourcesManager.getInstance().getResources(null, + null, + null, + androidAppInfo.resourceDirs, + androidAppInfo.sharedLibraryFiles, + Display.DEFAULT_DISPLAY, + null, + systemResources.getCompatibilityInfo(), + systemResources.getClassLoader()); + + sUseRoundIcon = overlayableRes.getBoolean(com.android.internal.R.bool.config_useRoundIcon); + } + public static class PackageParserException extends Exception { public final int error; diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java index 514015fe0c86..e5ef67b7d4bd 100644 --- a/core/java/android/content/res/AssetManager.java +++ b/core/java/android/content/res/AssetManager.java @@ -23,6 +23,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.StringRes; import android.annotation.StyleRes; +import android.annotation.TestApi; import android.annotation.UnsupportedAppUsage; import android.content.pm.ActivityInfo; import android.content.res.Configuration.NativeConfig; @@ -357,6 +358,22 @@ public final class AssetManager implements AutoCloseable { return sEmptyApkAssets; } + /** @hide */ + @TestApi + public @NonNull String[] getApkPaths() { + synchronized (this) { + if (mOpen) { + String[] paths = new String[mApkAssets.length]; + final int count = mApkAssets.length; + for (int i = 0; i < count; i++) { + paths[i] = mApkAssets[i].getAssetPath(); + } + return paths; + } + } + return new String[0]; + } + /** * Returns a cookie for use with the other APIs of AssetManager. * @return 0 if the path was not found, otherwise a positive integer cookie representing @@ -1356,6 +1373,7 @@ public final class AssetManager implements AutoCloseable { /** * @hide */ + @TestApi @GuardedBy("this") public @Nullable Map<String, String> getOverlayableMap(String packageName) { synchronized (this) { diff --git a/core/java/android/database/CursorWindow.java b/core/java/android/database/CursorWindow.java index 44bd88376c4f..647448caee82 100644 --- a/core/java/android/database/CursorWindow.java +++ b/core/java/android/database/CursorWindow.java @@ -61,7 +61,10 @@ public class CursorWindow extends SQLiteClosable implements Parcelable { private final CloseGuard mCloseGuard = CloseGuard.get(); + // May throw CursorWindowAllocationException private static native long nativeCreate(String name, int cursorWindowSize); + + // May throw CursorWindowAllocationException private static native long nativeCreateFromParcel(Parcel parcel); private static native void nativeDispose(long windowPtr); private static native void nativeWriteToParcel(long windowPtr, Parcel parcel); @@ -135,8 +138,7 @@ public class CursorWindow extends SQLiteClosable implements Parcelable { mName = name != null && name.length() != 0 ? name : "<unnamed>"; mWindowPtr = nativeCreate(mName, (int) windowSizeBytes); if (mWindowPtr == 0) { - throw new CursorWindowAllocationException("Cursor window allocation of " + - windowSizeBytes + " bytes failed. " + printStats()); + throw new IllegalStateException(); // Shouldn't happen. } mCloseGuard.open("close"); recordNewWindow(Binder.getCallingPid(), mWindowPtr); @@ -164,8 +166,7 @@ public class CursorWindow extends SQLiteClosable implements Parcelable { mStartPos = source.readInt(); mWindowPtr = nativeCreateFromParcel(source); if (mWindowPtr == 0) { - throw new CursorWindowAllocationException("Cursor window could not be " - + "created from binder."); + throw new IllegalStateException(); // Shouldn't happen. } mName = nativeGetName(mWindowPtr); mCloseGuard.open("close"); diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java index 3e8c334e8aa1..6035f40e768a 100644 --- a/core/java/android/hardware/face/FaceManager.java +++ b/core/java/android/hardware/face/FaceManager.java @@ -45,6 +45,7 @@ import android.util.Log; import android.util.Slog; import com.android.internal.R; +import com.android.internal.os.SomeArgs; import java.util.List; @@ -63,6 +64,8 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan private static final int MSG_AUTHENTICATION_FAILED = 103; private static final int MSG_ERROR = 104; private static final int MSG_REMOVED = 105; + private static final int MSG_GET_FEATURE_COMPLETED = 106; + private static final int MSG_SET_FEATURE_COMPLETED = 107; private IFaceService mService; private final Context mContext; @@ -70,6 +73,8 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan private AuthenticationCallback mAuthenticationCallback; private EnrollmentCallback mEnrollmentCallback; private RemovalCallback mRemovalCallback; + private SetFeatureCallback mSetFeatureCallback; + private GetFeatureCallback mGetFeatureCallback; private CryptoObject mCryptoObject; private Face mRemovalFace; private Handler mHandler; @@ -112,6 +117,20 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan public void onEnumerated(long deviceId, int faceId, int remaining) { // TODO: Finish. Low priority since it's not used. } + + @Override + public void onFeatureSet(boolean success, int feature) { + mHandler.obtainMessage(MSG_SET_FEATURE_COMPLETED, feature, 0, success).sendToTarget(); + } + + @Override + public void onFeatureGet(boolean success, int feature, boolean value) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = success; + args.argi1 = feature; + args.arg2 = value; + mHandler.obtainMessage(MSG_GET_FEATURE_COMPLETED, args).sendToTarget(); + } }; /** @@ -286,31 +305,31 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan * @hide */ @RequiresPermission(MANAGE_BIOMETRIC) - public boolean setFeature(int feature, boolean enabled, byte[] token) { + public void setFeature(int feature, boolean enabled, byte[] token, + SetFeatureCallback callback) { if (mService != null) { try { - return mService.setFeature(feature, enabled, token); + mSetFeatureCallback = callback; + mService.setFeature(feature, enabled, token, mServiceReceiver); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } - return false; } /** * @hide */ @RequiresPermission(MANAGE_BIOMETRIC) - public boolean getFeature(int feature) { - boolean result = true; + public void getFeature(int feature, GetFeatureCallback callback) { if (mService != null) { try { - result = mService.getFeature(feature); + mGetFeatureCallback = callback; + mService.getFeature(feature, mServiceReceiver); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } - return result; } /** @@ -874,6 +893,20 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan } } + /** + * @hide + */ + public abstract static class SetFeatureCallback { + public abstract void onCompleted(boolean success, int feature); + } + + /** + * @hide + */ + public abstract static class GetFeatureCallback { + public abstract void onCompleted(boolean success, int feature, boolean value); + } + private class OnEnrollCancelListener implements OnCancelListener { @Override public void onCancel() { @@ -926,9 +959,36 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan case MSG_REMOVED: sendRemovedResult((Face) msg.obj, msg.arg1 /* remaining */); break; + case MSG_SET_FEATURE_COMPLETED: + sendSetFeatureCompleted((boolean) msg.obj /* success */, + msg.arg1 /* feature */); + break; + case MSG_GET_FEATURE_COMPLETED: + SomeArgs args = (SomeArgs) msg.obj; + sendGetFeatureCompleted((boolean) args.arg1 /* success */, + args.argi1 /* feature */, + (boolean) args.arg2 /* value */); + args.recycle(); + break; + default: + Log.w(TAG, "Unknown message: " + msg.what); } } - }; + } + + private void sendSetFeatureCompleted(boolean success, int feature) { + if (mSetFeatureCallback == null) { + return; + } + mSetFeatureCallback.onCompleted(success, feature); + } + + private void sendGetFeatureCompleted(boolean success, int feature, boolean value) { + if (mGetFeatureCallback == null) { + return; + } + mGetFeatureCallback.onCompleted(success, feature, value); + } private void sendRemovedResult(Face face, int remaining) { if (mRemovalCallback == null) { diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl index 5043d4ce86df..601be7595581 100644 --- a/core/java/android/hardware/face/IFaceService.aidl +++ b/core/java/android/hardware/face/IFaceService.aidl @@ -51,7 +51,7 @@ interface IFaceService { // Start face enrollment void enroll(IBinder token, in byte [] cryptoToken, IFaceServiceReceiver receiver, - String opPackageName, in int [] disabledFeatures); + String opPackageName, in int [] disabledFeatures); // Cancel enrollment in progress void cancelEnrollment(IBinder token); @@ -98,9 +98,10 @@ interface IFaceService { // Enumerate all faces void enumerate(IBinder token, int userId, IFaceServiceReceiver receiver); - boolean setFeature(int feature, boolean enabled, in byte [] token); + void setFeature(int feature, boolean enabled, in byte [] token, + IFaceServiceReceiver receiver); - boolean getFeature(int feature); + void getFeature(int feature, IFaceServiceReceiver receiver); void userActivity(); } diff --git a/core/java/android/hardware/face/IFaceServiceReceiver.aidl b/core/java/android/hardware/face/IFaceServiceReceiver.aidl index cec9fd8381f9..217690273969 100644 --- a/core/java/android/hardware/face/IFaceServiceReceiver.aidl +++ b/core/java/android/hardware/face/IFaceServiceReceiver.aidl @@ -29,4 +29,6 @@ oneway interface IFaceServiceReceiver { void onError(long deviceId, int error, int vendorCode); void onRemoved(long deviceId, int faceId, int remaining); void onEnumerated(long deviceId, int faceId, int remaining); + void onFeatureSet(boolean success, int feature); + void onFeatureGet(boolean success, int feature, boolean value); } diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl index 2923bbf14c30..0daf30f25d59 100644 --- a/core/java/android/hardware/input/IInputManager.aidl +++ b/core/java/android/hardware/input/IInputManager.aidl @@ -16,6 +16,7 @@ package android.hardware.input; +import android.graphics.Rect; import android.hardware.input.InputDeviceIdentifier; import android.hardware.input.KeyboardLayout; import android.hardware.input.IInputDevicesChangedListener; @@ -24,6 +25,7 @@ import android.hardware.input.TouchCalibration; import android.os.IBinder; import android.view.InputDevice; import android.view.InputEvent; +import android.view.InputMonitor; import android.view.PointerIcon; /** @hide */ @@ -82,4 +84,7 @@ interface IInputManager { void setCustomPointerIcon(in PointerIcon icon); void requestPointerCapture(IBinder windowToken, boolean enabled); + + /** Create an input monitor for gestures. */ + InputMonitor monitorGestureInput(String name, int displayId); } diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java index fec5c34fab28..2a59be28a937 100644 --- a/core/java/android/hardware/input/InputManager.java +++ b/core/java/android/hardware/input/InputManager.java @@ -41,6 +41,7 @@ import android.util.Log; import android.util.SparseArray; import android.view.InputDevice; import android.view.InputEvent; +import android.view.InputMonitor; import android.view.MotionEvent; import android.view.PointerIcon; @@ -933,6 +934,19 @@ public final class InputManager { } } + /** + * Monitor input on the specified display for gestures. + * + * @hide + */ + public InputMonitor monitorGestureInput(String name, int displayId) { + try { + return mIm.monitorGestureInput(name, displayId); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + private void populateInputDevicesLocked() { if (mInputDevicesChangedListener == null) { final InputDevicesChangedListener listener = new InputDevicesChangedListener(); diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java index cf3395abf116..5b5bd7661bc5 100644 --- a/core/java/android/hardware/soundtrigger/SoundTrigger.java +++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java @@ -25,6 +25,7 @@ import static android.system.OsConstants.EPIPE; import android.annotation.Nullable; import android.annotation.SystemApi; import android.annotation.UnsupportedAppUsage; +import android.app.ActivityThread; import android.media.AudioFormat; import android.os.Handler; import android.os.Parcel; @@ -1440,6 +1441,17 @@ public class SoundTrigger { public static final int SERVICE_STATE_DISABLED = 1; /** + * @return returns current package name. + */ + static String getCurrentOpPackageName() { + String packageName = ActivityThread.currentOpPackageName(); + if (packageName == null) { + return ""; + } + return packageName; + } + + /** * Returns a list of descriptors for all hardware modules loaded. * @param modules A ModuleProperties array where the list will be returned. * @return - {@link #STATUS_OK} in case of success @@ -1452,7 +1464,23 @@ public class SoundTrigger { * @hide */ @UnsupportedAppUsage - public static native int listModules(ArrayList <ModuleProperties> modules); + public static int listModules(ArrayList<ModuleProperties> modules) { + return listModules(getCurrentOpPackageName(), modules); + } + + /** + * Returns a list of descriptors for all hardware modules loaded. + * @param opPackageName + * @param modules A ModuleProperties array where the list will be returned. + * @return - {@link #STATUS_OK} in case of success + * - {@link #STATUS_ERROR} in case of unspecified error + * - {@link #STATUS_PERMISSION_DENIED} if the caller does not have system permission + * - {@link #STATUS_NO_INIT} if the native service cannot be reached + * - {@link #STATUS_BAD_VALUE} if modules is null + * - {@link #STATUS_DEAD_OBJECT} if the binder transaction to the native service fails + */ + private static native int listModules(String opPackageName, + ArrayList<ModuleProperties> modules); /** * Get an interface on a hardware module to control sound models and recognition on diff --git a/core/java/android/hardware/soundtrigger/SoundTriggerModule.java b/core/java/android/hardware/soundtrigger/SoundTriggerModule.java index 402c228b93b1..911354862ff4 100644 --- a/core/java/android/hardware/soundtrigger/SoundTriggerModule.java +++ b/core/java/android/hardware/soundtrigger/SoundTriggerModule.java @@ -46,9 +46,10 @@ public class SoundTriggerModule { SoundTriggerModule(int moduleId, SoundTrigger.StatusListener listener, Handler handler) { mId = moduleId; mEventHandlerDelegate = new NativeEventHandlerDelegate(listener, handler); - native_setup(new WeakReference<SoundTriggerModule>(this)); + native_setup(SoundTrigger.getCurrentOpPackageName(), + new WeakReference<SoundTriggerModule>(this)); } - private native void native_setup(Object module_this); + private native void native_setup(String opPackageName, Object moduleThis); @Override protected void finalize() { diff --git a/core/java/android/nfc/cardemulation/CardEmulation.java b/core/java/android/nfc/cardemulation/CardEmulation.java index f23dc2da291a..c13f64e833c5 100644 --- a/core/java/android/nfc/cardemulation/CardEmulation.java +++ b/core/java/android/nfc/cardemulation/CardEmulation.java @@ -34,6 +34,7 @@ import android.util.Log; import java.util.HashMap; import java.util.List; +import java.util.regex.Pattern; /** * This class can be used to query the state of @@ -48,6 +49,7 @@ import java.util.List; * on the device. */ public final class CardEmulation { + private static final Pattern AID_PATTERN = Pattern.compile("[0-9A-Fa-f]{10,32}\\*?\\#?"); static final String TAG = "CardEmulation"; /** @@ -732,7 +734,7 @@ public final class CardEmulation { } // Verify hex characters - if (!aid.matches("[0-9A-Fa-f]{10,32}\\*?\\#?")) { + if (!AID_PATTERN.matcher(aid).matches()) { Log.e(TAG, "AID " + aid + " is not a valid AID."); return false; } diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java index eb91860d62da..66ddf21790fe 100644 --- a/core/java/android/os/Binder.java +++ b/core/java/android/os/Binder.java @@ -444,24 +444,20 @@ public class Binder implements IBinder { * * @param workSource The original UID responsible for the binder call. * @return token to restore original work source. - * @hide **/ @CriticalNative - @SystemApi public static final native long setCallingWorkSourceUid(int workSource); /** * Returns the work source set by the caller. * * Unlike {@link Binder#getCallingUid()}, this result of this method cannot be trusted. The - * caller can set the value to whatever he wants. Only use this value if you trust the calling + * caller can set the value to whatever they want. Only use this value if you trust the calling * uid. * * @return The original UID responsible for the binder transaction. - * @hide */ @CriticalNative - @SystemApi public static final native int getCallingWorkSourceUid(); /** @@ -484,10 +480,8 @@ public class Binder implements IBinder { * </pre> * * @return token to restore original work source. - * @hide **/ @CriticalNative - @SystemApi public static final native long clearCallingWorkSource(); /** @@ -503,11 +497,8 @@ public class Binder implements IBinder { * Binder.restoreCallingWorkSource(token); * } * </pre> - * - * @hide **/ @CriticalNative - @SystemApi public static final native void restoreCallingWorkSource(long token); /** diff --git a/core/java/android/os/SELinux.java b/core/java/android/os/SELinux.java index 8ffafe4bc364..f007dff0ab58 100644 --- a/core/java/android/os/SELinux.java +++ b/core/java/android/os/SELinux.java @@ -39,6 +39,13 @@ public class SELinux { private static final int SELINUX_ANDROID_RESTORECON_DATADATA = 16; /** + * Get context associated with path by file_contexts. + * @param path path to the regular file to get the security context for. + * @return a String representing the security context or null on failure. + */ + public static final native String fileSelabelLookup(String path); + + /** * Determine whether SELinux is disabled or enabled. * @return a boolean indicating whether SELinux is enabled. */ diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java index 44adc1c5cba4..ef28f07e817f 100644 --- a/core/java/android/provider/CallLog.java +++ b/core/java/android/provider/CallLog.java @@ -35,7 +35,6 @@ import android.provider.ContactsContract.CommonDataKinds.Callable; import android.provider.ContactsContract.CommonDataKinds.Phone; import android.provider.ContactsContract.Data; import android.provider.ContactsContract.DataUsageFeedback; -import android.telecom.CallIdentification; import android.telecom.PhoneAccount; import android.telecom.PhoneAccountHandle; import android.telecom.TelecomManager; @@ -605,69 +604,6 @@ public class CallLog { public static final String BLOCK_REASON = "block_reason"; /** - * The package name of the {@link android.telecom.CallScreeningService} which provided - * {@link android.telecom.CallIdentification} for this call. - * <P>Type: TEXT</P> - */ - public static final String CALL_ID_PACKAGE_NAME = "call_id_package_name"; - - /** - * The app name of the {@link android.telecom.CallScreeningService} which provided - * {@link android.telecom.CallIdentification} for this call. - * <P>Type: TEXT</P> - */ - public static final String CALL_ID_APP_NAME = "call_id_app_name"; - - /** - * The {@link CallIdentification#getName() name} of a call, as provided by the - * {@link android.telecom.CallScreeningService}. - * <p> - * The name is provided by the app identified by {@link #CALL_ID_PACKAGE_NAME} and - * {@link #CALL_ID_APP_NAME}. - * <P>Type: TEXT</P> - */ - public static final String CALL_ID_NAME = "call_id_name"; - - /** - * The {@link CallIdentification#getDescription() description} of a call, as provided by the - * {@link android.telecom.CallScreeningService}. - * <p> - * The description is provided by the app identified by {@link #CALL_ID_PACKAGE_NAME} and - * {@link #CALL_ID_APP_NAME}. - * <P>Type: TEXT</P> - */ - public static final String CALL_ID_DESCRIPTION = "call_id_description"; - - /** - * The {@link CallIdentification#getDetails() details} of a call, as provided by the - * {@link android.telecom.CallScreeningService}. - * <p> - * The details field is provided by the app identified by {@link #CALL_ID_PACKAGE_NAME} and - * {@link #CALL_ID_APP_NAME}. - * <P>Type: TEXT</P> - */ - public static final String CALL_ID_DETAILS = "call_id_details"; - - /** - * The {@link CallIdentification#getNuisanceConfidence() nuisance confidence} of a call, as - * provided by the {@link android.telecom.CallScreeningService}. - * <p> - * Valid values are defined in {@link CallIdentification}, and include: - * <ul> - * <li>{@link CallIdentification#CONFIDENCE_NOT_NUISANCE}</li> - * <li>{@link CallIdentification#CONFIDENCE_LIKELY_NOT_NUISANCE}</li> - * <li>{@link CallIdentification#CONFIDENCE_UNKNOWN}</li> - * <li>{@link CallIdentification#CONFIDENCE_LIKELY_NUISANCE}</li> - * <li>{@link CallIdentification#CONFIDENCE_NUISANCE}</li> - * </ul> - * <p> - * The nuisance confidence is provided by the app identified by - * {@link #CALL_ID_PACKAGE_NAME} and {@link #CALL_ID_APP_NAME}. - * <P>Type: INTEGER</P> - */ - public static final String CALL_ID_NUISANCE_CONFIDENCE = "call_id_nuisance_confidence"; - - /** * Adds a call to the call log. * * @param ci the CallerInfo object to get the target contact from. Can be null @@ -696,8 +632,7 @@ public class CallLog { presentation, callType, features, accountHandle, start, duration, dataUsage, false /* addForAllUsers */, null /* userToBeInsertedTo */, false /* isRead */, Calls.BLOCK_REASON_NOT_BLOCKED /* callBlockReason */, - null /* callScreeningAppName */, null /* callScreeningComponentName */, - null /* callIdentification */); + null /* callScreeningAppName */, null /* callScreeningComponentName */); } @@ -737,8 +672,7 @@ public class CallLog { features, accountHandle, start, duration, dataUsage, addForAllUsers, userToBeInsertedTo, false /* isRead */ , Calls.BLOCK_REASON_NOT_BLOCKED /* callBlockReason */, null /* callScreeningAppName */, - null /* callScreeningComponentName */, - null /* callIdentification */); + null /* callScreeningComponentName */); } /** @@ -784,7 +718,7 @@ public class CallLog { int features, PhoneAccountHandle accountHandle, long start, int duration, Long dataUsage, boolean addForAllUsers, UserHandle userToBeInsertedTo, boolean isRead, int callBlockReason, CharSequence callScreeningAppName, - String callScreeningComponentName, CallIdentification callIdentification) { + String callScreeningComponentName) { if (VERBOSE_LOG) { Log.v(LOG_TAG, String.format("Add call: number=%s, user=%s, for all=%s", number, userToBeInsertedTo, addForAllUsers)); @@ -839,26 +773,6 @@ public class CallLog { values.put(CALL_SCREENING_APP_NAME, charSequenceToString(callScreeningAppName)); values.put(CALL_SCREENING_COMPONENT_NAME, callScreeningComponentName); - if (callIdentification != null) { - values.put(CALL_ID_PACKAGE_NAME, callIdentification.getCallScreeningPackageName()); - values.put(CALL_ID_APP_NAME, - charSequenceToString(callIdentification.getCallScreeningAppName())); - values.put(CALL_ID_NAME, - charSequenceToString(callIdentification.getName())); - values.put(CALL_ID_DESCRIPTION, - charSequenceToString(callIdentification.getDescription())); - values.put(CALL_ID_DETAILS, - charSequenceToString(callIdentification.getDetails())); - values.put(CALL_ID_NUISANCE_CONFIDENCE, callIdentification.getNuisanceConfidence()); - } else { - values.putNull(CALL_ID_PACKAGE_NAME); - values.putNull(CALL_ID_APP_NAME); - values.putNull(CALL_ID_NAME); - values.putNull(CALL_ID_DESCRIPTION); - values.putNull(CALL_ID_DETAILS); - values.putNull(CALL_ID_NUISANCE_CONFIDENCE); - } - if ((ci != null) && (ci.contactIdOrZero > 0)) { // Update usage information for the number associated with the contact ID. // We need to use both the number and the ID for obtaining a data ID since other diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 227c9d426915..85feac878c67 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -8654,9 +8654,10 @@ public final class Settings { "location_permissions_upgrade_to_q_mode"; /** - * Comma separated list of enabled overlay packages for all android.theme.customization.* - * categories. If there is no corresponding package included for a category, then all - * overlay packages in that category must be disabled. + * Map of android.theme.customization.* categories to the enabled overlay package for that + * category, formatted as a serialized {@link org.json.JSONObject}. If there is no + * corresponding package included for a category, then all overlay packages in that + * category must be disabled. * @hide */ @SystemApi @@ -8664,7 +8665,7 @@ public final class Settings { "theme_customization_overlay_packages"; private static final Validator THEME_CUSTOMIZATION_OVERLAY_PACKAGES_VALIDATOR = - new SettingsValidators.PackageNameListValidator(","); + SettingsValidators.JSON_OBJECT_VALIDATOR; /** * Controls whether aware is enabled. diff --git a/core/java/android/provider/SettingsValidators.java b/core/java/android/provider/SettingsValidators.java index 25e77867b757..405121378d1f 100644 --- a/core/java/android/provider/SettingsValidators.java +++ b/core/java/android/provider/SettingsValidators.java @@ -19,9 +19,13 @@ package android.provider; import android.annotation.Nullable; import android.content.ComponentName; import android.net.Uri; +import android.text.TextUtils; import com.android.internal.util.ArrayUtils; +import org.json.JSONException; +import org.json.JSONObject; + import java.util.Locale; /** @@ -162,6 +166,19 @@ public class SettingsValidators { } }; + /** {@link Validator} that checks whether a value is a valid {@link JSONObject}. */ + public static final Validator JSON_OBJECT_VALIDATOR = (value) -> { + if (TextUtils.isEmpty(value)) { + return false; + } + try { + new JSONObject(value); + return true; + } catch (JSONException e) { + return false; + } + }; + public interface Validator { /** * Returns whether the input value is valid. Subclasses should handle the case where the diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index 5d33b49e181b..336c2e51c4a3 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -38,7 +38,8 @@ public class FeatureFlagUtils { public static final String HEARING_AID_SETTINGS = "settings_bluetooth_hearing_aid"; public static final String SAFETY_HUB = "settings_safety_hub"; public static final String SCREENRECORD_LONG_PRESS = "settings_screenrecord_long_press"; - public static final String GLOBAL_ACTIONS_GRID_ENABLED = "settings_global_actions_grid_enabled"; + public static final String FORCE_GLOBAL_ACTIONS_GRID_ENABLED = + "settings_global_actions_force_grid_enabled"; public static final String GLOBAL_ACTIONS_PANEL_ENABLED = "settings_global_actions_panel_enabled"; public static final String DYNAMIC_SYSTEM = "settings_dynamic_system"; @@ -58,7 +59,7 @@ public class FeatureFlagUtils { DEFAULT_FLAGS.put(HEARING_AID_SETTINGS, "false"); DEFAULT_FLAGS.put(SAFETY_HUB, "true"); DEFAULT_FLAGS.put(SCREENRECORD_LONG_PRESS, "false"); - DEFAULT_FLAGS.put(GLOBAL_ACTIONS_GRID_ENABLED, "true"); + DEFAULT_FLAGS.put(FORCE_GLOBAL_ACTIONS_GRID_ENABLED, "false"); DEFAULT_FLAGS.put(GLOBAL_ACTIONS_PANEL_ENABLED, "true"); DEFAULT_FLAGS.put("settings_wifi_details_saved_screen", "true"); DEFAULT_FLAGS.put("settings_wifi_details_datausage_header", "false"); diff --git a/telecomm/java/android/telecom/CallIdentification.aidl b/core/java/android/view/IInputMonitorHost.aidl index 532535c44714..bde737d9a65b 100644 --- a/telecomm/java/android/telecom/CallIdentification.aidl +++ b/core/java/android/view/IInputMonitorHost.aidl @@ -1,5 +1,5 @@ -/* - * Copyright 2018, The Android Open Source Project +/** + * Copyright (c) 2019, 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. @@ -14,9 +14,12 @@ * limitations under the License. */ -package android.telecom; +package android.view; /** - * {@hide} + * @hide */ -parcelable CallIdentification; +oneway interface IInputMonitorHost { + void pilferPointers(); + void dispose(); +} diff --git a/core/java/android/view/InputChannel.java b/core/java/android/view/InputChannel.java index af2b992ccba2..ecb727c79016 100644 --- a/core/java/android/view/InputChannel.java +++ b/core/java/android/view/InputChannel.java @@ -17,8 +17,8 @@ package android.view; import android.annotation.UnsupportedAppUsage; -import android.os.Parcel; import android.os.IBinder; +import android.os.Parcel; import android.os.Parcelable; import android.util.Slog; @@ -31,9 +31,9 @@ import android.util.Slog; */ public final class InputChannel implements Parcelable { private static final String TAG = "InputChannel"; - + private static final boolean DEBUG = false; - + @UnsupportedAppUsage public static final @android.annotation.NonNull Parcelable.Creator<InputChannel> CREATOR = new Parcelable.Creator<InputChannel>() { @@ -42,12 +42,12 @@ public final class InputChannel implements Parcelable { result.readFromParcel(source); return result; } - + public InputChannel[] newArray(int size) { return new InputChannel[size]; } }; - + @SuppressWarnings("unused") @UnsupportedAppUsage private long mPtr; // used by native code @@ -81,7 +81,7 @@ public final class InputChannel implements Parcelable { super.finalize(); } } - + /** * Creates a new input channel pair. One channel should be provided to the input * dispatcher and the other to the application's input queue. @@ -100,7 +100,7 @@ public final class InputChannel implements Parcelable { } return nativeOpenInputChannelPair(name); } - + /** * Gets the name of the input channel. * @return The input channel name. @@ -118,7 +118,7 @@ public final class InputChannel implements Parcelable { public void dispose() { nativeDispose(false); } - + /** * Transfers ownership of the internal state of the input channel to another * instance and invalidates this instance. This is used to pass an input channel @@ -129,7 +129,7 @@ public final class InputChannel implements Parcelable { if (outParameter == null) { throw new IllegalArgumentException("outParameter must not be null"); } - + nativeTransferTo(outParameter); } @@ -151,7 +151,7 @@ public final class InputChannel implements Parcelable { if (in == null) { throw new IllegalArgumentException("in must not be null"); } - + nativeReadFromParcel(in); } @@ -160,7 +160,7 @@ public final class InputChannel implements Parcelable { if (out == null) { throw new IllegalArgumentException("out must not be null"); } - + nativeWriteToParcel(out); if ((flags & PARCELABLE_WRITE_RETURN_VALUE) != 0) { diff --git a/core/java/android/view/InputMonitor.aidl b/core/java/android/view/InputMonitor.aidl new file mode 100644 index 000000000000..bdd14fef392a --- /dev/null +++ b/core/java/android/view/InputMonitor.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2019 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.view; + +parcelable InputMonitor; diff --git a/core/java/android/view/InputMonitor.java b/core/java/android/view/InputMonitor.java new file mode 100644 index 000000000000..693f2873522c --- /dev/null +++ b/core/java/android/view/InputMonitor.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2019 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.view; + +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.RemoteException; + +/** + * @hide + */ +public final class InputMonitor implements Parcelable { + private static final String TAG = "InputMonitor"; + + private static final boolean DEBUG = false; + + public static final Parcelable.Creator<InputMonitor> CREATOR = + new Parcelable.Creator<InputMonitor>() { + + public InputMonitor createFromParcel(Parcel source) { + return new InputMonitor(source); + } + + public InputMonitor[] newArray(int size) { + return new InputMonitor[size]; + } + }; + + @NonNull + private final String mName; + @NonNull + private final InputChannel mChannel; + @NonNull + private final IInputMonitorHost mHost; + + public InputMonitor(@NonNull String name, @NonNull InputChannel channel, + @NonNull IInputMonitorHost host) { + mName = name; + mChannel = channel; + mHost = host; + } + + public InputMonitor(Parcel in) { + mName = in.readString(); + mChannel = in.readParcelable(null); + mHost = IInputMonitorHost.Stub.asInterface(in.readStrongBinder()); + } + + /** + * Get the {@link InputChannel} corresponding to this InputMonitor + */ + public InputChannel getInputChannel() { + return mChannel; + } + + /** + * Get the name of this channel. + */ + public String getName() { + return mName; + } + + + /** + * Takes all of the current pointer events streams that are currently being sent to this + * monitor and generates appropriate cancellations for the windows that would normally get + * them. + * + * This method should be used with caution as unexpected pilfering can break fundamental user + * interactions. + */ + public void pilferPointers() { + try { + mHost.pilferPointers(); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + /** + * Disposes the input monitor. + * + * Explicitly release all of the resources this monitor is holding on to (e.g. the + * InputChannel). Once this method is called, this monitor and any resources it's provided may + * no longer be used. + */ + public void dispose() { + mChannel.dispose(); + try { + mHost.dispose(); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeString(mName); + out.writeParcelable(mChannel, flags); + out.writeStrongBinder(mHost.asBinder()); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public String toString() { + return "InputMonitor{mName=" + mName + ", mChannel=" + mChannel + ", mHost=" + mHost + "}"; + } +} diff --git a/core/java/android/view/LayoutInflater.java b/core/java/android/view/LayoutInflater.java index 35cf129d81b8..f9b629c85fb0 100644 --- a/core/java/android/view/LayoutInflater.java +++ b/core/java/android/view/LayoutInflater.java @@ -566,7 +566,7 @@ public abstract class LayoutInflater { String layout = res.getResourceEntryName(resource); try { - Class clazz = mPrecompiledClassLoader.loadClass("" + pkg + ".CompiledView"); + Class clazz = Class.forName("" + pkg + ".CompiledView", false, mPrecompiledClassLoader); Method inflater = clazz.getMethod(layout, Context.class, int.class); View view = (View) inflater.invoke(null, mContext, resource); @@ -827,8 +827,8 @@ public abstract class LayoutInflater { if (constructor == null) { // Class not found in the cache, see if it's real, and try to add it - clazz = mContext.getClassLoader().loadClass( - prefix != null ? (prefix + name) : name).asSubclass(View.class); + clazz = Class.forName(prefix != null ? (prefix + name) : name, false, + mContext.getClassLoader()).asSubclass(View.class); if (mFilter != null && clazz != null) { boolean allowed = mFilter.onLoadClass(clazz); @@ -846,8 +846,8 @@ public abstract class LayoutInflater { Boolean allowedState = mFilterMap.get(name); if (allowedState == null) { // New class -- remember whether it is allowed - clazz = mContext.getClassLoader().loadClass( - prefix != null ? (prefix + name) : name).asSubclass(View.class); + clazz = Class.forName(prefix != null ? (prefix + name) : name, false, + mContext.getClassLoader()).asSubclass(View.class); boolean allowed = clazz != null && mFilter.onLoadClass(clazz); mFilterMap.put(name, allowed); diff --git a/core/java/android/view/RenderNodeAnimator.java b/core/java/android/view/RenderNodeAnimator.java index 7ab9534941c7..04ac92292740 100644 --- a/core/java/android/view/RenderNodeAnimator.java +++ b/core/java/android/view/RenderNodeAnimator.java @@ -26,6 +26,7 @@ import android.graphics.RecordingCanvas; import android.graphics.RenderNode; import android.os.Build; import android.os.Handler; +import android.os.Looper; import android.util.SparseIntArray; import com.android.internal.util.VirtualRefBasePtr; @@ -191,6 +192,9 @@ public class RenderNodeAnimator extends Animator { } mState = STATE_DELAYED; + if (mHandler == null) { + mHandler = new Handler(true); + } applyInterpolator(); if (mNativePtr == null) { @@ -224,9 +228,6 @@ public class RenderNodeAnimator extends Animator { private void moveToRunningState() { mState = STATE_RUNNING; if (mNativePtr != null) { - if (mHandler == null) { - mHandler = new Handler(); - } nStart(mNativePtr.get()); } notifyStartListeners(); @@ -503,7 +504,11 @@ public class RenderNodeAnimator extends Animator { // Called by native @UnsupportedAppUsage private static void callOnFinished(RenderNodeAnimator animator) { - animator.mHandler.post(animator::onFinished); + if (animator.mHandler != null) { + animator.mHandler.post(animator::onFinished); + } else { + new Handler(Looper.getMainLooper(), null, true).post(animator::onFinished); + } } @Override diff --git a/core/java/android/view/textclassifier/intent/ClassificationIntentFactory.java b/core/java/android/view/textclassifier/intent/ClassificationIntentFactory.java index b0348462fd54..22e374f2b38f 100644 --- a/core/java/android/view/textclassifier/intent/ClassificationIntentFactory.java +++ b/core/java/android/view/textclassifier/intent/ClassificationIntentFactory.java @@ -48,6 +48,7 @@ public interface ClassificationIntentFactory { context.getString(com.android.internal.R.string.translate), /* titleWithEntity */ null, context.getString(com.android.internal.R.string.translate_desc), + /* descriptionWithAppName */ null, new Intent(Intent.ACTION_TRANSLATE) // TODO: Probably better to introduce a "translate" scheme instead of // using EXTRA_TEXT. diff --git a/core/java/android/view/textclassifier/intent/LabeledIntent.java b/core/java/android/view/textclassifier/intent/LabeledIntent.java index 11d64f185e7d..b4bc8d39d562 100644 --- a/core/java/android/view/textclassifier/intent/LabeledIntent.java +++ b/core/java/android/view/textclassifier/intent/LabeledIntent.java @@ -56,6 +56,8 @@ public final class LabeledIntent { @Nullable public final String titleWithEntity; public final String description; + @Nullable + public final String descriptionWithAppName; // Do not update this intent. public final Intent intent; public final int requestCode; @@ -75,6 +77,7 @@ public final class LabeledIntent { @Nullable String titleWithoutEntity, @Nullable String titleWithEntity, String description, + @Nullable String descriptionWithAppName, Intent intent, int requestCode) { if (TextUtils.isEmpty(titleWithEntity) && TextUtils.isEmpty(titleWithoutEntity)) { @@ -84,6 +87,7 @@ public final class LabeledIntent { this.titleWithoutEntity = titleWithoutEntity; this.titleWithEntity = titleWithEntity; this.description = Preconditions.checkNotNull(description); + this.descriptionWithAppName = descriptionWithAppName; this.intent = Preconditions.checkNotNull(intent); this.requestCode = requestCode; } @@ -141,11 +145,39 @@ public final class LabeledIntent { Log.w(TAG, "Custom titleChooser return null, fallback to the default titleChooser"); title = DEFAULT_TITLE_CHOOSER.chooseTitle(this, resolveInfo); } - final RemoteAction action = new RemoteAction(icon, title, description, pendingIntent); + final RemoteAction action = + new RemoteAction(icon, title, resolveDescription(resolveInfo, pm), pendingIntent); action.setShouldShowIcon(shouldShowIcon); return new Result(resolvedIntent, action); } + private String resolveDescription(ResolveInfo resolveInfo, PackageManager packageManager) { + if (!TextUtils.isEmpty(descriptionWithAppName)) { + // Example string format of descriptionWithAppName: "Use %1$s to open map". + String applicationName = getApplicationName(resolveInfo, packageManager); + if (!TextUtils.isEmpty(applicationName)) { + return String.format(descriptionWithAppName, applicationName); + } + } + return description; + } + + @Nullable + private String getApplicationName( + ResolveInfo resolveInfo, PackageManager packageManager) { + if (resolveInfo.activityInfo == null) { + return null; + } + if ("android".equals(resolveInfo.activityInfo.packageName)) { + return null; + } + if (resolveInfo.activityInfo.applicationInfo == null) { + return null; + } + return (String) packageManager.getApplicationLabel( + resolveInfo.activityInfo.applicationInfo); + } + private Bundle getFromTextClassifierExtra(@Nullable Bundle textLanguagesBundle) { if (textLanguagesBundle != null) { final Bundle bundle = new Bundle(); diff --git a/core/java/android/view/textclassifier/intent/LegacyClassificationIntentFactory.java b/core/java/android/view/textclassifier/intent/LegacyClassificationIntentFactory.java index 7916791e141d..8d60ad850afa 100644 --- a/core/java/android/view/textclassifier/intent/LegacyClassificationIntentFactory.java +++ b/core/java/android/view/textclassifier/intent/LegacyClassificationIntentFactory.java @@ -46,6 +46,7 @@ import java.util.concurrent.TimeUnit; * Creates intents based on the classification type. * @hide */ +// TODO: Consider to support {@code descriptionWithAppName}. public final class LegacyClassificationIntentFactory implements ClassificationIntentFactory { private static final String TAG = "LegacyClassificationIntentFactory"; @@ -108,6 +109,7 @@ public final class LegacyClassificationIntentFactory implements ClassificationIn context.getString(com.android.internal.R.string.email), /* titleWithEntity */ null, context.getString(com.android.internal.R.string.email_desc), + /* descriptionWithAppName */ null, new Intent(Intent.ACTION_SENDTO) .setData(Uri.parse(String.format("mailto:%s", text))), LabeledIntent.DEFAULT_REQUEST_CODE)); @@ -115,6 +117,7 @@ public final class LegacyClassificationIntentFactory implements ClassificationIn context.getString(com.android.internal.R.string.add_contact), /* titleWithEntity */ null, context.getString(com.android.internal.R.string.add_contact_desc), + /* descriptionWithAppName */ null, new Intent(Intent.ACTION_INSERT_OR_EDIT) .setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE) .putExtra(ContactsContract.Intents.Insert.EMAIL, text), @@ -133,6 +136,7 @@ public final class LegacyClassificationIntentFactory implements ClassificationIn context.getString(com.android.internal.R.string.dial), /* titleWithEntity */ null, context.getString(com.android.internal.R.string.dial_desc), + /* descriptionWithAppName */ null, new Intent(Intent.ACTION_DIAL).setData( Uri.parse(String.format("tel:%s", text))), LabeledIntent.DEFAULT_REQUEST_CODE)); @@ -141,6 +145,7 @@ public final class LegacyClassificationIntentFactory implements ClassificationIn context.getString(com.android.internal.R.string.add_contact), /* titleWithEntity */ null, context.getString(com.android.internal.R.string.add_contact_desc), + /* descriptionWithAppName */ null, new Intent(Intent.ACTION_INSERT_OR_EDIT) .setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE) .putExtra(ContactsContract.Intents.Insert.PHONE, text), @@ -150,6 +155,7 @@ public final class LegacyClassificationIntentFactory implements ClassificationIn context.getString(com.android.internal.R.string.sms), /* titleWithEntity */ null, context.getString(com.android.internal.R.string.sms_desc), + /* descriptionWithAppName */ null, new Intent(Intent.ACTION_SENDTO) .setData(Uri.parse(String.format("smsto:%s", text))), LabeledIntent.DEFAULT_REQUEST_CODE)); @@ -166,6 +172,7 @@ public final class LegacyClassificationIntentFactory implements ClassificationIn context.getString(com.android.internal.R.string.map), /* titleWithEntity */ null, context.getString(com.android.internal.R.string.map_desc), + /* descriptionWithAppName */ null, new Intent(Intent.ACTION_VIEW) .setData(Uri.parse(String.format("geo:0,0?q=%s", encText))), LabeledIntent.DEFAULT_REQUEST_CODE)); @@ -185,6 +192,7 @@ public final class LegacyClassificationIntentFactory implements ClassificationIn context.getString(com.android.internal.R.string.browse), /* titleWithEntity */ null, context.getString(com.android.internal.R.string.browse_desc), + /* descriptionWithAppName */ null, new Intent(Intent.ACTION_VIEW) .setDataAndNormalize(Uri.parse(text)) .putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName()), @@ -216,6 +224,7 @@ public final class LegacyClassificationIntentFactory implements ClassificationIn context.getString(com.android.internal.R.string.view_flight), /* titleWithEntity */ null, context.getString(com.android.internal.R.string.view_flight_desc), + /* descriptionWithAppName */ null, new Intent(Intent.ACTION_WEB_SEARCH) .putExtra(SearchManager.QUERY, text), text.hashCode())); @@ -231,6 +240,7 @@ public final class LegacyClassificationIntentFactory implements ClassificationIn context.getString(com.android.internal.R.string.view_calendar), /* titleWithEntity */ null, context.getString(com.android.internal.R.string.view_calendar_desc), + /* descriptionWithAppName */ null, new Intent(Intent.ACTION_VIEW).setData(builder.build()), LabeledIntent.DEFAULT_REQUEST_CODE); } @@ -243,6 +253,7 @@ public final class LegacyClassificationIntentFactory implements ClassificationIn context.getString(com.android.internal.R.string.add_calendar_event), /* titleWithEntity */ null, context.getString(com.android.internal.R.string.add_calendar_event_desc), + /* descriptionWithAppName */ null, new Intent(Intent.ACTION_INSERT) .setData(CalendarContract.Events.CONTENT_URI) .putExtra(CalendarContract.EXTRA_EVENT_ALL_DAY, isAllDay) @@ -260,6 +271,7 @@ public final class LegacyClassificationIntentFactory implements ClassificationIn context.getString(com.android.internal.R.string.define), /* titleWithEntity */ null, context.getString(com.android.internal.R.string.define_desc), + /* descriptionWithAppName */ null, new Intent(Intent.ACTION_DEFINE) .putExtra(Intent.EXTRA_TEXT, text), text.hashCode())); diff --git a/core/java/android/view/textclassifier/intent/TemplateIntentFactory.java b/core/java/android/view/textclassifier/intent/TemplateIntentFactory.java index 59cd7aba05e6..7a3956996972 100644 --- a/core/java/android/view/textclassifier/intent/TemplateIntentFactory.java +++ b/core/java/android/view/textclassifier/intent/TemplateIntentFactory.java @@ -61,6 +61,7 @@ public final class TemplateIntentFactory { remoteActionTemplate.titleWithoutEntity, remoteActionTemplate.titleWithEntity, remoteActionTemplate.description, + remoteActionTemplate.descriptionWithAppName, createIntent(remoteActionTemplate), remoteActionTemplate.requestCode == null ? LabeledIntent.DEFAULT_REQUEST_CODE diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl index 1c901823566a..72dbbf3b8b87 100644 --- a/core/java/com/android/internal/app/IAppOpsService.aidl +++ b/core/java/com/android/internal/app/IAppOpsService.aidl @@ -26,8 +26,9 @@ import com.android.internal.app.IAppOpsActiveCallback; import com.android.internal.app.IAppOpsNotedCallback; interface IAppOpsService { - // These first methods are also called by native code, so must + // These methods are also called by native code, so must // be kept in sync with frameworks/native/libs/binder/include/binder/IAppOpsService.h + // and not be reordered int checkOperation(int code, int uid, String packageName); int noteOperation(int code, int uid, String packageName); int startOperation(IBinder token, int code, int uid, String packageName, @@ -38,6 +39,10 @@ interface IAppOpsService { void stopWatchingMode(IAppOpsCallback callback); IBinder getToken(IBinder clientToken); int permissionToOpCode(String permission); + int checkAudioOperation(int code, int usage, int uid, String packageName); + // End of methods also called by native code. + // Any new method exposed to native must be added after the last one, do not reorder + int noteProxyOperation(int code, int proxyUid, String proxyPackageName, int callingUid, String callingPackageName); @@ -62,7 +67,6 @@ interface IAppOpsService { void setMode(int code, int uid, String packageName, int mode); @UnsupportedAppUsage void resetAllModes(int reqUserId, String reqPackageName); - int checkAudioOperation(int code, int usage, int uid, String packageName); void setAudioRestriction(int code, int usage, int uid, int mode, in String[] exceptionPackages); void setUserRestrictions(in Bundle restrictions, IBinder token, int userHandle); diff --git a/core/java/com/android/internal/os/KernelCpuThreadReader.java b/core/java/com/android/internal/os/KernelCpuThreadReader.java index 3686048cee15..d92f725bb4df 100644 --- a/core/java/com/android/internal/os/KernelCpuThreadReader.java +++ b/core/java/com/android/internal/os/KernelCpuThreadReader.java @@ -21,6 +21,7 @@ import android.os.Process; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.ArrayUtils; import com.android.internal.util.Preconditions; import java.io.IOException; @@ -29,6 +30,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; +import java.util.Arrays; import java.util.function.Predicate; /** @@ -233,7 +235,7 @@ public class KernelCpuThreadReader { mFrequencyBucketCreator = new FrequencyBucketCreator(mProcTimeInStateReader.getFrequenciesKhz(), numBuckets); mFrequenciesKhz = - mFrequencyBucketCreator.getBucketMinFrequencies( + mFrequencyBucketCreator.bucketFrequencies( mProcTimeInStateReader.getFrequenciesKhz()); } @@ -317,7 +319,7 @@ public class KernelCpuThreadReader { if (cpuUsagesLong == null) { return null; } - int[] cpuUsages = mFrequencyBucketCreator.getBucketedValues(cpuUsagesLong); + int[] cpuUsages = mFrequencyBucketCreator.bucketValues(cpuUsagesLong); return new ThreadCpuUsage(threadId, threadName, cpuUsages); } @@ -359,128 +361,156 @@ public class KernelCpuThreadReader { } } - /** Puts frequencies and usage times into buckets */ + /** + * Quantizes a list of N frequencies into a list of M frequencies (where M<=N) + * + * <p>In order to reduce data sent from the device, we discard precise frequency information for + * an approximation. This is done by putting groups of adjacent frequencies into the same + * bucket, and then reporting that bucket under the minimum frequency in that bucket. + * + * <p>Many devices have multiple core clusters. We do not want to report frequencies from + * different clusters under the same bucket, so some complication arises. + * + * <p>Buckets are allocated evenly across all core clusters, i.e. they all have the same number + * of buckets regardless of how many frequencies they contain. This is done to reduce code + * complexity, and in practice the number of frequencies doesn't vary too much between core + * clusters. + * + * <p>If the number of buckets is not a factor of the number of frequencies, the remainder of + * the frequencies are placed into the last bucket. + * + * <p>It is possible to have less buckets than asked for, so any calling code can't assume that + * initializing with N buckets will use return N values. This happens in two scenarios: + * + * <ul> + * <li>There are less frequencies available than buckets asked for. + * <li>There are less frequencies in a core cluster than buckets allocated to that core + * cluster. + * </ul> + */ @VisibleForTesting public static class FrequencyBucketCreator { - private final int mNumBuckets; private final int mNumFrequencies; - private final int mBigFrequenciesStartIndex; - private final int mLittleNumBuckets; - private final int mBigNumBuckets; - private final int mLittleBucketSize; - private final int mBigBucketSize; + private final int mNumBuckets; + private final int[] mBucketStartIndices; - /** - * Buckets based of a list of frequencies - * - * @param frequencies the frequencies to base buckets off - * @param numBuckets how many buckets to create - */ @VisibleForTesting - public FrequencyBucketCreator(long[] frequencies, int numBuckets) { - Preconditions.checkArgument(numBuckets > 0); - + public FrequencyBucketCreator(long[] frequencies, int targetNumBuckets) { mNumFrequencies = frequencies.length; - mBigFrequenciesStartIndex = getBigFrequenciesStartIndex(frequencies); - - final int littleNumBuckets; - final int bigNumBuckets; - if (mBigFrequenciesStartIndex < frequencies.length) { - littleNumBuckets = numBuckets / 2; - bigNumBuckets = numBuckets - littleNumBuckets; - } else { - // If we've got no big frequencies, set all buckets to little frequencies - littleNumBuckets = numBuckets; - bigNumBuckets = 0; - } - - // Ensure that we don't have more buckets than frequencies - mLittleNumBuckets = Math.min(littleNumBuckets, mBigFrequenciesStartIndex); - mBigNumBuckets = - Math.min(bigNumBuckets, frequencies.length - mBigFrequenciesStartIndex); - mNumBuckets = mLittleNumBuckets + mBigNumBuckets; - - // Set the size of each little and big bucket. If they have no buckets, the size is zero - mLittleBucketSize = - mLittleNumBuckets == 0 ? 0 : mBigFrequenciesStartIndex / mLittleNumBuckets; - mBigBucketSize = - mBigNumBuckets == 0 - ? 0 - : (frequencies.length - mBigFrequenciesStartIndex) / mBigNumBuckets; + int[] clusterStartIndices = getClusterStartIndices(frequencies); + mBucketStartIndices = + getBucketStartIndices(clusterStartIndices, targetNumBuckets, mNumFrequencies); + mNumBuckets = mBucketStartIndices.length; } - /** Find the index where frequencies change from little core to big core */ + /** + * Put an array of values into buckets. This takes a {@code long[]} and returns {@code + * int[]} as everywhere this method is used will have to do the conversion anyway, so we + * save time by doing it here instead + * + * @param values the values to bucket + * @return the bucketed usage times + */ @VisibleForTesting - public static int getBigFrequenciesStartIndex(long[] frequenciesKhz) { - for (int i = 0; i < frequenciesKhz.length - 1; i++) { - if (frequenciesKhz[i] > frequenciesKhz[i + 1]) { - return i + 1; + public int[] bucketValues(long[] values) { + Preconditions.checkArgument(values.length == mNumFrequencies); + int[] buckets = new int[mNumBuckets]; + for (int bucketIdx = 0; bucketIdx < mNumBuckets; bucketIdx++) { + final int bucketStartIdx = getLowerBound(bucketIdx, mBucketStartIndices); + final int bucketEndIdx = + getUpperBound(bucketIdx, mBucketStartIndices, values.length); + for (int valuesIdx = bucketStartIdx; valuesIdx < bucketEndIdx; valuesIdx++) { + buckets[bucketIdx] += values[valuesIdx]; } } - - return frequenciesKhz.length; + return buckets; } /** Get the minimum frequency in each bucket */ @VisibleForTesting - public int[] getBucketMinFrequencies(long[] frequenciesKhz) { - Preconditions.checkArgument(frequenciesKhz.length == mNumFrequencies); - // If there's only one bucket, we bucket everything together so the first bucket is the - // min frequency - if (mNumBuckets == 1) { - return new int[] {(int) frequenciesKhz[0]}; - } - - final int[] bucketMinFrequencies = new int[mNumBuckets]; - // Initialize little buckets min frequencies - for (int i = 0; i < mLittleNumBuckets; i++) { - bucketMinFrequencies[i] = (int) frequenciesKhz[i * mLittleBucketSize]; + public int[] bucketFrequencies(long[] frequencies) { + Preconditions.checkArgument(frequencies.length == mNumFrequencies); + int[] buckets = new int[mNumBuckets]; + for (int i = 0; i < buckets.length; i++) { + buckets[i] = (int) frequencies[mBucketStartIndices[i]]; } - // Initialize big buckets min frequencies - for (int i = 0; i < mBigNumBuckets; i++) { - final int frequencyIndex = mBigFrequenciesStartIndex + i * mBigBucketSize; - bucketMinFrequencies[mLittleNumBuckets + i] = (int) frequenciesKhz[frequencyIndex]; - } - return bucketMinFrequencies; + return buckets; } /** - * Put an array of values into buckets. This takes a {@code long[]} and returns {@code - * int[]} as everywhere this method is used will have to do the conversion anyway, so we - * save time by doing it here instead + * Get the index in frequencies where each core cluster starts * - * @param values the values to bucket - * @return the bucketed usage times + * <p>The frequencies for each cluster are given in ascending order, appended to each other. + * This means that every time there is a decrease in frequencies (instead of increase) a new + * cluster has started. */ - @VisibleForTesting - @SuppressWarnings("ForLoopReplaceableByForEach") - public int[] getBucketedValues(long[] values) { - Preconditions.checkArgument(values.length == mNumFrequencies); - final int[] bucketed = new int[mNumBuckets]; - - // If there's only one bucket, add all frequencies in - if (mNumBuckets == 1) { - for (int i = 0; i < values.length; i++) { - bucketed[0] += values[i]; + private static int[] getClusterStartIndices(long[] frequencies) { + ArrayList<Integer> indices = new ArrayList<>(); + indices.add(0); + for (int i = 0; i < frequencies.length - 1; i++) { + if (frequencies[i] >= frequencies[i + 1]) { + indices.add(i + 1); } - return bucketed; } + return ArrayUtils.convertToIntArray(indices); + } + + /** Get the index in frequencies where each bucket starts */ + private static int[] getBucketStartIndices( + int[] clusterStartIndices, int targetNumBuckets, int numFrequencies) { + int numClusters = clusterStartIndices.length; + + // If we haven't got enough buckets for every cluster, we instead have one bucket per + // cluster, with the last bucket containing the remaining clusters + if (numClusters > targetNumBuckets) { + return Arrays.copyOfRange(clusterStartIndices, 0, targetNumBuckets); + } + + ArrayList<Integer> bucketStartIndices = new ArrayList<>(); + for (int clusterIdx = 0; clusterIdx < numClusters; clusterIdx++) { + final int clusterStartIdx = getLowerBound(clusterIdx, clusterStartIndices); + final int clusterEndIdx = + getUpperBound(clusterIdx, clusterStartIndices, numFrequencies); + + final int numBucketsInCluster; + if (clusterIdx != numClusters - 1) { + numBucketsInCluster = targetNumBuckets / numClusters; + } else { + // If we're in the last cluster, the bucket will contain the remainder of the + // frequencies + int previousBucketsInCluster = targetNumBuckets / numClusters; + numBucketsInCluster = + targetNumBuckets - (previousBucketsInCluster * (numClusters - 1)); + } - // Initialize the little buckets - for (int i = 0; i < mBigFrequenciesStartIndex; i++) { - final int bucketIndex = Math.min(i / mLittleBucketSize, mLittleNumBuckets - 1); - bucketed[bucketIndex] += values[i]; + final int numFrequenciesInCluster = clusterEndIdx - clusterStartIdx; + // If there are less frequencies than buckets in a cluster, we have one bucket per + // frequency, and do not use the remaining buckets + final int numFrequenciesInBucket = + Math.max(1, numFrequenciesInCluster / numBucketsInCluster); + for (int bucketIdx = 0; bucketIdx < numBucketsInCluster; bucketIdx++) { + int bucketStartIdx = clusterStartIdx + bucketIdx * numFrequenciesInBucket; + // If we've gone over the end index, ignore the rest of the buckets for this + // cluster + if (bucketStartIdx >= clusterEndIdx) { + break; + } + bucketStartIndices.add(bucketStartIdx); + } } - // Initialize the big buckets - for (int i = mBigFrequenciesStartIndex; i < values.length; i++) { - final int bucketIndex = - Math.min( - mLittleNumBuckets - + (i - mBigFrequenciesStartIndex) / mBigBucketSize, - mNumBuckets - 1); - bucketed[bucketIndex] += values[i]; + return ArrayUtils.convertToIntArray(bucketStartIndices); + } + + private static int getLowerBound(int index, int[] startIndices) { + return startIndices[index]; + } + + private static int getUpperBound(int index, int[] startIndices, int max) { + if (index != startIndices.length - 1) { + return startIndices[index + 1]; + } else { + return max; } - return bucketed; } } diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java index 8ca0bd1df1eb..afdeb1b602d7 100644 --- a/core/java/com/android/internal/os/Zygote.java +++ b/core/java/com/android/internal/os/Zygote.java @@ -197,9 +197,6 @@ public final class Zygote { private Zygote() {} - /** Called for some security initialization before any fork. */ - static native void nativeSecurityInit(); - /** * Forks a new VM instance. The current VM must have been started * with the -Xzygote flag. <b>NOTE: new instance keeps all @@ -384,22 +381,19 @@ public final class Zygote { native protected static void nativeInstallSeccompUidGidFilter(int uidGidMin, int uidGidMax); /** - * Zygote unmount storage space on initializing. - * This method is called once. - */ - protected static native void nativeUnmountStorageOnInit(); - - /** - * Get socket file descriptors (opened by init) from the environment and - * store them for access from native code later. + * Initialize the native state of the Zygote. This inclues + * - Fetching socket FDs from the environment + * - Initializing security properties + * - Unmounting storage as appropriate + * - Loading necessary performance profile information * * @param isPrimary True if this is the zygote process, false if it is zygote_secondary */ - public static void getSocketFDs(boolean isPrimary) { - nativeGetSocketFDs(isPrimary); + static void initNativeState(boolean isPrimary) { + nativeInitNativeState(isPrimary); } - protected static native void nativeGetSocketFDs(boolean isPrimary); + protected static native void nativeInitNativeState(boolean isPrimary); /** * Returns the raw string value of a system property. diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index 7cca7b77b45d..bb7b09ab4df3 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -20,10 +20,10 @@ import static android.system.OsConstants.S_IRWXG; import static android.system.OsConstants.S_IRWXO; import android.annotation.UnsupportedAppUsage; -import android.content.res.Resources; -import android.content.res.TypedArray; import android.app.ApplicationLoaders; import android.content.pm.SharedLibraryInfo; +import android.content.res.Resources; +import android.content.res.TypedArray; import android.os.Build; import android.os.Environment; import android.os.IInstalld; @@ -863,8 +863,6 @@ public class ZygoteInit { throw new RuntimeException("No ABI list supplied."); } - Zygote.getSocketFDs(isPrimaryZygote); - // In some configurations, we avoid preloading resources and classes eagerly. // In such cases, we will preload things prior to our first fork. if (!enableLazyPreload) { @@ -889,10 +887,8 @@ public class ZygoteInit { // Zygote. Trace.setTracingEnabled(false, 0); - Zygote.nativeSecurityInit(); - // Zygote process unmounts root storage spaces. - Zygote.nativeUnmountStorageOnInit(); + Zygote.initNativeState(isPrimaryZygote); ZygoteHooks.stopZygoteNoThreadCreation(); diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java index ca5db940a4d0..625814ddc4c7 100644 --- a/core/java/com/android/internal/policy/DecorView.java +++ b/core/java/com/android/internal/policy/DecorView.java @@ -1143,7 +1143,6 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind // If we didn't request fullscreen layout, but we still got it because of the // mForceWindowDrawsStatusBarBackground flag, also consume top inset. boolean consumingStatusBar = (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) == 0 - && (sysUiVisibility & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0 && (attrs.flags & FLAG_LAYOUT_IN_SCREEN) == 0 && (attrs.flags & FLAG_LAYOUT_INSET_DECOR) == 0 && mForceWindowDrawsStatusBarBackground diff --git a/core/jni/Android.bp b/core/jni/Android.bp index 000c0449358a..0e31ab9c5fbb 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -204,7 +204,6 @@ cc_library_shared { "android_content_res_Configuration.cpp", "android_animation_PropertyValuesHolder.cpp", "android_security_Scrypt.cpp", - "com_android_internal_net_NetworkStatsFactory.cpp", "com_android_internal_os_AtomicDirectory.cpp", "com_android_internal_os_ClassLoaderFactory.cpp", "com_android_internal_os_FuseAppLoop.cpp", diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index ccd0b666865e..967abce60cd3 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -227,7 +227,6 @@ extern int register_android_content_res_Configuration(JNIEnv* env); extern int register_android_animation_PropertyValuesHolder(JNIEnv *env); extern int register_android_security_Scrypt(JNIEnv *env); extern int register_com_android_internal_content_NativeLibraryHelper(JNIEnv *env); -extern int register_com_android_internal_net_NetworkStatsFactory(JNIEnv *env); extern int register_com_android_internal_os_AtomicDirectory(JNIEnv *env); extern int register_com_android_internal_os_ClassLoaderFactory(JNIEnv* env); extern int register_com_android_internal_os_FuseAppLoop(JNIEnv* env); @@ -1569,7 +1568,6 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_animation_PropertyValuesHolder), REG_JNI(register_android_security_Scrypt), REG_JNI(register_com_android_internal_content_NativeLibraryHelper), - REG_JNI(register_com_android_internal_net_NetworkStatsFactory), REG_JNI(register_com_android_internal_os_AtomicDirectory), REG_JNI(register_com_android_internal_os_FuseAppLoop), }; diff --git a/core/jni/android_database_CursorWindow.cpp b/core/jni/android_database_CursorWindow.cpp index 86cda4455734..5e4d6e38569b 100644 --- a/core/jni/android_database_CursorWindow.cpp +++ b/core/jni/android_database_CursorWindow.cpp @@ -92,7 +92,9 @@ static jlong nativeCreate(JNIEnv* env, jclass clazz, jstring nameObj, jint curso CursorWindow* window; status_t status = CursorWindow::create(name, cursorWindowSize, &window); if (status || !window) { - ALOGE("Could not allocate CursorWindow '%s' of size %d due to error %d.", + jniThrowExceptionFmt(env, + "android/database/CursorWindowAllocationException", + "Could not allocate CursorWindow '%s' of size %d due to error %d.", name.string(), cursorWindowSize, status); return 0; } @@ -107,7 +109,9 @@ static jlong nativeCreateFromParcel(JNIEnv* env, jclass clazz, jobject parcelObj CursorWindow* window; status_t status = CursorWindow::createFromParcel(parcel, &window); if (status || !window) { - ALOGE("Could not create CursorWindow from Parcel due to error %d, process fd count=%d", + jniThrowExceptionFmt(env, + "android/database/CursorWindowAllocationException", + "Could not create CursorWindow from Parcel due to error %d, process fd count=%d", status, getFdCount()); return 0; } diff --git a/core/jni/android_hardware_SoundTrigger.cpp b/core/jni/android_hardware_SoundTrigger.cpp index c7805ea12f02..03057dc5e602 100644 --- a/core/jni/android_hardware_SoundTrigger.cpp +++ b/core/jni/android_hardware_SoundTrigger.cpp @@ -21,6 +21,7 @@ #include "jni.h" #include <nativehelper/JNIHelp.h> +#include <nativehelper/ScopedUtfChars.h> #include "core_jni_helpers.h" #include <system/sound_trigger.h> #include <soundtrigger/SoundTriggerCallback.h> @@ -395,7 +396,7 @@ static sp<SoundTrigger> setSoundTrigger(JNIEnv* env, jobject thiz, const sp<Soun static jint android_hardware_SoundTrigger_listModules(JNIEnv *env, jobject clazz, - jobject jModules) + jstring opPackageName, jobject jModules) { ALOGV("listModules"); @@ -411,7 +412,10 @@ android_hardware_SoundTrigger_listModules(JNIEnv *env, jobject clazz, unsigned int numModules = 0; struct sound_trigger_module_descriptor *nModules = NULL; - status_t status = SoundTrigger::listModules(nModules, &numModules); + ScopedUtfChars opPackageNameStr(env, opPackageName); + const String16 opPackageNameString16 = String16(opPackageNameStr.c_str()); + + status_t status = SoundTrigger::listModules(opPackageNameString16, nModules, &numModules); if (status != NO_ERROR || numModules == 0) { return (jint)status; } @@ -419,7 +423,7 @@ android_hardware_SoundTrigger_listModules(JNIEnv *env, jobject clazz, nModules = (struct sound_trigger_module_descriptor *) calloc(numModules, sizeof(struct sound_trigger_module_descriptor)); - status = SoundTrigger::listModules(nModules, &numModules); + status = SoundTrigger::listModules(opPackageNameString16, nModules, &numModules); ALOGV("listModules SoundTrigger::listModules status %d numModules %d", status, numModules); if (status != NO_ERROR) { @@ -470,16 +474,20 @@ exit: } static void -android_hardware_SoundTrigger_setup(JNIEnv *env, jobject thiz, jobject weak_this) +android_hardware_SoundTrigger_setup(JNIEnv *env, jobject thiz, + jstring opPackageName, jobject weak_this) { ALOGV("setup"); + ScopedUtfChars opPackageNameStr(env, opPackageName); + const String16 opPackageNameString16 = String16(opPackageNameStr.c_str()); + sp<JNISoundTriggerCallback> callback = new JNISoundTriggerCallback(env, thiz, weak_this); sound_trigger_module_handle_t handle = (sound_trigger_module_handle_t)env->GetIntField(thiz, gModuleFields.mId); - sp<SoundTrigger> module = SoundTrigger::attach(handle, callback); + sp<SoundTrigger> module = SoundTrigger::attach(opPackageNameString16, handle, callback); if (module == 0) { return; } @@ -816,14 +824,14 @@ android_hardware_SoundTrigger_getModelState(JNIEnv *env, jobject thiz, static const JNINativeMethod gMethods[] = { {"listModules", - "(Ljava/util/ArrayList;)I", + "(Ljava/lang/String;Ljava/util/ArrayList;)I", (void *)android_hardware_SoundTrigger_listModules}, }; static const JNINativeMethod gModuleMethods[] = { {"native_setup", - "(Ljava/lang/Object;)V", + "(Ljava/lang/String;Ljava/lang/Object;)V", (void *)android_hardware_SoundTrigger_setup}, {"native_finalize", "()V", diff --git a/core/jni/android_os_SELinux.cpp b/core/jni/android_os_SELinux.cpp index 8cb10782310c..236ee6123cde 100644 --- a/core/jni/android_os_SELinux.cpp +++ b/core/jni/android_os_SELinux.cpp @@ -24,10 +24,30 @@ #include "selinux/android.h" #include <errno.h> #include <memory> +#include <atomic> #include <nativehelper/ScopedLocalRef.h> #include <nativehelper/ScopedUtfChars.h> namespace android { +namespace { +std::atomic<selabel_handle*> sehandle{nullptr}; + +selabel_handle* GetSELabelHandle() { + selabel_handle* h = sehandle.load(); + if (h != nullptr) { + return h; + } + + h = selinux_android_file_context_handle(); + selabel_handle* expected = nullptr; + if (!sehandle.compare_exchange_strong(expected, h)) { + selabel_close(h); + return sehandle.load(); + } + return h; +} + +} struct SecurityContext_Delete { void operator()(security_context_t p) const { @@ -60,6 +80,44 @@ static jboolean isSELinuxEnforced(JNIEnv *env, jobject) { return (security_getenforce() == 1) ? true : false; } +static jstring fileSelabelLookup(JNIEnv* env, jobject, jstring pathStr) { + if (isSELinuxDisabled) { + ALOGE("fileSelabelLookup => SELinux is disabled"); + return NULL; + } + + if (pathStr == NULL) { + ALOGE("fileSelabelLookup => got null path."); + jniThrowNullPointerException( + env, "Trying to get security context of a null path."); + return NULL; + } + + ScopedUtfChars path(env, pathStr); + const char* path_c_str = path.c_str(); + if (path_c_str == NULL) { + ALOGE("fileSelabelLookup => Got null path"); + jniThrowNullPointerException( + env, "Trying to get security context of a null path."); + return NULL; + } + + auto* selabel_handle = GetSELabelHandle(); + if (selabel_handle == NULL) { + ALOGE("fileSelabelLookup => Failed to get SEHandle"); + return NULL; + } + + security_context_t tmp = NULL; + if (selabel_lookup(selabel_handle, &tmp, path_c_str, S_IFREG) != 0) { + ALOGE("fileSelabelLookup => selabel_lookup for %s failed: %d", path_c_str, errno); + return NULL; + } + + Unique_SecurityContext context(tmp); + return env->NewStringUTF(context.get()); +} + static jstring getFdConInner(JNIEnv *env, jobject fileDescriptor, bool isSocket) { if (isSELinuxDisabled) { return NULL; @@ -354,6 +412,7 @@ static const JNINativeMethod method_table[] = { { "native_restorecon" , "(Ljava/lang/String;I)Z" , (void*)native_restorecon}, { "setFileContext" , "(Ljava/lang/String;Ljava/lang/String;)Z" , (void*)setFileCon }, { "setFSCreateContext" , "(Ljava/lang/String;)Z" , (void*)setFSCreateCon }, + { "fileSelabelLookup" , "(Ljava/lang/String;)Ljava/lang/String;" , (void*)fileSelabelLookup}, }; static int log_callback(int type, const char *fmt, ...) { diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp index bf56ef438a1d..d3f9196ce763 100644 --- a/core/jni/android_util_AssetManager.cpp +++ b/core/jni/android_util_AssetManager.cpp @@ -163,7 +163,7 @@ static void NativeVerifySystemIdmaps(JNIEnv* /*env*/, jclass /*clazz*/) { } // Generic idmap parameters - const char* argv[8]; + const char* argv[9]; int argc = 0; struct stat st; @@ -199,6 +199,10 @@ static void NativeVerifySystemIdmaps(JNIEnv* /*env*/, jclass /*clazz*/) { argv[argc++] = AssetManager::PRODUCT_SERVICES_OVERLAY_DIR; } + if (stat(AssetManager::ODM_OVERLAY_DIR, &st) == 0) { + argv[argc++] = AssetManager::ODM_OVERLAY_DIR; + } + // Finally, invoke idmap (if any overlay directory exists) if (argc > 5) { execv(AssetManager::IDMAP_BIN, (char* const*)argv); @@ -233,6 +237,10 @@ static jobjectArray NativeCreateIdmapsForStaticOverlaysTargetingAndroid(JNIEnv* input_dirs.push_back(AssetManager::PRODUCT_SERVICES_OVERLAY_DIR); } + if (stat(AssetManager::ODM_OVERLAY_DIR, &st) == 0) { + input_dirs.push_back(AssetManager::ODM_OVERLAY_DIR); + } + if (input_dirs.empty()) { LOG(WARNING) << "no directories for idmap2 to scan"; return env->NewObjectArray(0, g_stringClass, nullptr); diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index 8dd7e8ea3b90..430b9c505223 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -126,7 +126,7 @@ static constexpr const char* kZygoteInitClassName = "com/android/internal/os/Zyg static jclass gZygoteInitClass; static jmethodID gCreateSystemServerClassLoader; -static bool g_is_security_enforced = true; +static bool gIsSecurityEnforced = true; /** * The maximum number of characters (not including a null terminator) that a @@ -510,7 +510,7 @@ static void PreApplicationInit() { } static void SetUpSeccompFilter(uid_t uid, bool is_child_zygote) { - if (!g_is_security_enforced) { + if (!gIsSecurityEnforced) { ALOGI("seccomp disabled by setenforce 0"); return; } @@ -1626,18 +1626,48 @@ std::vector<int> MakeUsapPipeReadFDVector() { return fd_vec; } -} // anonymous namespace +static void UnmountStorageOnInit(JNIEnv* env) { + // Zygote process unmount root storage space initially before every child processes are forked. + // Every forked child processes (include SystemServer) only mount their own root storage space + // and no need unmount storage operation in MountEmulatedStorage method. + // Zygote process does not utilize root storage spaces and unshares its mount namespace below. -namespace android { + // See storage config details at http://source.android.com/tech/storage/ + // Create private mount namespace shared by all children + if (unshare(CLONE_NEWNS) == -1) { + RuntimeAbort(env, __LINE__, "Failed to unshare()"); + return; + } -static void com_android_internal_os_Zygote_nativeSecurityInit(JNIEnv*, jclass) { - // security_getenforce is not allowed on app process. Initialize and cache - // the value before zygote forks. - g_is_security_enforced = security_getenforce(); + // Mark rootfs as being a slave so that changes from default + // namespace only flow into our children. + if (mount("rootfs", "/", nullptr, (MS_SLAVE | MS_REC), nullptr) == -1) { + RuntimeAbort(env, __LINE__, "Failed to mount() rootfs as MS_SLAVE"); + return; + } - selinux_android_seapp_context_init(); + // Create a staging tmpfs that is shared by our children; they will + // bind mount storage into their respective private namespaces, which + // are isolated from each other. + const char* target_base = getenv("EMULATED_STORAGE_TARGET"); + if (target_base != nullptr) { +#define STRINGIFY_UID(x) __STRING(x) + if (mount("tmpfs", target_base, "tmpfs", MS_NOSUID | MS_NODEV, + "uid=0,gid=" STRINGIFY_UID(AID_SDCARD_R) ",mode=0751") == -1) { + ALOGE("Failed to mount tmpfs to %s", target_base); + RuntimeAbort(env, __LINE__, "Failed to mount tmpfs"); + return; + } +#undef STRINGIFY_UID + } + + UnmountTree("/storage"); } +} // anonymous namespace + +namespace android { + static void com_android_internal_os_Zygote_nativePreApplicationInit(JNIEnv*, jclass) { PreApplicationInit(); } @@ -1791,47 +1821,9 @@ static void com_android_internal_os_Zygote_nativeAllowFileAcrossFork( FileDescriptorWhitelist::Get()->Allow(path_cstr); } -static void com_android_internal_os_Zygote_nativeUnmountStorageOnInit(JNIEnv* env, jclass) { - // Zygote process unmount root storage space initially before every child processes are forked. - // Every forked child processes (include SystemServer) only mount their own root storage space - // and no need unmount storage operation in MountEmulatedStorage method. - // Zygote process does not utilize root storage spaces and unshares its mount namespace below. - - // See storage config details at http://source.android.com/tech/storage/ - // Create private mount namespace shared by all children - if (unshare(CLONE_NEWNS) == -1) { - RuntimeAbort(env, __LINE__, "Failed to unshare()"); - return; - } - - // Mark rootfs as being a slave so that changes from default - // namespace only flow into our children. - if (mount("rootfs", "/", nullptr, (MS_SLAVE | MS_REC), nullptr) == -1) { - RuntimeAbort(env, __LINE__, "Failed to mount() rootfs as MS_SLAVE"); - return; - } - - // Create a staging tmpfs that is shared by our children; they will - // bind mount storage into their respective private namespaces, which - // are isolated from each other. - const char* target_base = getenv("EMULATED_STORAGE_TARGET"); - if (target_base != nullptr) { -#define STRINGIFY_UID(x) __STRING(x) - if (mount("tmpfs", target_base, "tmpfs", MS_NOSUID | MS_NODEV, - "uid=0,gid=" STRINGIFY_UID(AID_SDCARD_R) ",mode=0751") == -1) { - ALOGE("Failed to mount tmpfs to %s", target_base); - RuntimeAbort(env, __LINE__, "Failed to mount tmpfs"); - return; - } -#undef STRINGIFY_UID - } - - UnmountTree("/storage"); -} - static void com_android_internal_os_Zygote_nativeInstallSeccompUidGidFilter( JNIEnv* env, jclass, jint uidGidMin, jint uidGidMax) { - if (!g_is_security_enforced) { + if (!gIsSecurityEnforced) { ALOGI("seccomp disabled by setenforce 0"); return; } @@ -1883,8 +1875,12 @@ static void com_android_internal_os_Zygote_nativeSpecializeAppProcess( * @param is_primary If this process is the primary or secondary Zygote; used to compute the name * of the environment variable storing the file descriptors. */ -static void com_android_internal_os_Zygote_nativeGetSocketFDs(JNIEnv* env, jclass, - jboolean is_primary) { +static void com_android_internal_os_Zygote_nativeInitNativeState(JNIEnv* env, jclass, + jboolean is_primary) { + /* + * Obtain file descriptors created by init from the environment. + */ + std::string android_socket_prefix(ANDROID_SOCKET_PREFIX); std::string env_var_name = android_socket_prefix + (is_primary ? "zygote" : "zygote_secondary"); char* env_var_val = getenv(env_var_name.c_str()); @@ -1905,6 +1901,30 @@ static void com_android_internal_os_Zygote_nativeGetSocketFDs(JNIEnv* env, jclas } else { ALOGE("Unable to fetch USAP pool socket file descriptor"); } + + /* + * Security Initialization + */ + + // security_getenforce is not allowed on app process. Initialize and cache + // the value before zygote forks. + gIsSecurityEnforced = security_getenforce(); + + selinux_android_seapp_context_init(); + + /* + * Storage Initialization + */ + + UnmountStorageOnInit(env); + + /* + * Performance Initialization + */ + + if (!SetTaskProfiles(0, {})) { + ZygoteFailure(env, "zygote", nullptr, "Zygote SetTaskProfiles failed"); + } } /** @@ -2001,8 +2021,6 @@ static jboolean com_android_internal_os_Zygote_nativeDisableExecuteOnly(JNIEnv* } static const JNINativeMethod gMethods[] = { - { "nativeSecurityInit", "()V", - (void *) com_android_internal_os_Zygote_nativeSecurityInit }, { "nativeForkAndSpecialize", "(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;)I", (void *) com_android_internal_os_Zygote_nativeForkAndSpecialize }, @@ -2010,8 +2028,6 @@ static const JNINativeMethod gMethods[] = { (void *) com_android_internal_os_Zygote_nativeForkSystemServer }, { "nativeAllowFileAcrossFork", "(Ljava/lang/String;)V", (void *) com_android_internal_os_Zygote_nativeAllowFileAcrossFork }, - { "nativeUnmountStorageOnInit", "()V", - (void *) com_android_internal_os_Zygote_nativeUnmountStorageOnInit }, { "nativePreApplicationInit", "()V", (void *) com_android_internal_os_Zygote_nativePreApplicationInit }, { "nativeInstallSeccompUidGidFilter", "(II)V", @@ -2021,8 +2037,8 @@ static const JNINativeMethod gMethods[] = { { "nativeSpecializeAppProcess", "(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;)V", (void *) com_android_internal_os_Zygote_nativeSpecializeAppProcess }, - { "nativeGetSocketFDs", "(Z)V", - (void *) com_android_internal_os_Zygote_nativeGetSocketFDs }, + { "nativeInitNativeState", "(Z)V", + (void *) com_android_internal_os_Zygote_nativeInitNativeState }, { "nativeGetUsapPipeFDs", "()[I", (void *) com_android_internal_os_Zygote_nativeGetUsapPipeFDs }, { "nativeRemoveUsapTableEntry", "(I)Z", diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp index d8d46560876d..099635246f05 100644 --- a/core/jni/fd_utils.cpp +++ b/core/jni/fd_utils.cpp @@ -102,6 +102,8 @@ bool FileDescriptorWhitelist::IsAllowed(const std::string& path) const { static const char* kProductOverlayDir = "/product/overlay"; static const char* kSystemProductServicesOverlayDir = "/system/product_services/overlay/"; static const char* kProductServicesOverlayDir = "/product_services/overlay"; + static const char* kSystemOdmOverlayDir = "/system/odm/overlay"; + static const char* kOdmOverlayDir = "/odm/overlay"; static const char* kApkSuffix = ".apk"; if ((android::base::StartsWith(path, kOverlayDir) @@ -110,7 +112,9 @@ bool FileDescriptorWhitelist::IsAllowed(const std::string& path) const { || android::base::StartsWith(path, kSystemProductOverlayDir) || android::base::StartsWith(path, kProductOverlayDir) || android::base::StartsWith(path, kSystemProductServicesOverlayDir) - || android::base::StartsWith(path, kProductServicesOverlayDir)) + || android::base::StartsWith(path, kProductServicesOverlayDir) + || android::base::StartsWith(path, kSystemOdmOverlayDir) + || android::base::StartsWith(path, kOdmOverlayDir)) && android::base::EndsWith(path, kApkSuffix) && path.find("/../") == std::string::npos) { return true; diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 3788f479a1a4..fb92fbfd8eb7 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -4506,6 +4506,10 @@ <permission android:name="android.permission.ACCESS_SHARED_LIBRARIES" android:protectionLevel="signature|installer" /> + <!-- Allows input events to be monitored. Very dangerous! @hide --> + <permission android:name="android.permission.MONITOR_INPUT" + android:protectionLevel="signature" /> + <application android:process="system" android:persistent="true" android:hasCode="false" diff --git a/core/res/res/layout-car/car_preference.xml b/core/res/res/layout-car/car_preference.xml index 939c3fbc521d..ae3d63bd2d6a 100644 --- a/core/res/res/layout-car/car_preference.xml +++ b/core/res/res/layout-car/car_preference.xml @@ -20,7 +20,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:background="?android:attr/selectableItemBackground" - android:focusable="true" + android:clipToPadding="false" android:minHeight="?android:attr/listPreferredItemHeightSmall" android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" android:paddingStart="?android:attr/listPreferredItemPaddingStart"> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index b92033eeab7d..4b8a96cf3c4e 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -1567,7 +1567,7 @@ <string name="face_error_lockout_permanent">Too many attempts. Face authentication disabled.</string> <!-- Generic error message shown when the face hardware can't recognize the face. [CHAR LIMIT=50] --> <string name="face_error_unable_to_process">Can\u2019t verify face. Try again.</string> - <!-- Generic error message shown when the user has no enrolled face. [CHAR LIMIT=50] --> + <!-- Generic error message shown when the user has no enrolled face. [CHAR LIMIT=52] --> <string name="face_error_not_enrolled">You haven\u2019t set up face authentication</string> <!-- Generic error message shown when the app requests face authentication on a device without a sensor. [CHAR LIMIT=60] --> <string name="face_error_hw_not_present">Face authentication is not supported on this device</string> diff --git a/core/tests/BroadcastRadioTests/Android.bp b/core/tests/BroadcastRadioTests/Android.bp new file mode 100644 index 000000000000..0aebaaaf137a --- /dev/null +++ b/core/tests/BroadcastRadioTests/Android.bp @@ -0,0 +1,35 @@ +// 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. + +android_test { + name: "BroadcastRadioTests", + privileged: true, + certificate: "platform", + // TODO(b/13282254): uncomment when b/13282254 is fixed + // sdk_version: "current" + platform_apis: true, + static_libs: [ + "compatibility-device-util-axt", + "androidx.test.rules", + "testng", + ], + libs: ["android.test.base"], + srcs: ["src/**/*.java"], + dex_preopt: { + enabled: false, + }, + optimize: { + enabled: false, + }, +} diff --git a/core/tests/BroadcastRadioTests/Android.mk b/core/tests/BroadcastRadioTests/Android.mk deleted file mode 100644 index faffc4b28a58..000000000000 --- a/core/tests/BroadcastRadioTests/Android.mk +++ /dev/null @@ -1,38 +0,0 @@ -# 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_PACKAGE_NAME := BroadcastRadioTests - -LOCAL_PRIVILEGED_MODULE := true -LOCAL_CERTIFICATE := platform -LOCAL_MODULE_TAGS := tests -# TODO(b/13282254): uncomment when b/13282254 is fixed -# LOCAL_SDK_VERSION := current -LOCAL_PRIVATE_PLATFORM_APIS := true - -LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-util-axt androidx.test.rules testng - -LOCAL_JAVA_LIBRARIES := android.test.base - -LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS) -LOCAL_SRC_FILES := $(call all-java-files-under, src) - -LOCAL_DEX_PREOPT := false -LOCAL_PROGUARD_ENABLED := disabled - -include $(BUILD_PACKAGE) diff --git a/core/tests/coretests/src/android/provider/SettingsValidatorsTest.java b/core/tests/coretests/src/android/provider/SettingsValidatorsTest.java index 08f9de602684..8081e9e6c67b 100644 --- a/core/tests/coretests/src/android/provider/SettingsValidatorsTest.java +++ b/core/tests/coretests/src/android/provider/SettingsValidatorsTest.java @@ -16,6 +16,8 @@ package android.provider; +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -26,6 +28,8 @@ import android.provider.SettingsValidators.Validator; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import org.json.JSONException; +import org.json.JSONObject; import org.junit.Test; import org.junit.runner.RunWith; @@ -218,6 +222,31 @@ public class SettingsValidatorsTest { } @Test + public void testJSONObjectValidator() throws JSONException { + Validator v = SettingsValidators.JSON_OBJECT_VALIDATOR; + + assertThat(v.validate(new JSONObject().toString())).isTrue(); + assertThat(v.validate("{}")).isTrue(); + assertThat(v.validate(new JSONObject().put("foo", "bar").toString())) + .isTrue(); + assertThat(v.validate("{\"foo\": \"bar\"}")).isTrue(); + + assertThat(v.validate("random string")).isFalse(); + assertThat(v.validate("random: string")).isFalse(); + assertThat(v.validate("{random: }")).isFalse(); + } + + @Test + public void testJSONObjectValidator_onNullValue_returnsFalse() { + assertThat(SettingsValidators.JSON_OBJECT_VALIDATOR.validate(null)).isFalse(); + } + + @Test + public void testJSONObjectValidator_onEmptyString_returnsFalse() { + assertThat(SettingsValidators.JSON_OBJECT_VALIDATOR.validate("")).isFalse(); + } + + @Test public void ensureAllBackedUpSystemSettingsHaveValidators() { String offenders = getOffenders(concat(Settings.System.SETTINGS_TO_BACKUP, Settings.System.LEGACY_RESTORE_SETTINGS), Settings.System.VALIDATORS); diff --git a/core/tests/coretests/src/android/view/textclassifier/ActionsSuggestionsHelperTest.java b/core/tests/coretests/src/android/view/textclassifier/ActionsSuggestionsHelperTest.java index 67423c8402a3..db5f82a0a373 100644 --- a/core/tests/coretests/src/android/view/textclassifier/ActionsSuggestionsHelperTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/ActionsSuggestionsHelperTest.java @@ -263,6 +263,7 @@ public class ActionsSuggestionsHelperTest { "title", null, "description", + null, Intent.ACTION_VIEW, Uri.parse("http://www.android.com").toString(), null, diff --git a/core/tests/coretests/src/android/view/textclassifier/FakeContextBuilder.java b/core/tests/coretests/src/android/view/textclassifier/FakeContextBuilder.java index 2674e3796c4a..11bea0c1fe44 100644 --- a/core/tests/coretests/src/android/view/textclassifier/FakeContextBuilder.java +++ b/core/tests/coretests/src/android/view/textclassifier/FakeContextBuilder.java @@ -54,6 +54,7 @@ public final class FakeContextBuilder { private final PackageManager mPackageManager; private final ContextWrapper mContext; private final Map<String, ComponentName> mComponents = new HashMap<>(); + private final Map<String, CharSequence> mAppLabels = new HashMap<>(); private @Nullable ComponentName mAllIntentComponent; public FakeContextBuilder() { @@ -79,6 +80,14 @@ public final class FakeContextBuilder { return this; } + /** + * Sets the app label res for a specified package. + */ + public FakeContextBuilder setAppLabel(String packageName, @Nullable CharSequence appLabel) { + Preconditions.checkNotNull(packageName); + mAppLabels.put(packageName, appLabel); + return this; + } /** * Sets the component name of an activity to handle all intents. @@ -102,6 +111,11 @@ public final class FakeContextBuilder { : mAllIntentComponent; return getResolveInfo(component); }); + when(mPackageManager.getApplicationLabel(any(ApplicationInfo.class))).thenAnswer( + (Answer<CharSequence>) invocation -> { + ApplicationInfo applicationInfo = invocation.getArgument(0); + return mAppLabels.get(applicationInfo.packageName); + }); return mContext; } @@ -125,6 +139,7 @@ public final class FakeContextBuilder { info.activityInfo.name = component.getClassName(); info.activityInfo.exported = true; info.activityInfo.applicationInfo = new ApplicationInfo(); + info.activityInfo.applicationInfo.packageName = component.getPackageName(); info.activityInfo.applicationInfo.icon = 0; } return info; diff --git a/core/tests/coretests/src/android/view/textclassifier/intent/LabeledIntentTest.java b/core/tests/coretests/src/android/view/textclassifier/intent/LabeledIntentTest.java index 857408f12049..3ad26f5d5108 100644 --- a/core/tests/coretests/src/android/view/textclassifier/intent/LabeledIntentTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/intent/LabeledIntentTest.java @@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.testng.Assert.assertThrows; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.net.Uri; @@ -40,17 +41,21 @@ public final class LabeledIntentTest { private static final String TITLE_WITHOUT_ENTITY = "Map"; private static final String TITLE_WITH_ENTITY = "Map NW14D1"; private static final String DESCRIPTION = "Check the map"; + private static final String DESCRIPTION_WITH_APP_NAME = "Use %1$s to open map"; private static final Intent INTENT = new Intent(Intent.ACTION_VIEW).setDataAndNormalize(Uri.parse("http://www.android.com")); private static final int REQUEST_CODE = 42; private static final Bundle TEXT_LANGUAGES_BUNDLE = Bundle.EMPTY; + private static final String APP_LABEL = "fake"; private Context mContext; @Before public void setup() { + final ComponentName component = FakeContextBuilder.DEFAULT_COMPONENT; mContext = new FakeContextBuilder() - .setIntentComponent(Intent.ACTION_VIEW, FakeContextBuilder.DEFAULT_COMPONENT) + .setIntentComponent(Intent.ACTION_VIEW, component) + .setAppLabel(component.getPackageName(), APP_LABEL) .build(); } @@ -60,6 +65,7 @@ public final class LabeledIntentTest { TITLE_WITHOUT_ENTITY, TITLE_WITH_ENTITY, DESCRIPTION, + null, INTENT, REQUEST_CODE ); @@ -82,6 +88,7 @@ public final class LabeledIntentTest { TITLE_WITHOUT_ENTITY, null, DESCRIPTION, + null, INTENT, REQUEST_CODE ); @@ -103,6 +110,7 @@ public final class LabeledIntentTest { TITLE_WITHOUT_ENTITY, null, DESCRIPTION, + null, INTENT, REQUEST_CODE ); @@ -124,6 +132,7 @@ public final class LabeledIntentTest { TITLE_WITHOUT_ENTITY, null, DESCRIPTION, + null, INTENT, REQUEST_CODE ); @@ -148,6 +157,7 @@ public final class LabeledIntentTest { null, null, DESCRIPTION, + null, INTENT, REQUEST_CODE )); @@ -161,6 +171,7 @@ public final class LabeledIntentTest { TITLE_WITHOUT_ENTITY, null, DESCRIPTION, + null, unresolvableIntent, REQUEST_CODE); @@ -168,4 +179,22 @@ public final class LabeledIntentTest { assertThat(result).isNull(); } + + @Test + public void resolve_descriptionWithAppName() { + LabeledIntent labeledIntent = new LabeledIntent( + TITLE_WITHOUT_ENTITY, + TITLE_WITH_ENTITY, + DESCRIPTION, + DESCRIPTION_WITH_APP_NAME, + INTENT, + REQUEST_CODE + ); + + LabeledIntent.Result result = labeledIntent.resolve( + mContext, /*titleChooser*/ null, TEXT_LANGUAGES_BUNDLE); + + assertThat(result).isNotNull(); + assertThat(result.remoteAction.getContentDescription()).isEqualTo("Use fake to open map"); + } } diff --git a/core/tests/coretests/src/android/view/textclassifier/intent/TemplateClassificationIntentFactoryTest.java b/core/tests/coretests/src/android/view/textclassifier/intent/TemplateClassificationIntentFactoryTest.java index 2e97e638ba57..b9a1a8cc4e42 100644 --- a/core/tests/coretests/src/android/view/textclassifier/intent/TemplateClassificationIntentFactoryTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/intent/TemplateClassificationIntentFactoryTest.java @@ -50,6 +50,7 @@ public class TemplateClassificationIntentFactoryTest { private static final String TEXT = "text"; private static final String TITLE_WITHOUT_ENTITY = "Map"; private static final String DESCRIPTION = "Opens in Maps"; + private static final String DESCRIPTION_WITH_APP_NAME = "Use %1$s to open Map"; private static final String ACTION = Intent.ACTION_VIEW; @Mock @@ -219,6 +220,7 @@ public class TemplateClassificationIntentFactoryTest { TITLE_WITHOUT_ENTITY, null, DESCRIPTION, + DESCRIPTION_WITH_APP_NAME, ACTION, null, null, diff --git a/core/tests/coretests/src/android/view/textclassifier/intent/TemplateIntentFactoryTest.java b/core/tests/coretests/src/android/view/textclassifier/intent/TemplateIntentFactoryTest.java index 6e3de2d378b4..a33c35811276 100644 --- a/core/tests/coretests/src/android/view/textclassifier/intent/TemplateIntentFactoryTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/intent/TemplateIntentFactoryTest.java @@ -41,6 +41,7 @@ public class TemplateIntentFactoryTest { private static final String TITLE_WITHOUT_ENTITY = "Map"; private static final String TITLE_WITH_ENTITY = "Map NW14D1"; private static final String DESCRIPTION = "Check the map"; + private static final String DESCRIPTION_WITH_APP_NAME = "Use %1$s to open map"; private static final String ACTION = Intent.ACTION_VIEW; private static final String DATA = Uri.parse("http://www.android.com").toString(); private static final String TYPE = "text/html"; @@ -73,6 +74,7 @@ public class TemplateIntentFactoryTest { TITLE_WITHOUT_ENTITY, TITLE_WITH_ENTITY, DESCRIPTION, + DESCRIPTION_WITH_APP_NAME, ACTION, DATA, TYPE, @@ -91,6 +93,7 @@ public class TemplateIntentFactoryTest { assertThat(labeledIntent.titleWithoutEntity).isEqualTo(TITLE_WITHOUT_ENTITY); assertThat(labeledIntent.titleWithEntity).isEqualTo(TITLE_WITH_ENTITY); assertThat(labeledIntent.description).isEqualTo(DESCRIPTION); + assertThat(labeledIntent.descriptionWithAppName).isEqualTo(DESCRIPTION_WITH_APP_NAME); assertThat(labeledIntent.requestCode).isEqualTo(REQUEST_CODE); Intent intent = labeledIntent.intent; assertThat(intent.getAction()).isEqualTo(ACTION); @@ -109,6 +112,7 @@ public class TemplateIntentFactoryTest { TITLE_WITHOUT_ENTITY, TITLE_WITH_ENTITY, DESCRIPTION, + DESCRIPTION_WITH_APP_NAME, ACTION, "HTTp://www.android.com", TYPE, @@ -132,6 +136,7 @@ public class TemplateIntentFactoryTest { TITLE_WITHOUT_ENTITY, null, DESCRIPTION, + null, ACTION, null, null, @@ -177,6 +182,7 @@ public class TemplateIntentFactoryTest { TITLE_WITHOUT_ENTITY, TITLE_WITH_ENTITY, DESCRIPTION, + DESCRIPTION_WITH_APP_NAME, ACTION, DATA, TYPE, @@ -199,6 +205,7 @@ public class TemplateIntentFactoryTest { null, null, DESCRIPTION, + DESCRIPTION_WITH_APP_NAME, ACTION, null, null, @@ -221,6 +228,7 @@ public class TemplateIntentFactoryTest { TITLE_WITHOUT_ENTITY, TITLE_WITH_ENTITY, null, + null, ACTION, null, null, @@ -243,6 +251,7 @@ public class TemplateIntentFactoryTest { TITLE_WITHOUT_ENTITY, TITLE_WITH_ENTITY, DESCRIPTION, + DESCRIPTION_WITH_APP_NAME, null, null, null, diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderDiffTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderDiffTest.java index 460fe47966ea..b45f8d2733db 100644 --- a/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderDiffTest.java +++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderDiffTest.java @@ -26,8 +26,8 @@ import static java.util.stream.Collectors.toList; import android.platform.test.annotations.Presubmit; -import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; import org.junit.After; import org.junit.Before; @@ -210,6 +210,73 @@ public class KernelCpuThreadReaderDiffTest { assertThat(threadNames(processes2)).containsExactly("thread0"); } + @Test + public void test_nonNegativeOtherThreads() { + when(mMockReader.getProcessCpuUsage()) + .thenReturn(createProcess(new int[] {0}, new int[] {0})) + .thenReturn(createProcess(new int[] {4}, new int[] {4})) + .thenReturn(createProcess(new int[] {10}, new int[] {7})) + .thenReturn(createProcess(new int[] {20}, new int[] {15})); + KernelCpuThreadReaderDiff kernelCpuThreadReaderDiff = + new KernelCpuThreadReaderDiff(mMockReader, 5); + assertThat(kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed()).isNull(); + + ArrayList<KernelCpuThreadReader.ProcessCpuUsage> processes1 = + kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed(); + assertThat(cpuUsages(processes1)).containsExactly(Collections.singletonList(8)); + assertThat(threadNames(processes1)).containsExactly("__OTHER_THREADS"); + + ArrayList<KernelCpuThreadReader.ProcessCpuUsage> processes2 = + kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed(); + assertThat(cpuUsages(processes2)) + .containsExactly(Collections.singletonList(6), Collections.singletonList(3)); + assertThat(threadNames(processes2)).containsExactly("thread0", "__OTHER_THREADS"); + + ArrayList<KernelCpuThreadReader.ProcessCpuUsage> processes3 = + kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed(); + assertThat(cpuUsages(processes3)) + .containsExactly(Collections.singletonList(10), Collections.singletonList(8)); + assertThat(threadNames(processes3)).containsExactly("thread0", "thread1"); + } + + @Test + public void test_otherThreadsOnZeroDiff() { + when(mMockReader.getProcessCpuUsage()) + .thenReturn(createProcess(new int[] {0})) + .thenReturn(createProcess(new int[] {0})); + KernelCpuThreadReaderDiff kernelCpuThreadReaderDiff = + new KernelCpuThreadReaderDiff(mMockReader, 5); + assertThat(kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed()).isNull(); + + ArrayList<KernelCpuThreadReader.ProcessCpuUsage> processes1 = + kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed(); + assertThat(cpuUsages(processes1)).containsExactly(Collections.singletonList(0)); + assertThat(threadNames(processes1)).containsExactly("__OTHER_THREADS"); + } + + @Test + public void test_failureAndNewThread() { + when(mMockReader.getProcessCpuUsage()) + .thenReturn(createProcess(new int[] {0})) + .thenThrow(new RuntimeException()) + .thenReturn(createProcess(new int[] {1}, new int[] {10})) + .thenReturn(createProcess(new int[] {2}, new int[] {12})); + KernelCpuThreadReaderDiff kernelCpuThreadReaderDiff = + new KernelCpuThreadReaderDiff(mMockReader, 0); + assertThat(kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed()).isNull(); + + assertThrows( + RuntimeException.class, + () -> cpuUsages(kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed())); + assertThat(kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed()).isNull(); + + ArrayList<KernelCpuThreadReader.ProcessCpuUsage> processes1 = + kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed(); + assertThat(cpuUsages(processes1)) + .containsExactly(Collections.singletonList(1), Collections.singletonList(2)); + assertThat(threadNames(processes1)).containsExactly("thread0", "thread1"); + } + private ArrayList<KernelCpuThreadReader.ProcessCpuUsage> createProcess( int[]... cpuUsageMillis) { ArrayList<KernelCpuThreadReader.ThreadCpuUsage> threadCpuUsages = new ArrayList<>(); diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderTest.java index ae847c125633..c3e4014bf29a 100644 --- a/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderTest.java +++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderTest.java @@ -176,10 +176,10 @@ public class KernelCpuThreadReaderTest { frequencies, 4); assertArrayEquals( new int[]{1, 3, 1, 3}, - frequencyBucketCreator.getBucketMinFrequencies(frequencies)); + frequencyBucketCreator.bucketFrequencies(frequencies)); assertArrayEquals( new int[]{2, 2, 2, 2}, - frequencyBucketCreator.getBucketedValues(new long[]{1, 1, 1, 1, 1, 1, 1, 1})); + frequencyBucketCreator.bucketValues(new long[]{1, 1, 1, 1, 1, 1, 1, 1})); } @Test @@ -190,10 +190,10 @@ public class KernelCpuThreadReaderTest { frequencies, 4); assertArrayEquals( new int[]{1, 3, 5, 7}, - frequencyBucketCreator.getBucketMinFrequencies(frequencies)); + frequencyBucketCreator.bucketFrequencies(frequencies)); assertArrayEquals( new int[]{2, 2, 2, 2}, - frequencyBucketCreator.getBucketedValues(new long[]{1, 1, 1, 1, 1, 1, 1, 1})); + frequencyBucketCreator.bucketValues(new long[]{1, 1, 1, 1, 1, 1, 1, 1})); } @Test @@ -204,10 +204,10 @@ public class KernelCpuThreadReaderTest { frequencies, 4); assertArrayEquals( new int[]{1, 3, 1, 2}, - frequencyBucketCreator.getBucketMinFrequencies(frequencies)); + frequencyBucketCreator.bucketFrequencies(frequencies)); assertArrayEquals( new int[]{2, 3, 1, 2}, - frequencyBucketCreator.getBucketedValues(new long[]{1, 1, 1, 1, 1, 1, 1, 1})); + frequencyBucketCreator.bucketValues(new long[]{1, 1, 1, 1, 1, 1, 1, 1})); } @Test @@ -218,10 +218,10 @@ public class KernelCpuThreadReaderTest { frequencies, 4); assertArrayEquals( new int[]{1, 2, 1, 3}, - frequencyBucketCreator.getBucketMinFrequencies(frequencies)); + frequencyBucketCreator.bucketFrequencies(frequencies)); assertArrayEquals( new int[]{1, 2, 2, 3}, - frequencyBucketCreator.getBucketedValues(new long[]{1, 1, 1, 1, 1, 1, 1, 1})); + frequencyBucketCreator.bucketValues(new long[]{1, 1, 1, 1, 1, 1, 1, 1})); } @Test @@ -232,10 +232,10 @@ public class KernelCpuThreadReaderTest { frequencies, 8); assertArrayEquals( new int[]{1, 2, 3, 4, 1, 2, 3, 4}, - frequencyBucketCreator.getBucketMinFrequencies(frequencies)); + frequencyBucketCreator.bucketFrequencies(frequencies)); assertArrayEquals( new int[]{1, 1, 1, 1, 1, 1, 1, 1}, - frequencyBucketCreator.getBucketedValues(new long[]{1, 1, 1, 1, 1, 1, 1, 1})); + frequencyBucketCreator.bucketValues(new long[]{1, 1, 1, 1, 1, 1, 1, 1})); } @Test @@ -246,10 +246,10 @@ public class KernelCpuThreadReaderTest { frequencies, 8); assertArrayEquals( new int[]{1, 3, 5, 7, 1, 2, 3}, - frequencyBucketCreator.getBucketMinFrequencies(frequencies)); + frequencyBucketCreator.bucketFrequencies(frequencies)); assertArrayEquals( new int[]{2, 2, 2, 3, 1, 1, 1}, - frequencyBucketCreator.getBucketedValues( + frequencyBucketCreator.bucketValues( new long[]{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1})); } @@ -261,39 +261,37 @@ public class KernelCpuThreadReaderTest { frequencies, 1); assertArrayEquals( new int[]{1}, - frequencyBucketCreator.getBucketMinFrequencies(frequencies)); + frequencyBucketCreator.bucketFrequencies(frequencies)); assertArrayEquals( new int[]{8}, - frequencyBucketCreator.getBucketedValues( + frequencyBucketCreator.bucketValues( new long[]{1, 1, 1, 1, 1, 1, 1, 1})); } @Test - public void testGetBigFrequenciesStartIndex_simple() { - assertEquals( - 3, KernelCpuThreadReader.FrequencyBucketCreator.getBigFrequenciesStartIndex( - new long[]{1, 2, 3, 1, 2, 3})); - } - - @Test - public void testGetBigFrequenciesStartIndex_moreLittle() { - assertEquals( - 4, KernelCpuThreadReader.FrequencyBucketCreator.getBigFrequenciesStartIndex( - new long[]{1, 2, 3, 4, 1, 2})); - } - - @Test - public void testGetBigFrequenciesStartIndex_moreBig() { - assertEquals( - 2, KernelCpuThreadReader.FrequencyBucketCreator.getBigFrequenciesStartIndex( - new long[]{1, 2, 1, 2, 3, 4})); + public void testBucketSetup_threeClusters() { + long[] frequencies = {1, 2, 3, 4, 2, 3, 4, 5, 3, 4, 5, 6}; + KernelCpuThreadReader.FrequencyBucketCreator frequencyBucketCreator = + new KernelCpuThreadReader.FrequencyBucketCreator(frequencies, 6); + assertArrayEquals( + new int[] {1, 3, 2, 4, 3, 5}, + frequencyBucketCreator.bucketFrequencies(frequencies)); + assertArrayEquals( + new int[] {2, 2, 2, 2, 2, 2}, + frequencyBucketCreator.bucketValues( + new long[] {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1})); } @Test - public void testGetBigFrequenciesStartIndex_noBig() { - assertEquals( - 4, KernelCpuThreadReader.FrequencyBucketCreator.getBigFrequenciesStartIndex( - new long[]{1, 2, 3, 4})); + public void testBucketSetup_moreClustersThanBuckets() { + long[] frequencies = {1, 1, 1, 1, 1, 1, 1, 1}; + KernelCpuThreadReader.FrequencyBucketCreator frequencyBucketCreator = + new KernelCpuThreadReader.FrequencyBucketCreator(frequencies, 4); + assertArrayEquals( + new int[] {1, 1, 1, 1}, frequencyBucketCreator.bucketFrequencies(frequencies)); + assertArrayEquals( + new int[] {1, 1, 1, 5}, + frequencyBucketCreator.bucketValues(new long[] {1, 1, 1, 1, 1, 1, 1, 1})); } @Test diff --git a/core/tests/featureflagtests/Android.bp b/core/tests/featureflagtests/Android.bp new file mode 100644 index 000000000000..8730b7012c4a --- /dev/null +++ b/core/tests/featureflagtests/Android.bp @@ -0,0 +1,19 @@ +android_test { + name: "FrameworksCoreFeatureFlagTests", + // We only want this apk build for tests. + // Include all test java files. + srcs: ["src/**/*.java"], + dxflags: ["--core-library"], + static_libs: [ + "android-common", + "frameworks-core-util-lib", + "androidx.test.rules", + ], + libs: [ + "android.test.runner", + "android.test.base", + ], + platform_apis: true, + certificate: "platform", + test_suites: ["device-tests"], +} diff --git a/core/tests/featureflagtests/Android.mk b/core/tests/featureflagtests/Android.mk deleted file mode 100644 index ce7cb181b53a..000000000000 --- a/core/tests/featureflagtests/Android.mk +++ /dev/null @@ -1,20 +0,0 @@ -LOCAL_PATH:= $(call my-dir) -include $(CLEAR_VARS) - -# We only want this apk build for tests. -LOCAL_MODULE_TAGS := tests - -# Include all test java files. -LOCAL_SRC_FILES := \ - $(call all-java-files-under, src) - -LOCAL_DX_FLAGS := --core-library -LOCAL_STATIC_JAVA_LIBRARIES := android-common frameworks-core-util-lib androidx.test.rules -LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base -LOCAL_PACKAGE_NAME := FrameworksCoreFeatureFlagTests -LOCAL_PRIVATE_PLATFORM_APIS := true - -LOCAL_CERTIFICATE := platform -LOCAL_COMPATIBILITY_SUITE := device-tests - -include $(BUILD_PACKAGE) diff --git a/core/tests/overlaytests/device/test-apps/AppOverlayOne/Android.mk b/core/tests/overlaytests/device/test-apps/AppOverlayOne/Android.mk index 97a3d0078e26..fa15241b9cc5 100644 --- a/core/tests/overlaytests/device/test-apps/AppOverlayOne/Android.mk +++ b/core/tests/overlaytests/device/test-apps/AppOverlayOne/Android.mk @@ -19,7 +19,6 @@ LOCAL_MODULE_TAGS := tests LOCAL_PACKAGE_NAME := OverlayDeviceTests_AppOverlayOne LOCAL_SDK_VERSION := current LOCAL_COMPATIBILITY_SUITE := device-tests -LOCAL_CERTIFICATE := platform LOCAL_USE_AAPT2 := true LOCAL_AAPT_FLAGS := --no-resource-removal include $(BUILD_PACKAGE) diff --git a/core/tests/overlaytests/device/test-apps/AppOverlayOne/AndroidManifest.xml b/core/tests/overlaytests/device/test-apps/AppOverlayOne/AndroidManifest.xml index 8ac6953b44e5..7d2840886683 100644 --- a/core/tests/overlaytests/device/test-apps/AppOverlayOne/AndroidManifest.xml +++ b/core/tests/overlaytests/device/test-apps/AppOverlayOne/AndroidManifest.xml @@ -19,5 +19,5 @@ android:versionCode="1" android:versionName="1.0"> <application android:hasCode="false" /> - <overlay android:targetPackage="com.android.overlaytest" android:priority="1" /> + <overlay android:targetPackage="com.android.overlaytest" /> </manifest> diff --git a/core/tests/overlaytests/device/test-apps/AppOverlayTwo/Android.mk b/core/tests/overlaytests/device/test-apps/AppOverlayTwo/Android.mk index a3470255997d..ada9b3cb625d 100644 --- a/core/tests/overlaytests/device/test-apps/AppOverlayTwo/Android.mk +++ b/core/tests/overlaytests/device/test-apps/AppOverlayTwo/Android.mk @@ -19,7 +19,6 @@ LOCAL_MODULE_TAGS := tests LOCAL_PACKAGE_NAME := OverlayDeviceTests_AppOverlayTwo LOCAL_SDK_VERSION := current LOCAL_COMPATIBILITY_SUITE := device-tests -LOCAL_CERTIFICATE := platform LOCAL_USE_AAPT2 := true LOCAL_AAPT_FLAGS := --no-resource-removal include $(BUILD_PACKAGE) diff --git a/core/tests/overlaytests/device/test-apps/AppOverlayTwo/AndroidManifest.xml b/core/tests/overlaytests/device/test-apps/AppOverlayTwo/AndroidManifest.xml index f3c39ccbb301..6e75a350204d 100644 --- a/core/tests/overlaytests/device/test-apps/AppOverlayTwo/AndroidManifest.xml +++ b/core/tests/overlaytests/device/test-apps/AppOverlayTwo/AndroidManifest.xml @@ -19,5 +19,5 @@ android:versionCode="1" android:versionName="1.0"> <application android:hasCode="false" /> - <overlay android:targetPackage="com.android.overlaytest" android:priority="2" /> + <overlay android:targetPackage="com.android.overlaytest" /> </manifest> diff --git a/core/tests/overlaytests/host/Android.mk b/core/tests/overlaytests/host/Android.mk index b48a46bbda9a..e7348d52adfe 100644 --- a/core/tests/overlaytests/host/Android.mk +++ b/core/tests/overlaytests/host/Android.mk @@ -21,7 +21,7 @@ LOCAL_MODULE := OverlayHostTests LOCAL_JAVA_LIBRARIES := tradefed LOCAL_COMPATIBILITY_SUITE := general-tests LOCAL_TARGET_REQUIRED_MODULES := \ - OverlayHostTests_BadSignatureOverlay \ + OverlayHostTests_NonPlatformSignatureOverlay \ OverlayHostTests_PlatformSignatureStaticOverlay \ OverlayHostTests_PlatformSignatureOverlay \ OverlayHostTests_UpdateOverlay \ diff --git a/core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java b/core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java index f9672d27e129..99b6421d2bc7 100644 --- a/core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java +++ b/core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java @@ -67,10 +67,10 @@ public class InstallOverlayTests extends BaseHostJUnit4Test { } @Test - public void failToInstallNonPlatformSignedOverlay() throws Exception { + public void failToInstallNonPlatformSignedOverlayTargetPreQ() throws Exception { try { - installPackage("OverlayHostTests_BadSignatureOverlay.apk"); - fail("installed a non-platform signed overlay"); + installPackage("OverlayHostTests_NonPlatformSignatureOverlay.apk"); + fail("installed a non-platform signed overlay with targetSdkVersion < Q"); } catch (Exception e) { // Expected. } @@ -155,9 +155,17 @@ public class InstallOverlayTests extends BaseHostJUnit4Test { } } + @Test + public void instantAppsNotVisibleToOMS() throws Exception { + installInstantPackage("OverlayHostTests_AppOverlayV1.apk"); + assertFalse(overlayManagerContainsPackage(APP_OVERLAY_PACKAGE_NAME)); + installConvertExistingInstantPackageToFull(APP_OVERLAY_PACKAGE_NAME); + assertTrue(overlayManagerContainsPackage(APP_OVERLAY_PACKAGE_NAME)); + } + private void delay() { try { - Thread.sleep(100); + Thread.sleep(1000); } catch (InterruptedException e) { } } @@ -167,6 +175,15 @@ public class InstallOverlayTests extends BaseHostJUnit4Test { delay(); } + private void installInstantPackage(String pkg) throws Exception { + super.installPackage(pkg, "--instant"); + delay(); + } + + private void installConvertExistingInstantPackageToFull(String pkg) throws Exception { + getDevice().executeShellCommand("cmd package install-existing --wait --full " + pkg); + } + private void setPackageEnabled(String pkg, boolean enabled) throws Exception { getDevice().executeShellCommand("cmd package " + (enabled ? "enable " : "disable ") + pkg); delay(); diff --git a/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk b/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk index 3d2410dd77a5..cc7704b0ce98 100644 --- a/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk +++ b/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk @@ -18,7 +18,7 @@ my_package_prefix := com.android.server.om.hosttest.signature_overlay include $(CLEAR_VARS) LOCAL_MODULE_TAGS := tests -LOCAL_PACKAGE_NAME := OverlayHostTests_BadSignatureOverlay +LOCAL_PACKAGE_NAME := OverlayHostTests_NonPlatformSignatureOverlay LOCAL_SDK_VERSION := current LOCAL_COMPATIBILITY_SUITE := general-tests LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_bad diff --git a/core/tests/overlaytests/host/test-apps/SignatureOverlay/AndroidManifest.xml b/core/tests/overlaytests/host/test-apps/SignatureOverlay/AndroidManifest.xml index 26b3875caab6..67592f834bb2 100644 --- a/core/tests/overlaytests/host/test-apps/SignatureOverlay/AndroidManifest.xml +++ b/core/tests/overlaytests/host/test-apps/SignatureOverlay/AndroidManifest.xml @@ -16,6 +16,7 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.server.om.hosttest.signature_overlay"> + <uses-sdk android:targetSdkVersion="28" /> <application android:hasCode="false" /> <overlay android:targetPackage="android" /> </manifest> diff --git a/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk b/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk index c7b2dd1ac8f9..f8607f44bda6 100644 --- a/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk +++ b/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk @@ -58,7 +58,6 @@ LOCAL_MODULE_TAGS := tests LOCAL_PACKAGE_NAME := OverlayHostTests_AppOverlayV1 LOCAL_SDK_VERSION := current LOCAL_COMPATIBILITY_SUITE := general-tests -LOCAL_CERTIFICATE := platform LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v1 LOCAL_AAPT_FLAGS += --version-code 1 --version-name v1 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/app/v1/res @@ -70,7 +69,6 @@ LOCAL_MODULE_TAGS := tests LOCAL_PACKAGE_NAME := OverlayHostTests_AppOverlayV2 LOCAL_SDK_VERSION := current LOCAL_COMPATIBILITY_SUITE := general-tests -LOCAL_CERTIFICATE := platform LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v2 LOCAL_AAPT_FLAGS += --version-code 2 --version-name v2 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/app/v2/res diff --git a/core/tests/overlaytests/host/test-apps/UpdateOverlay/app/v2/AndroidManifest.xml b/core/tests/overlaytests/host/test-apps/UpdateOverlay/app/v2/AndroidManifest.xml index f1a39817af86..b6ff0c3c6725 100644 --- a/core/tests/overlaytests/host/test-apps/UpdateOverlay/app/v2/AndroidManifest.xml +++ b/core/tests/overlaytests/host/test-apps/UpdateOverlay/app/v2/AndroidManifest.xml @@ -17,6 +17,5 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.server.om.hosttest.app_overlay"> <application android:hasCode="false" /> - <overlay android:targetPackage="com.android.server.om.hosttest.update_overlay_test" - android:category="android.theme" /> + <overlay android:targetPackage="com.android.server.om.hosttest.update_overlay_test" /> </manifest> diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml index 626bbf0001dd..072beae8baf7 100644 --- a/data/fonts/fonts.xml +++ b/data/fonts/fonts.xml @@ -100,36 +100,6 @@ <font weight="400" style="normal">CarroisGothicSC-Regular.ttf</font> </family> - <family name="arbutus-slab"> - <font weight="400" style="normal">ArbutusSlab-Regular.ttf</font> - </family> - - <family name="arvo"> - <font weight="400" style="normal">Arvo-Regular.ttf</font> - <font weight="400" style="italic">Arvo-Italic.ttf</font> - <font weight="700" style="normal">Arvo-Bold.ttf</font> - <font weight="700" style="italic">Arvo-BoldItalic.ttf</font> - </family> - <alias name="arvo-bold" to="arvo" weight="700" /> - - <family name="lato"> - <font weight="400" style="normal">Lato-Regular.ttf</font> - <font weight="400" style="italic">Lato-Italic.ttf</font> - <font weight="700" style="normal">Lato-Bold.ttf</font> - <font weight="700" style="italic">Lato-BoldItalic.ttf</font> - </family> - <alias name="lato-bold" to="lato" weight="700" /> - - <family name="rubik"> - <font weight="400" style="normal">Rubik-Regular.ttf</font> - <font weight="400" style="italic">Rubik-Italic.ttf</font> - <font weight="500" style="normal">Rubik-Medium.ttf</font> - <font weight="500" style="italic">Rubik-MediumItalic.ttf</font> - <font weight="700" style="normal">Rubik-Bold.ttf</font> - <font weight="700" style="italic">Rubik-BoldItalic.ttf</font> - </family> - <alias name="rubik-medium" to="rubik" weight="500" /> - <family name="source-sans-pro"> <font weight="400" style="normal">SourceSansPro-Regular.ttf</font> <font weight="400" style="italic">SourceSansPro-Italic.ttf</font> diff --git a/libs/androidfw/AssetManager.cpp b/libs/androidfw/AssetManager.cpp index 365be10f597f..21609d30e92c 100644 --- a/libs/androidfw/AssetManager.cpp +++ b/libs/androidfw/AssetManager.cpp @@ -75,6 +75,7 @@ const char* AssetManager::IDMAP_BIN = "/system/bin/idmap"; const char* AssetManager::VENDOR_OVERLAY_DIR = "/vendor/overlay"; const char* AssetManager::PRODUCT_OVERLAY_DIR = "/product/overlay"; const char* AssetManager::PRODUCT_SERVICES_OVERLAY_DIR = "/product_services/overlay"; +const char* AssetManager::ODM_OVERLAY_DIR = "/odm/overlay"; const char* AssetManager::OVERLAY_THEME_DIR_PROPERTY = "ro.boot.vendor.overlay.theme"; const char* AssetManager::TARGET_PACKAGE_NAME = "android"; const char* AssetManager::TARGET_APK_PATH = "/system/framework/framework-res.apk"; diff --git a/libs/androidfw/CursorWindow.cpp b/libs/androidfw/CursorWindow.cpp index a99e77f8dbb9..e1067fcd4d3d 100644 --- a/libs/androidfw/CursorWindow.cpp +++ b/libs/androidfw/CursorWindow.cpp @@ -49,15 +49,21 @@ status_t CursorWindow::create(const String8& name, size_t size, CursorWindow** o int ashmemFd = ashmem_create_region(ashmemName.string(), size); if (ashmemFd < 0) { result = -errno; + ALOGE("CursorWindow: ashmem_create_region() failed: errno=%d.", errno); } else { result = ashmem_set_prot_region(ashmemFd, PROT_READ | PROT_WRITE); - if (result >= 0) { + if (result < 0) { + ALOGE("CursorWindow: ashmem_set_prot_region() failed: errno=%d",errno); + } else { void* data = ::mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, ashmemFd, 0); if (data == MAP_FAILED) { result = -errno; + ALOGE("CursorWindow: mmap() failed: errno=%d.", errno); } else { result = ashmem_set_prot_region(ashmemFd, PROT_READ); - if (result >= 0) { + if (result < 0) { + ALOGE("CursorWindow: ashmem_set_prot_region() failed: errno=%d.", errno); + } else { CursorWindow* window = new CursorWindow(name, ashmemFd, data, size, false /*readOnly*/); result = window->clear(); @@ -86,26 +92,34 @@ status_t CursorWindow::createFromParcel(Parcel* parcel, CursorWindow** outCursor String8 name = parcel->readString8(); status_t result; + int actualSize; int ashmemFd = parcel->readFileDescriptor(); if (ashmemFd == int(BAD_TYPE)) { result = BAD_TYPE; + ALOGE("CursorWindow: readFileDescriptor() failed"); } else { ssize_t size = ashmem_get_size_region(ashmemFd); if (size < 0) { result = UNKNOWN_ERROR; + ALOGE("CursorWindow: ashmem_get_size_region() failed: errno=%d.", errno); } else { int dupAshmemFd = ::fcntl(ashmemFd, F_DUPFD_CLOEXEC, 0); if (dupAshmemFd < 0) { result = -errno; + ALOGE("CursorWindow: fcntl() failed: errno=%d.", errno); } else { // the size of the ashmem descriptor can be modified between ashmem_get_size_region // call and mmap, so we'll check again immediately after memory is mapped void* data = ::mmap(NULL, size, PROT_READ, MAP_SHARED, dupAshmemFd, 0); if (data == MAP_FAILED) { result = -errno; - } else if (ashmem_get_size_region(dupAshmemFd) != size) { + ALOGE("CursorWindow: mmap() failed: errno=%d.", errno); + } else if ((actualSize = ashmem_get_size_region(dupAshmemFd)) != size) { ::munmap(data, size); result = BAD_VALUE; + ALOGE("CursorWindow: ashmem_get_size_region() returned %d, expected %d" + " errno=%d", + actualSize, (int) size, errno); } else { CursorWindow* window = new CursorWindow(name, dupAshmemFd, data, size, true /*readOnly*/); diff --git a/libs/androidfw/OWNERS b/libs/androidfw/OWNERS index f1903a5a54a7..87b14673ea2c 100644 --- a/libs/androidfw/OWNERS +++ b/libs/androidfw/OWNERS @@ -1,3 +1,5 @@ set noparent toddke@google.com -rtmitchell@google.com
\ No newline at end of file +rtmitchell@google.com + +per-file CursorWindow.cpp=omakoto@google.com diff --git a/libs/androidfw/include/androidfw/AssetManager.h b/libs/androidfw/include/androidfw/AssetManager.h index e22e2d239a55..a015eabc200c 100644 --- a/libs/androidfw/include/androidfw/AssetManager.h +++ b/libs/androidfw/include/androidfw/AssetManager.h @@ -62,6 +62,7 @@ public: static const char* VENDOR_OVERLAY_DIR; static const char* PRODUCT_OVERLAY_DIR; static const char* PRODUCT_SERVICES_OVERLAY_DIR; + static const char* ODM_OVERLAY_DIR; /* * If OVERLAY_THEME_DIR_PROPERTY is set, search for runtime resource overlay * APKs in VENDOR_OVERLAY_DIR/<value of OVERLAY_THEME_DIR_PROPERTY> in diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java index bf1063d0907f..55f1911119f4 100644 --- a/media/java/android/media/AudioAttributes.java +++ b/media/java/android/media/AudioAttributes.java @@ -388,9 +388,10 @@ public final class AudioAttributes implements Parcelable { */ public static final int FLAG_NO_SYSTEM_CAPTURE = 0x1 << 12; - private final static int FLAG_ALL = FLAG_AUDIBILITY_ENFORCED | FLAG_SECURE | FLAG_SCO | - FLAG_BEACON | FLAG_HW_AV_SYNC | FLAG_HW_HOTWORD | FLAG_BYPASS_INTERRUPTION_POLICY | - FLAG_BYPASS_MUTE | FLAG_LOW_LATENCY | FLAG_DEEP_BUFFER | FLAG_MUTE_HAPTIC; + private static final int FLAG_ALL = FLAG_AUDIBILITY_ENFORCED | FLAG_SECURE | FLAG_SCO + | FLAG_BEACON | FLAG_HW_AV_SYNC | FLAG_HW_HOTWORD | FLAG_BYPASS_INTERRUPTION_POLICY + | FLAG_BYPASS_MUTE | FLAG_LOW_LATENCY | FLAG_DEEP_BUFFER | FLAG_NO_MEDIA_PROJECTION + | FLAG_MUTE_HAPTIC | FLAG_NO_SYSTEM_CAPTURE; private final static int FLAG_ALL_PUBLIC = FLAG_AUDIBILITY_ENFORCED | FLAG_HW_AV_SYNC | FLAG_LOW_LATENCY; @@ -557,7 +558,7 @@ public final class AudioAttributes implements Parcelable { private int mContentType = CONTENT_TYPE_UNKNOWN; private int mSource = MediaRecorder.AudioSource.AUDIO_SOURCE_INVALID; private int mFlags = 0x0; - private boolean mMuteHapticChannels = true; + private boolean mMuteHapticChannels = false; private HashSet<String> mTags = new HashSet<String>(); private Bundle mBundle; @@ -888,7 +889,7 @@ public final class AudioAttributes implements Parcelable { /** * Specifying if haptic should be muted or not when playing audio-haptic coupled data. - * By default, haptic channels are disabled. + * By default, haptic channels are enabled. * @param muted true to force muting haptic channels. * @return the same Builder instance. */ diff --git a/media/java/android/media/AudioPlaybackConfiguration.java b/media/java/android/media/AudioPlaybackConfiguration.java index 5d12c3cd251d..b2ebfa934178 100644 --- a/media/java/android/media/AudioPlaybackConfiguration.java +++ b/media/java/android/media/AudioPlaybackConfiguration.java @@ -43,8 +43,6 @@ public final class AudioPlaybackConfiguration implements Parcelable { /** @hide */ public static final int PLAYER_PIID_INVALID = -1; /** @hide */ - public static final int PLAYER_PIID_UNASSIGNED = 0; - /** @hide */ public static final int PLAYER_UPID_INVALID = -1; // information about the implementation diff --git a/media/java/android/media/PlayerBase.java b/media/java/android/media/PlayerBase.java index 50caf733da9d..ee8f1b3eec77 100644 --- a/media/java/android/media/PlayerBase.java +++ b/media/java/android/media/PlayerBase.java @@ -50,6 +50,10 @@ public abstract class PlayerBase { private static final boolean DEBUG = DEBUG_APP_OPS || false; private static IAudioService sService; //lazy initialization, use getService() + /** if true, only use OP_PLAY_AUDIO monitoring for logging, and rely on muting to happen + * in AudioFlinger */ + private static final boolean USE_AUDIOFLINGER_MUTING_FOR_OP = true; + // parameters of the player that affect AppOps protected AudioAttributes mAttributes; @@ -67,13 +71,13 @@ public abstract class PlayerBase { // for AppOps private @Nullable IAppOpsService mAppOps; - private IAppOpsCallback mAppOpsCallback; + private @Nullable IAppOpsCallback mAppOpsCallback; @GuardedBy("mLock") private boolean mHasAppOpsPlayAudio = true; private final int mImplType; // uniquely identifies the Player Interface throughout the system (P I Id) - private int mPlayerIId = AudioPlaybackConfiguration.PLAYER_PIID_UNASSIGNED; + private int mPlayerIId = AudioPlaybackConfiguration.PLAYER_PIID_INVALID; @GuardedBy("mLock") private int mState; @@ -104,27 +108,27 @@ public abstract class PlayerBase { * Call from derived class when instantiation / initialization is successful */ protected void baseRegisterPlayer() { - int newPiid = AudioPlaybackConfiguration.PLAYER_PIID_INVALID; - IBinder b = ServiceManager.getService(Context.APP_OPS_SERVICE); - mAppOps = IAppOpsService.Stub.asInterface(b); - // initialize mHasAppOpsPlayAudio - updateAppOpsPlayAudio(); - // register a callback to monitor whether the OP_PLAY_AUDIO is still allowed - mAppOpsCallback = new IAppOpsCallbackWrapper(this); - try { - mAppOps.startWatchingMode(AppOpsManager.OP_PLAY_AUDIO, - ActivityThread.currentPackageName(), mAppOpsCallback); - } catch (RemoteException e) { - Log.e(TAG, "Error registering appOps callback", e); - mHasAppOpsPlayAudio = false; + if (!USE_AUDIOFLINGER_MUTING_FOR_OP) { + IBinder b = ServiceManager.getService(Context.APP_OPS_SERVICE); + mAppOps = IAppOpsService.Stub.asInterface(b); + // initialize mHasAppOpsPlayAudio + updateAppOpsPlayAudio(); + // register a callback to monitor whether the OP_PLAY_AUDIO is still allowed + mAppOpsCallback = new IAppOpsCallbackWrapper(this); + try { + mAppOps.startWatchingMode(AppOpsManager.OP_PLAY_AUDIO, + ActivityThread.currentPackageName(), mAppOpsCallback); + } catch (RemoteException e) { + Log.e(TAG, "Error registering appOps callback", e); + mHasAppOpsPlayAudio = false; + } } try { - newPiid = getService().trackPlayer( + mPlayerIId = getService().trackPlayer( new PlayerIdCard(mImplType, mAttributes, new IPlayerWrapper(this))); } catch (RemoteException e) { Log.e(TAG, "Error talking to audio service, player will not be tracked", e); } - mPlayerIId = newPiid; } /** @@ -284,6 +288,9 @@ public abstract class PlayerBase { * Must be called synchronized on mLock. */ void updateAppOpsPlayAudio_sync(boolean attributesChanged) { + if (USE_AUDIOFLINGER_MUTING_FOR_OP) { + return; + } boolean oldHasAppOpsPlayAudio = mHasAppOpsPlayAudio; try { int mode = AppOpsManager.MODE_IGNORED; @@ -333,6 +340,9 @@ public abstract class PlayerBase { * @return */ boolean isRestricted_sync() { + if (USE_AUDIOFLINGER_MUTING_FOR_OP) { + return false; + } // check app ops if (mHasAppOpsPlayAudio) { return false; diff --git a/media/jni/Android.bp b/media/jni/Android.bp index 65b58ab6bfef..5c3d7805b6bd 100644 --- a/media/jni/Android.bp +++ b/media/jni/Android.bp @@ -129,6 +129,7 @@ cc_library_shared { // NDK or LLNDK or NDK-compliant "libandroid", "libbinder_ndk", + "libcgrouprc", "libmediandk", "libmediametrics", "libnativehelper_compat_libc++", diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt index 1f2480ba0b47..177f2b8ee491 100644 --- a/native/android/libandroid.map.txt +++ b/native/android/libandroid.map.txt @@ -248,16 +248,21 @@ LIBANDROID { ASystemFontIterator_open; # introduced=29 ASystemFontIterator_close; # introduced=29 ASystemFontIterator_next; # introduced=29 - ASystemFont_close; # introduced=29 - ASystemFont_getFontFilePath; # introduced=29 - ASystemFont_getWeight; # introduced=29 - ASystemFont_isItalic; # introduced=29 - ASystemFont_getLocale; # introduced=29 - ASystemFont_getCollectionIndex; # introduced=29 - ASystemFont_getAxisCount; # introduced=29 - ASystemFont_getAxisTag; # introduced=29 - ASystemFont_getAxisValue; # introduced=29 - ASystemFont_matchFamilyStyleCharacter; # introduced=29 + AFont_close; # introduced=29 + AFont_getFontFilePath; # introduced=29 + AFont_getWeight; # introduced=29 + AFont_isItalic; # introduced=29 + AFont_getLocale; # introduced=29 + AFont_getCollectionIndex; # introduced=29 + AFont_getAxisCount; # introduced=29 + AFont_getAxisTag; # introduced=29 + AFont_getAxisValue; # introduced=29 + AFontMatcher_create; # introduced=29 + AFontMatcher_destroy; # introduced=29 + AFontMatcher_setStyle; # introduced=29 + AFontMatcher_setLocales; # introduced=29 + AFontMatcher_setFamilyVariant; # introduced=29 + AFontMatcher_match; # introduced=29 ATrace_beginSection; # introduced=23 ATrace_endSection; # introduced=23 ATrace_isEnabled; # introduced=23 diff --git a/native/android/system_fonts.cpp b/native/android/system_fonts.cpp index 4d3d1d66ffda..302cbd11da4b 100644 --- a/native/android/system_fonts.cpp +++ b/native/android/system_fonts.cpp @@ -16,6 +16,8 @@ #include <jni.h> +#include <android/font.h> +#include <android/font_matcher.h> #include <android/system_fonts.h> #include <memory> @@ -53,7 +55,7 @@ struct ASystemFontIterator { XmlDocUniquePtr mCustomizationXmlDoc; }; -struct ASystemFont { +struct AFont { std::string mFilePath; std::unique_ptr<std::string> mLocale; uint16_t mWeight; @@ -62,6 +64,19 @@ struct ASystemFont { std::vector<std::pair<uint32_t, float>> mAxes; }; +struct AFontMatcher { + minikin::FontStyle mFontStyle; + uint32_t mLocaleListId = 0; // 0 is reserved for empty locale ID. + bool mFamilyVariant = AFAMILY_VARIANT_DEFAULT; +}; + +static_assert(static_cast<uint32_t>(AFAMILY_VARIANT_DEFAULT) == + static_cast<uint32_t>(minikin::FamilyVariant::DEFAULT)); +static_assert(static_cast<uint32_t>(AFAMILY_VARIANT_COMPACT) == + static_cast<uint32_t>(minikin::FamilyVariant::COMPACT)); +static_assert(static_cast<uint32_t>(AFAMILY_VARIANT_ELEGANT) == + static_cast<uint32_t>(minikin::FamilyVariant::ELEGANT)); + namespace { std::string xmlTrim(const std::string& in) { @@ -101,7 +116,7 @@ xmlNode* nextSibling(xmlNode* node, const xmlChar* tag) { return nullptr; } -void copyFont(const XmlDocUniquePtr& xmlDoc, xmlNode* fontNode, ASystemFont* out, +void copyFont(const XmlDocUniquePtr& xmlDoc, xmlNode* fontNode, AFont* out, const std::string& pathPrefix) { const xmlChar* LOCALE_ATTR_NAME = BAD_CAST("lang"); XmlCharUniquePtr filePathStr( @@ -197,24 +212,48 @@ void ASystemFontIterator_close(ASystemFontIterator* ite) { delete ite; } -ASystemFont* ASystemFont_matchFamilyStyleCharacter( - const char* _Nonnull familyName, +AFontMatcher* _Nonnull AFontMatcher_create() { + return new AFontMatcher(); +} + +void AFontMatcher_destroy(AFontMatcher* matcher) { + delete matcher; +} + +void AFontMatcher_setStyle( + AFontMatcher* _Nonnull matcher, uint16_t weight, - bool italic, - const char* _Nonnull languageTags, + bool italic) { + matcher->mFontStyle = minikin::FontStyle( + weight, static_cast<minikin::FontStyle::Slant>(italic)); +} + +void AFontMatcher_setLocales( + AFontMatcher* _Nonnull matcher, + const char* _Nonnull languageTags) { + matcher->mLocaleListId = minikin::registerLocaleList(languageTags); +} + +void AFontMatcher_setFamilyVariant(AFontMatcher* _Nonnull matcher, uint32_t familyVariant) { + matcher->mFamilyVariant = familyVariant; +} + +AFont* _Nonnull AFontMatcher_match( + const AFontMatcher* _Nonnull matcher, + const char* _Nonnull familyName, const uint16_t* _Nonnull text, - uint32_t textLength, + const uint32_t textLength, uint32_t* _Nullable runLength) { std::shared_ptr<minikin::FontCollection> fc = minikin::SystemFonts::findFontCollection(familyName); - std::vector<minikin::FontCollection::Run> runs = - fc->itemize(minikin::U16StringPiece(text, textLength), - minikin::FontStyle(weight, static_cast<minikin::FontStyle::Slant>(italic)), - minikin::registerLocaleList(languageTags), - minikin::FamilyVariant::DEFAULT); + std::vector<minikin::FontCollection::Run> runs = fc->itemize( + minikin::U16StringPiece(text, textLength), + matcher->mFontStyle, + matcher->mLocaleListId, + static_cast<minikin::FamilyVariant>(matcher->mFamilyVariant)); const minikin::Font* font = runs[0].fakedFont.font; - std::unique_ptr<ASystemFont> result = std::make_unique<ASystemFont>(); + std::unique_ptr<AFont> result = std::make_unique<AFont>(); const android::MinikinFontSkia* minikinFontSkia = reinterpret_cast<android::MinikinFontSkia*>(font->typeface().get()); result->mFilePath = minikinFontSkia->getFilePath(); @@ -253,7 +292,7 @@ xmlNode* findNextFontNode(const XmlDocUniquePtr& xmlDoc, xmlNode* fontNode) { } } -ASystemFont* ASystemFontIterator_next(ASystemFontIterator* ite) { +AFont* ASystemFontIterator_next(ASystemFontIterator* ite) { LOG_ALWAYS_FATAL_IF(ite == nullptr, "nullptr has passed as iterator argument"); if (ite->mXmlDoc) { ite->mFontNode = findNextFontNode(ite->mXmlDoc, ite->mFontNode); @@ -262,7 +301,7 @@ ASystemFont* ASystemFontIterator_next(ASystemFontIterator* ite) { ite->mXmlDoc.reset(); ite->mFontNode = nullptr; } else { - std::unique_ptr<ASystemFont> font = std::make_unique<ASystemFont>(); + std::unique_ptr<AFont> font = std::make_unique<AFont>(); copyFont(ite->mXmlDoc, ite->mFontNode, font.get(), "/system/fonts/"); if (!isFontFileAvailable(font->mFilePath)) { return ASystemFontIterator_next(ite); @@ -279,7 +318,7 @@ ASystemFont* ASystemFontIterator_next(ASystemFontIterator* ite) { ite->mFontNode = nullptr; return nullptr; } else { - std::unique_ptr<ASystemFont> font = std::make_unique<ASystemFont>(); + std::unique_ptr<AFont> font = std::make_unique<AFont>(); copyFont(ite->mCustomizationXmlDoc, ite->mFontNode, font.get(), "/product/fonts/"); if (!isFontFileAvailable(font->mFilePath)) { return ASystemFontIterator_next(ite); @@ -290,48 +329,48 @@ ASystemFont* ASystemFontIterator_next(ASystemFontIterator* ite) { return nullptr; } -void ASystemFont_close(ASystemFont* font) { +void AFont_close(AFont* font) { delete font; } -const char* ASystemFont_getFontFilePath(const ASystemFont* font) { +const char* AFont_getFontFilePath(const AFont* font) { LOG_ALWAYS_FATAL_IF(font == nullptr, "nullptr has passed as font argument"); return font->mFilePath.c_str(); } -uint16_t ASystemFont_getWeight(const ASystemFont* font) { +uint16_t AFont_getWeight(const AFont* font) { LOG_ALWAYS_FATAL_IF(font == nullptr, "nullptr has passed as font argument"); return font->mWeight; } -bool ASystemFont_isItalic(const ASystemFont* font) { +bool AFont_isItalic(const AFont* font) { LOG_ALWAYS_FATAL_IF(font == nullptr, "nullptr has passed as font argument"); return font->mItalic; } -const char* ASystemFont_getLocale(const ASystemFont* font) { +const char* AFont_getLocale(const AFont* font) { LOG_ALWAYS_FATAL_IF(font == nullptr, "nullptr has passed to font argument"); return font->mLocale ? nullptr : font->mLocale->c_str(); } -size_t ASystemFont_getCollectionIndex(const ASystemFont* font) { +size_t AFont_getCollectionIndex(const AFont* font) { LOG_ALWAYS_FATAL_IF(font == nullptr, "nullptr has passed to font argument"); return font->mCollectionIndex; } -size_t ASystemFont_getAxisCount(const ASystemFont* font) { +size_t AFont_getAxisCount(const AFont* font) { LOG_ALWAYS_FATAL_IF(font == nullptr, "nullptr has passed to font argument"); return font->mAxes.size(); } -uint32_t ASystemFont_getAxisTag(const ASystemFont* font, uint32_t axisIndex) { +uint32_t AFont_getAxisTag(const AFont* font, uint32_t axisIndex) { LOG_ALWAYS_FATAL_IF(font == nullptr, "nullptr has passed to font argument"); LOG_ALWAYS_FATAL_IF(axisIndex >= font->mAxes.size(), "given axis index is out of bounds. (< %zd", font->mAxes.size()); return font->mAxes[axisIndex].first; } -float ASystemFont_getAxisValue(const ASystemFont* font, uint32_t axisIndex) { +float AFont_getAxisValue(const AFont* font, uint32_t axisIndex) { LOG_ALWAYS_FATAL_IF(font == nullptr, "nullptr has passed to font argument"); LOG_ALWAYS_FATAL_IF(axisIndex >= font->mAxes.size(), "given axis index is out of bounds. (< %zd", font->mAxes.size()); diff --git a/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreService.java b/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreService.java index 5650f2125737..bee4bbd9f42d 100644 --- a/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreService.java +++ b/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreService.java @@ -33,8 +33,8 @@ import android.net.IIpMemoryStore; import android.net.ipmemorystore.Blob; import android.net.ipmemorystore.IOnBlobRetrievedListener; import android.net.ipmemorystore.IOnL2KeyResponseListener; -import android.net.ipmemorystore.IOnNetworkAttributesRetrieved; -import android.net.ipmemorystore.IOnSameNetworkResponseListener; +import android.net.ipmemorystore.IOnNetworkAttributesRetrievedListener; +import android.net.ipmemorystore.IOnSameL3NetworkResponseListener; import android.net.ipmemorystore.IOnStatusListener; import android.net.ipmemorystore.NetworkAttributes; import android.net.ipmemorystore.NetworkAttributesParcelable; @@ -297,16 +297,16 @@ public class IpMemoryStoreService extends IIpMemoryStore.Stub { */ @Override public void isSameNetwork(@Nullable final String l2Key1, @Nullable final String l2Key2, - @Nullable final IOnSameNetworkResponseListener listener) { + @Nullable final IOnSameL3NetworkResponseListener listener) { if (null == listener) return; mExecutor.execute(() -> { try { if (null == l2Key1 || null == l2Key2) { - listener.onSameNetworkResponse(makeStatus(ERROR_ILLEGAL_ARGUMENT), null); + listener.onSameL3NetworkResponse(makeStatus(ERROR_ILLEGAL_ARGUMENT), null); return; } if (null == mDb) { - listener.onSameNetworkResponse(makeStatus(ERROR_ILLEGAL_ARGUMENT), null); + listener.onSameL3NetworkResponse(makeStatus(ERROR_ILLEGAL_ARGUMENT), null); return; } try { @@ -315,16 +315,16 @@ public class IpMemoryStoreService extends IIpMemoryStore.Stub { final NetworkAttributes attr2 = IpMemoryStoreDatabase.retrieveNetworkAttributes(mDb, l2Key2); if (null == attr1 || null == attr2) { - listener.onSameNetworkResponse(makeStatus(SUCCESS), + listener.onSameL3NetworkResponse(makeStatus(SUCCESS), new SameL3NetworkResponse(l2Key1, l2Key2, -1f /* never connected */).toParcelable()); return; } final float confidence = attr1.getNetworkGroupSamenessConfidence(attr2); - listener.onSameNetworkResponse(makeStatus(SUCCESS), + listener.onSameL3NetworkResponse(makeStatus(SUCCESS), new SameL3NetworkResponse(l2Key1, l2Key2, confidence).toParcelable()); } catch (Exception e) { - listener.onSameNetworkResponse(makeStatus(ERROR_GENERIC), null); + listener.onSameL3NetworkResponse(makeStatus(ERROR_GENERIC), null); } } catch (final RemoteException e) { // Client at the other end died @@ -343,7 +343,7 @@ public class IpMemoryStoreService extends IIpMemoryStore.Stub { */ @Override public void retrieveNetworkAttributes(@Nullable final String l2Key, - @Nullable final IOnNetworkAttributesRetrieved listener) { + @Nullable final IOnNetworkAttributesRetrievedListener listener) { if (null == listener) return; mExecutor.execute(() -> { try { diff --git a/packages/NetworkStack/tests/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreServiceTest.java b/packages/NetworkStack/tests/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreServiceTest.java index 94cc58919459..a00eff7992d4 100644 --- a/packages/NetworkStack/tests/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreServiceTest.java +++ b/packages/NetworkStack/tests/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreServiceTest.java @@ -31,8 +31,8 @@ import android.content.Context; import android.net.ipmemorystore.Blob; import android.net.ipmemorystore.IOnBlobRetrievedListener; import android.net.ipmemorystore.IOnL2KeyResponseListener; -import android.net.ipmemorystore.IOnNetworkAttributesRetrieved; -import android.net.ipmemorystore.IOnSameNetworkResponseListener; +import android.net.ipmemorystore.IOnNetworkAttributesRetrievedListener; +import android.net.ipmemorystore.IOnSameL3NetworkResponseListener; import android.net.ipmemorystore.IOnStatusListener; import android.net.ipmemorystore.NetworkAttributes; import android.net.ipmemorystore.NetworkAttributesParcelable; @@ -163,9 +163,9 @@ public class IpMemoryStoreServiceTest { private interface OnNetworkAttributesRetrievedListener { void onNetworkAttributesRetrieved(Status status, String l2Key, NetworkAttributes attr); } - private IOnNetworkAttributesRetrieved onNetworkAttributesRetrieved( + private IOnNetworkAttributesRetrievedListener onNetworkAttributesRetrieved( final OnNetworkAttributesRetrievedListener functor) { - return new IOnNetworkAttributesRetrieved() { + return new IOnNetworkAttributesRetrievedListener() { @Override public void onNetworkAttributesRetrieved(final StatusParcelable status, final String l2Key, final NetworkAttributesParcelable attributes) @@ -182,17 +182,17 @@ public class IpMemoryStoreServiceTest { } /** Helper method to make an IOnSameNetworkResponseListener */ - private interface OnSameNetworkResponseListener { - void onSameNetworkResponse(Status status, SameL3NetworkResponse answer); + private interface OnSameL3NetworkResponseListener { + void onSameL3NetworkResponse(Status status, SameL3NetworkResponse answer); } - private IOnSameNetworkResponseListener onSameResponse( - final OnSameNetworkResponseListener functor) { - return new IOnSameNetworkResponseListener() { + private IOnSameL3NetworkResponseListener onSameResponse( + final OnSameL3NetworkResponseListener functor) { + return new IOnSameL3NetworkResponseListener() { @Override - public void onSameNetworkResponse(final StatusParcelable status, + public void onSameL3NetworkResponse(final StatusParcelable status, final SameL3NetworkResponseParcelable sameL3Network) throws RemoteException { - functor.onSameNetworkResponse(new Status(status), + functor.onSameL3NetworkResponse(new Status(status), null == sameL3Network ? null : new SameL3NetworkResponse(sameL3Network)); } diff --git a/packages/SettingsLib/BarChartPreference/res/layout/settings_bar_chart.xml b/packages/SettingsLib/BarChartPreference/res/layout/settings_bar_chart.xml index 25246d63f2b7..0a336bf879c1 100644 --- a/packages/SettingsLib/BarChartPreference/res/layout/settings_bar_chart.xml +++ b/packages/SettingsLib/BarChartPreference/res/layout/settings_bar_chart.xml @@ -36,6 +36,7 @@ <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" + android:layout_marginBottom="20dp" android:gravity="center|bottom"> <com.android.settingslib.widget.BarView diff --git a/packages/SettingsLib/BarChartPreference/res/values/dimens.xml b/packages/SettingsLib/BarChartPreference/res/values/dimens.xml index 7148afa2d62f..5f741a0ad4a8 100644 --- a/packages/SettingsLib/BarChartPreference/res/values/dimens.xml +++ b/packages/SettingsLib/BarChartPreference/res/values/dimens.xml @@ -16,6 +16,6 @@ --> <resources> - <dimen name="settings_bar_view_max_height">106dp</dimen> + <dimen name="settings_bar_view_max_height">72dp</dimen> <dimen name="settings_bar_view_icon_size">24dp</dimen> </resources>
\ No newline at end of file diff --git a/packages/SettingsLib/BarChartPreference/res/values/styles.xml b/packages/SettingsLib/BarChartPreference/res/values/styles.xml index 8302c0118adf..7a3fb7d9386e 100644 --- a/packages/SettingsLib/BarChartPreference/res/values/styles.xml +++ b/packages/SettingsLib/BarChartPreference/res/values/styles.xml @@ -40,7 +40,7 @@ <style name="SettingsBarViewStyle"> <item name="android:layout_width">0dp</item> - <item name="android:layout_height">250dp</item> + <item name="android:layout_height">168dp</item> <item name="android:layout_weight">1</item> <item name="android:layout_marginStart">8dp</item> <item name="android:layout_marginEnd">8dp</item> @@ -63,6 +63,7 @@ <item name="android:layout_height">@dimen/settings_bar_view_icon_size</item> <item name="android:scaleType">fitCenter</item> <item name="android:layout_marginTop">12dp</item> + <item name="android:tint">?android:attr/textColorPrimary</item> </style> <style name="SettingsBarChartBarTitle"> @@ -78,7 +79,6 @@ <item name="android:layout_width">wrap_content</item> <item name="android:layout_height">wrap_content</item> <item name="android:layout_marginTop">4dp</item> - <item name="android:layout_marginBottom">12dp</item> <item name="android:singleLine">true</item> <item name="android:ellipsize">marquee</item> <item name="android:textAppearance">@style/BarChart.Text.Summary</item> diff --git a/packages/SettingsLib/HelpUtils/res/drawable/ic_help_actionbar.xml b/packages/SettingsLib/HelpUtils/res/drawable/ic_help_actionbar.xml index 9089a930c254..1a4b1c3ebe50 100644 --- a/packages/SettingsLib/HelpUtils/res/drawable/ic_help_actionbar.xml +++ b/packages/SettingsLib/HelpUtils/res/drawable/ic_help_actionbar.xml @@ -20,6 +20,7 @@ android:height="24dp" android:viewportWidth="24.0" android:viewportHeight="24.0" + android:autoMirrored="true" android:tint="?android:attr/colorControlNormal"> <path android:fillColor="#FFFFFFFF" diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerWhitelistBackend.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerWhitelistBackend.java index 9feacac60365..eeb6cb015ae6 100644 --- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerWhitelistBackend.java +++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerWhitelistBackend.java @@ -75,20 +75,32 @@ public class PowerWhitelistBackend { return true; } + if (isDefaultActiveApp(pkg)) { + return true; + } + + return false; + } + + /** + * Check if it is default active app in multiple area(i.e. SMS, Dialer, Device admin..) + */ + public boolean isDefaultActiveApp(String pkg) { // Additionally, check if pkg is default dialer/sms. They are considered essential apps and // should be automatically whitelisted (otherwise user may be able to set restriction on // them, leading to bad device behavior.) - if (!mAppContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) { - return false; - } + + final boolean hasTelephony = mAppContext.getPackageManager().hasSystemFeature( + PackageManager.FEATURE_TELEPHONY); final ComponentName defaultSms = SmsApplication.getDefaultSmsApplication(mAppContext, true /* updateIfNeeded */); - if (defaultSms != null && TextUtils.equals(pkg, defaultSms.getPackageName())) { + if (hasTelephony && defaultSms != null && TextUtils.equals(pkg, + defaultSms.getPackageName())) { return true; } final String defaultDialer = DefaultDialerManager.getDefaultDialerApplication(mAppContext); - if (TextUtils.equals(pkg, defaultDialer)) { + if (hasTelephony && TextUtils.equals(pkg, defaultDialer)) { return true; } diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/ZenDurationDialog.java b/packages/SettingsLib/src/com/android/settingslib/notification/ZenDurationDialog.java index b9197fe1ebd5..66ee8021957f 100644 --- a/packages/SettingsLib/src/com/android/settingslib/notification/ZenDurationDialog.java +++ b/packages/SettingsLib/src/com/android/settingslib/notification/ZenDurationDialog.java @@ -210,7 +210,6 @@ public class ZenDurationDialog { private void setupUi(ConditionTag tag, View row) { if (tag.lines == null) { tag.lines = row.findViewById(android.R.id.content); - tag.lines.setAccessibilityLiveRegion(View.ACCESSIBILITY_LIVE_REGION_POLITE); } if (tag.line1 == null) { diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerWhitelistBackendTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerWhitelistBackendTest.java index bbf807d29402..44ee423785c2 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerWhitelistBackendTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerWhitelistBackendTest.java @@ -118,6 +118,7 @@ public class PowerWhitelistBackendTest { ShadowSmsApplication.setDefaultSmsApplication(new ComponentName(testSms, "receiver")); assertThat(mPowerWhitelistBackend.isWhitelisted(testSms)).isTrue(); + assertThat(mPowerWhitelistBackend.isDefaultActiveApp(testSms)).isTrue(); } @Test @@ -126,6 +127,7 @@ public class PowerWhitelistBackendTest { ShadowDefaultDialerManager.setDefaultDialerApplication(testDialer); assertThat(mPowerWhitelistBackend.isWhitelisted(testDialer)).isTrue(); + assertThat(mPowerWhitelistBackend.isDefaultActiveApp(testDialer)).isTrue(); } @Test @@ -133,6 +135,7 @@ public class PowerWhitelistBackendTest { doReturn(true).when(mDevicePolicyManager).packageHasActiveAdmins(PACKAGE_ONE); assertThat(mPowerWhitelistBackend.isWhitelisted(PACKAGE_ONE)).isTrue(); + assertThat(mPowerWhitelistBackend.isDefaultActiveApp(PACKAGE_ONE)).isTrue(); } @Test diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index abd2b7684526..fcf920064e92 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -112,6 +112,7 @@ <uses-permission android:name="android.permission.REGISTER_WINDOW_MANAGER_LISTENERS" /> <uses-permission android:name="android.permission.SET_ORIENTATION" /> <uses-permission android:name="android.permission.DISABLE_KEYGUARD" /> + <uses-permission android:name="android.permission.MONITOR_INPUT" /> <!-- DreamManager --> <uses-permission android:name="android.permission.READ_DREAM_STATE" /> diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockPlugin.java index 105be46c05d7..58d50ead72ce 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockPlugin.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockPlugin.java @@ -28,7 +28,7 @@ import java.util.TimeZone; public interface ClockPlugin extends Plugin { String ACTION = "com.android.systemui.action.PLUGIN_CLOCK"; - int VERSION = 3; + int VERSION = 4; /** * Get the name of the clock face. @@ -72,6 +72,14 @@ public interface ClockPlugin extends Plugin { } /** + * Allows the plugin to clean up resources when no longer needed. + * + * Called when the view previously created by {@link ClockPlugin#getView()} has been detached + * from the view hierarchy. + */ + void onDestroyView(); + + /** * Set clock paint style. * @param style The new style to set in the paint. */ diff --git a/packages/SystemUI/res/drawable-nodpi/icon.xml b/packages/SystemUI/res/drawable-nodpi/icon.xml index 48094c48aa68..7a68c032d8be 100644 --- a/packages/SystemUI/res/drawable-nodpi/icon.xml +++ b/packages/SystemUI/res/drawable-nodpi/icon.xml @@ -15,5 +15,5 @@ Copyright (C) 2018 The Android Open Source Project --> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <background android:drawable="@drawable/icon_bg"/> - <foreground android:drawable="@drawable/p"/> + <foreground android:drawable="@drawable/q"/> </adaptive-icon> diff --git a/packages/SystemUI/res/drawable-nodpi/icon_bg.xml b/packages/SystemUI/res/drawable-nodpi/icon_bg.xml index 31ecf7edfb11..2a54dfad8191 100644 --- a/packages/SystemUI/res/drawable-nodpi/icon_bg.xml +++ b/packages/SystemUI/res/drawable-nodpi/icon_bg.xml @@ -14,5 +14,5 @@ Copyright (C) 2018 The Android Open Source Project limitations under the License. --> <color xmlns:android="http://schemas.android.com/apk/res/android" - android:color="#C5E1A5" /> + android:color="#77C360" /> diff --git a/packages/SystemUI/res/drawable-nodpi/p.xml b/packages/SystemUI/res/drawable-nodpi/p.xml deleted file mode 100644 index 596b7824cdb7..000000000000 --- a/packages/SystemUI/res/drawable-nodpi/p.xml +++ /dev/null @@ -1,33 +0,0 @@ -<!-- -Copyright (C) 2018 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="108dp" - android:height="108dp" - android:viewportWidth="108" - android:viewportHeight="108"> - <path - android:pathData="M49,65L54,65C60.075,65 65,60.075 65,54C65,47.925 60.075,43 54,43C47.925,43 43,47.925 43,54L43,108" - android:strokeWidth="16" - android:fillColor="#00000000" - android:strokeColor="#7CB342" - android:fillType="evenOdd"/> - <path - android:pathData="M51,65L54,65C60.075,65 65,60.075 65,54C65,47.925 60.075,43 54,43C47.925,43 43,47.925 43,54L43,108" - android:strokeWidth="8" - android:fillColor="#00000000" - android:strokeColor="#FFFFFF" - android:fillType="evenOdd"/> -</vector> diff --git a/packages/SystemUI/res/drawable-nodpi/q.xml b/packages/SystemUI/res/drawable-nodpi/q.xml new file mode 100644 index 000000000000..0f42d2e20040 --- /dev/null +++ b/packages/SystemUI/res/drawable-nodpi/q.xml @@ -0,0 +1,40 @@ +<!-- +Copyright (C) 2019 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="108dp" + android:height="108dp" + android:viewportWidth="108.0" + android:viewportHeight="108.0"> + <group + android:name="scale" + android:pivotX="54" android:pivotY="54" + android:scaleX="0.9" + android:scaleY="0.9"> + <group + android:name="nudge" + android:translateX="24" + android:translateY="23.5"> + <path + android:name="tail" + android:fillColor="#FFFFFF" + android:pathData="M21.749674,34.122784l-9.431964,9.529709l-6.31771,-6.2529106l15.736504,-15.899582l64.765724,65.16436l-6.3046494,6.266083z"/> + <path + android:name="counter" + android:fillColor="#FFFFFF" + android:pathData="M30,9.32352941 C41.6954418,9.32352941 51.1764706,18.8045582 51.1764706,30.5 C51.1764706,42.1954418 41.6954418,51.6764706 30,51.6764706 C18.3045582,51.6764706 8.82352941,42.1954418 8.82352941,30.5 C8.82352941,18.8045582 18.3045582,9.32352941 30,9.32352941 L30,9.32352941 Z M30,0.5 C13.4314575,0.5 -5.53805368e-15,13.9314575 -7.10542736e-15,30.5 C-1.02401747e-14,47.0685425 13.4314575,60.5 30,60.5 C46.5685425,60.5 60,47.0685425 60,30.5 C59.9805514,13.9395201 46.5604799,0.519448617 30,0.5 Z"/> + </group> + </group> +</vector> diff --git a/packages/SystemUI/res/drawable/bubble_flyout.xml b/packages/SystemUI/res/drawable/bubble_flyout.xml new file mode 100644 index 000000000000..5406aaa65372 --- /dev/null +++ b/packages/SystemUI/res/drawable/bubble_flyout.xml @@ -0,0 +1,29 @@ +<!-- + ~ Copyright (C) 2019 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 + --> +<layer-list xmlns:android="http://schemas.android.com/apk/res/android" > + <!-- TODO: Add the triangle pointing to the bubble stack. --> + <item> + <shape android:shape="rectangle"> + <solid android:color="?android:attr/colorBackgroundFloating" /> + <corners + android:bottomLeftRadius="?android:attr/dialogCornerRadius" + android:topLeftRadius="?android:attr/dialogCornerRadius" + android:bottomRightRadius="?android:attr/dialogCornerRadius" + android:topRightRadius="?android:attr/dialogCornerRadius" + /> + </shape> + </item> +</layer-list>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/ic_notification_gentle.xml b/packages/SystemUI/res/drawable/ic_notification_gentle.xml new file mode 100644 index 000000000000..7074130a63ce --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_notification_gentle.xml @@ -0,0 +1,40 @@ +<!-- +Copyright (C) 2019 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. +--> +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> + <item + android:id="@+id/back"> + <shape android:shape="oval"> + <solid + android:color="@color/GM2_green_500" /> + <size + android:height="36dp" + android:width="36dp"/> + </shape> + </item> + <item + android:id="@+id/fore" + android:gravity="center"> + <vector + android:width="32dp" + android:height="32dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:fillColor="#FFFFFFFF" + android:pathData="M15,14.5c-1.38,0 -2.5,-1.12 -2.5,-2.5c0,-0.28 -0.22,-0.5 -0.5,-0.5s-0.5,0.22 -0.5,0.5c0,1.38 -1.12,2.5 -2.5,2.5S6.5,13.38 6.5,12c0,-0.28 -0.22,-0.5 -0.5,-0.5c-0.24,0 -0.46,0.18 -0.49,0.42C5.41,12.55 4.89,13 4.27,13H2v-2h1.71C4.1,10.11 5,9.5 6,9.5c1.38,0 2.5,1.12 2.5,2.5c0,0.28 0.22,0.5 0.5,0.5s0.5,-0.22 0.5,-0.5c0,-1.38 1.12,-2.5 2.5,-2.5s2.5,1.12 2.5,2.5c0,0.28 0.22,0.5 0.5,0.5s0.5,-0.22 0.5,-0.5c0,-1.38 1.12,-2.5 2.5,-2.5c1.02,0 1.91,0.6 2.29,1.5H22v2h-2.27c-0.62,0 -1.14,-0.45 -1.23,-1.08c-0.04,-0.24 -0.25,-0.42 -0.49,-0.42c-0.28,0 -0.5,0.22 -0.5,0.5C17.5,13.38 16.38,14.5 15,14.5z"/> + </vector> + </item> +</layer-list> diff --git a/packages/SystemUI/res/drawable/ic_notification_interruptive.xml b/packages/SystemUI/res/drawable/ic_notification_interruptive.xml new file mode 100644 index 000000000000..0a8b3b8fa775 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_notification_interruptive.xml @@ -0,0 +1,41 @@ +<!-- +Copyright (C) 2019 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. +--> + +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> + <item + android:id="@+id/back"> + <shape android:shape="oval"> + <solid + android:color="@color/GM2_yellow_500" /> + <size + android:height="36dp" + android:width="36dp"/> + </shape> + </item> + <item + android:id="@+id/fore" + android:gravity="center"> + <vector + android:width="32dp" + android:height="32dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:fillColor="#FFFFFFFF" + android:pathData="M8.98,16.65c-0.47,0 -0.91,-0.27 -1.12,-0.69l-1.93,-4.61L5.46,12.3c-0.21,0.43 -0.64,0.69 -1.12,0.69H2v-2h1.88l1,-2C5.1,8.56 5.52,8.3 6,8.3s0.9,0.26 1.12,0.69l1.73,4.14l2,-7c0.2,-0.46 0.65,-0.76 1.15,-0.76s0.95,0.3 1.15,0.76l0.04,0.12l1.96,6.88l1.7,-4.08c0.49,-0.98 1.84,-0.91 2.26,-0.06l1,2H22v2h-2.35c-0.47,0 -0.91,-0.27 -1.12,-0.7l-0.47,-0.95l-1.9,4.55c-0.25,0.5 -0.69,0.77 -1.18,0.75c-0.48,-0.01 -0.92,-0.31 -1.11,-0.76l-0.04,-0.12L12,9.37l-1.87,6.52c-0.19,0.45 -0.63,0.74 -1.11,0.76C9.01,16.65 9,16.65 8.98,16.65zM20.32,11.4L20.32,11.4C20.32,11.4 20.32,11.4 20.32,11.4z" /> + </vector> + </item> +</layer-list> diff --git a/packages/SystemUI/res/layout/bubble_flyout.xml b/packages/SystemUI/res/layout/bubble_flyout.xml new file mode 100644 index 000000000000..74c6c123479c --- /dev/null +++ b/packages/SystemUI/res/layout/bubble_flyout.xml @@ -0,0 +1,33 @@ +<!-- + ~ Copyright (C) 2019 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<FrameLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/bubble_flyout" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:background="@drawable/bubble_flyout" + android:padding="@dimen/bubble_flyout_padding" + android:translationZ="@dimen/bubble_flyout_elevation"> + + <TextView + android:id="@+id/bubble_flyout_text" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:maxLines="2" + android:maxWidth="@dimen/bubble_flyout_maxwidth" + android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Title" /> + +</FrameLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/bubble_view.xml b/packages/SystemUI/res/layout/bubble_view.xml index 13186fc6437c..a8eb2914b0b2 100644 --- a/packages/SystemUI/res/layout/bubble_view.xml +++ b/packages/SystemUI/res/layout/bubble_view.xml @@ -27,12 +27,4 @@ android:padding="@dimen/bubble_view_padding" android:clipToPadding="false"/> - <TextView - android:id="@+id/message_view" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:minWidth="@dimen/bubble_message_min_width" - android:maxWidth="@dimen/bubble_message_max_width" - android:padding="@dimen/bubble_message_padding"/> - </com.android.systemui.bubbles.BubbleView> diff --git a/packages/SystemUI/res/layout/notification_info.xml b/packages/SystemUI/res/layout/notification_info.xml index 0d44931463bd..f7c6c435d258 100644 --- a/packages/SystemUI/res/layout/notification_info.xml +++ b/packages/SystemUI/res/layout/notification_info.xml @@ -24,6 +24,7 @@ android:clipChildren="false" android:clipToPadding="false" android:orientation="vertical" + android:paddingStart="@*android:dimen/notification_content_margin_start" android:background="@color/notification_guts_bg_color"> <!-- Package Info --> @@ -31,7 +32,6 @@ android:id="@+id/header" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginStart="@*android:dimen/notification_content_margin_start" android:clipChildren="false" android:clipToPadding="false"> <ImageView @@ -44,7 +44,7 @@ android:id="@+id/pkgname" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Info" + style="@style/TextAppearance.NotificationInfo.Primary" android:layout_marginStart="3dp" android:layout_marginEnd="2dp" android:singleLine="true" @@ -54,7 +54,7 @@ android:id="@+id/pkg_divider" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Info" + style="@style/TextAppearance.NotificationInfo.Primary" android:layout_marginStart="2dp" android:layout_marginEnd="2dp" android:text="@*android:string/notification_header_divider_symbol" @@ -64,7 +64,7 @@ android:id="@+id/delegate_name" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Info" + style="@style/TextAppearance.NotificationInfo.Primary" android:layout_marginStart="2dp" android:layout_marginEnd="2dp" android:ellipsize="end" @@ -77,286 +77,284 @@ android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_alignParentEnd="true" - android:paddingHorizontal="16dp" + android:orientation="horizontal"> <!-- Optional link to app. Only appears if the channel is not disabled and the app asked for it --> <ImageButton android:id="@+id/app_settings" - android:layout_width="40dp" - android:layout_height="56dp" + android:layout_width="48dp" + android:layout_height="48dp" android:layout_centerVertical="true" - android:paddingRight="16dp" android:visibility="gone" android:background="@drawable/ripple_drawable" android:contentDescription="@string/notification_app_settings" - android:src="@drawable/ic_settings" - android:tint="?android:attr/colorAccent" /> + android:src="@drawable/ic_info" + android:tint="@color/notification_guts_link_icon_tint" /> <!-- 24 dp icon with 16 dp padding all around to mirror notification content margins --> <ImageButton android:id="@+id/info" - android:layout_width="24dp" - android:layout_height="56dp" + android:layout_width="48dp" + android:layout_height="48dp" android:layout_centerVertical="true" android:background="@drawable/ripple_drawable" android:contentDescription="@string/notification_more_settings" - android:src="@drawable/ic_info" - android:tint="?android:attr/colorAccent" /> + android:src="@drawable/ic_settings" + android:tint="@color/notification_guts_link_icon_tint" /> </LinearLayout> </RelativeLayout> + <!-- Channel Info Block --> <LinearLayout - android:id="@+id/prompt" + android:id="@+id/channel_info" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="@dimen/notification_guts_button_spacing" - android:clipChildren="false" - android:clipToPadding="false" + android:paddingEnd="@*android:dimen/notification_content_margin_end" android:orientation="vertical"> - - <!-- Channel Info Block --> - <LinearLayout + <RelativeLayout + android:id="@+id/names" android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginStart="@*android:dimen/notification_content_margin_start" - android:layout_marginEnd="@*android:dimen/notification_content_margin_start" - android:orientation="vertical"> - <RelativeLayout - android:id="@+id/names" - android:layout_width="match_parent" - android:layout_height="wrap_content"> - <TextView - android:id="@+id/group_name" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Title" - android:layout_marginStart="2dp" - android:layout_marginEnd="2dp" - android:ellipsize="end" - android:maxLines="1" - android:layout_centerVertical="true" /> - <TextView - android:id="@+id/pkg_group_divider" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Title" - android:layout_marginStart="2dp" - android:layout_marginEnd="2dp" - android:text="@*android:string/notification_header_divider_symbol" - android:layout_centerVertical="true" - android:layout_toEndOf="@id/group_name" /> - <!-- Channel Name --> - <TextView - android:id="@+id/channel_name" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_weight="1" - style="@*android:style/TextAppearance.DeviceDefault.Notification.Title" - android:layout_toEndOf="@id/pkg_group_divider"/> - </RelativeLayout> - <!-- Question prompt --> + android:layout_height="wrap_content"> <TextView - android:id="@+id/block_prompt" + android:id="@+id/group_name" android:layout_width="wrap_content" android:layout_height="wrap_content" - style="@*android:style/TextAppearance.DeviceDefault.Notification" /> - </LinearLayout> + style="@style/TextAppearance.NotificationInfo.Primary" + android:layout_marginStart="2dp" + android:layout_marginEnd="2dp" + android:ellipsize="end" + android:maxLines="1" + android:layout_centerVertical="true" /> + <TextView + android:id="@+id/pkg_group_divider" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + style="@style/TextAppearance.NotificationInfo.Primary" + android:layout_marginStart="2dp" + android:layout_marginEnd="2dp" + android:text="@*android:string/notification_header_divider_symbol" + android:layout_centerVertical="true" + android:layout_toEndOf="@id/group_name" /> + <!-- Channel Name --> + <TextView + android:id="@+id/channel_name" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="1" + style="@style/TextAppearance.NotificationInfo.Primary" + android:layout_toEndOf="@id/pkg_group_divider"/> + </RelativeLayout> + </LinearLayout> - <!-- Settings and Done buttons --> + <LinearLayout + android:id="@+id/blocking_helper" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="@dimen/notification_guts_button_spacing" + android:paddingEnd="@*android:dimen/notification_content_margin_end" + android:clipChildren="false" + android:clipToPadding="false" + android:orientation="vertical"> + <!-- blocking helper text. no need for non-configurable check b/c controls won't be + activated in that case --> + <TextView + android:id="@+id/blocking_helper_text" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="2dp" + android:text="@string/inline_blocking_helper" + style="@*android:style/TextAppearance.DeviceDefault.Notification" /> <RelativeLayout - android:id="@+id/block_or_minimize" + android:id="@+id/block_buttons" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginTop="@dimen/notification_guts_button_spacing" - android:layout_marginStart="@dimen/notification_guts_button_side_margin" - android:layout_marginEnd="@dimen/notification_guts_button_side_margin" - android:clipChildren="false" - android:clipToPadding="false"> + android:layout_marginTop="@dimen/notification_guts_button_spacing"> <TextView - android:id="@+id/done" - android:text="@string/inline_ok_button" + android:id="@+id/blocking_helper_turn_off_notifications" + android:text="@string/inline_turn_off_notifications" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" - android:maxWidth="100dp" + android:layout_alignParentStart="true" + android:width="110dp" + android:paddingEnd="15dp" + android:breakStrategy="simple" style="@style/TextAppearance.NotificationInfo.Button"/> - - <LinearLayout - android:id="@+id/block_buttons" + <TextView + android:id="@+id/deliver_silently" + android:text="@string/inline_deliver_silently_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + android:layout_marginStart="@dimen/notification_guts_button_horizontal_spacing" + android:paddingEnd="15dp" + android:width="110dp" + android:breakStrategy="simple" + android:layout_toStartOf="@+id/keep_showing" + style="@style/TextAppearance.NotificationInfo.Button"/> + <TextView + android:id="@+id/keep_showing" + android:text="@string/inline_keep_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" + android:layout_marginStart="@dimen/notification_guts_button_horizontal_spacing" + android:width="110dp" + android:breakStrategy="simple" android:layout_alignParentEnd="true" - android:maxWidth="200dp" - android:orientation="horizontal"> - <TextView - android:id="@+id/deliver_silently" - android:text="@string/inline_silent_button_silent" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_centerVertical="true" - android:layout_marginStart="@dimen/notification_guts_button_horizontal_spacing" - android:paddingRight="24dp" - android:maxWidth="125dp" - style="@style/TextAppearance.NotificationInfo.Button"/> - <TextView - android:id="@+id/block" - android:text="@string/inline_block_button" - android:minWidth="48dp" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_centerVertical="true" - android:maxWidth="75dp" - android:layout_marginStart="@dimen/notification_guts_button_horizontal_spacing" - style="@style/TextAppearance.NotificationInfo.Button"/> - <TextView - android:id="@+id/minimize" - android:text="@string/inline_minimize_button" - android:minWidth="48dp" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_centerVertical="true" - android:maxWidth="75dp" - android:layout_marginStart="@dimen/notification_guts_button_horizontal_spacing" - style="@style/TextAppearance.NotificationInfo.Button"/> - </LinearLayout> + style="@style/TextAppearance.NotificationInfo.Button"/> </RelativeLayout> + + </LinearLayout> + + <LinearLayout + android:id="@+id/inline_controls" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="@dimen/notification_guts_button_spacing" + android:paddingEnd="@*android:dimen/notification_content_margin_end" + android:clipChildren="false" + android:clipToPadding="false" + android:orientation="vertical"> + + <!-- Non configurable app/channel text. appears instead of @+id/interruptiveness_settings--> + <TextView + android:id="@+id/non_configurable_text" + android:text="@string/notification_unblockable_desc" + android:visibility="gone" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingTop="@dimen/notification_guts_option_vertical_padding" + style="@*android:style/TextAppearance.DeviceDefault.Notification" /> + + <!-- Non configurable multichannel text. appears instead of @+id/interruptiveness_settings--> + <TextView + android:id="@+id/non_configurable_multichannel_text" + android:text="@string/notification_multichannel_desc" + android:visibility="gone" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingTop="@dimen/notification_guts_option_vertical_padding" + style="@*android:style/TextAppearance.DeviceDefault.Notification" /> + <LinearLayout android:id="@+id/interruptiveness_settings" android:layout_width="match_parent" android:layout_height="wrap_content" - android:orientation="vertical" - android:visibility="gone"> + android:orientation="vertical"> + <!-- Interruptive row --> <LinearLayout + android:id="@+id/alert_row" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginTop="2dp" - android:layout_marginStart="@dimen/notification_guts_button_side_margin" - android:layout_marginEnd="@dimen/notification_guts_button_side_margin" - android:gravity="center" + android:paddingTop="@dimen/notification_guts_option_vertical_padding" + android:paddingBottom="@dimen/notification_guts_option_vertical_padding" + android:paddingStart="@dimen/notification_guts_option_horizontal_padding" android:orientation="horizontal"> + + <ImageView + android:id="@+id/int_alert" + android:src="@drawable/ic_notification_interruptive" + android:background="@android:color/transparent" + android:layout_gravity="center" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:contentDescription="@string/inline_silent_button_alert"/> + <LinearLayout - android:layout_width="0dp" - android:layout_height="match_parent" - android:layout_weight="1" - android:gravity="center_horizontal" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingStart="@dimen/notification_guts_option_horizontal_padding" + android:paddingEnd="@dimen/notification_guts_option_horizontal_padding" android:orientation="vertical"> - <FrameLayout - android:id="@+id/int_block_wrapper" - android:padding="4dp" - android:layout_width="48dp" - android:layout_height="48dp" - android:gravity="center"> - <ImageButton - android:id="@+id/int_block" - android:background="@drawable/circle_white_40dp" - android:src="@drawable/ic_notification_block" - android:layout_gravity="center" - android:layout_width="40dp" - android:layout_height="40dp" - android:clickable="false" - android:contentDescription="@string/inline_block_button" - android:tint="@color/GM2_grey_400" - style="@style/TextAppearance.NotificationInfo.Button"/> - </FrameLayout> <TextView - android:id="@+id/int_block_label" - android:text="@string/inline_block_button" - android:layout_gravity="center_horizontal" - android:gravity="center" + android:id="@+id/int_alert_label" + android:text="@string/inline_silent_button_alert" android:layout_width="match_parent" android:layout_height="wrap_content" android:ellipsize="end" android:maxLines="1" - style="@style/TextAppearance.NotificationInfo.ButtonLabel"/> + style="@style/TextAppearance.NotificationInfo.Primary"/> + <TextView + android:id="@+id/int_alert_summary" + android:text="@string/hint_text_alert" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:ellipsize="end" + style="@style/TextAppearance.NotificationInfo.Secondary"/> </LinearLayout> + </LinearLayout> + + <!-- Gentle row --> + <LinearLayout + android:id="@+id/silent_row" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingTop="@dimen/notification_guts_option_vertical_padding" + android:paddingBottom="@dimen/notification_guts_option_vertical_padding" + android:paddingStart="@dimen/notification_guts_option_horizontal_padding" + android:layout_marginTop="@dimen/notification_guts_option_vertical_margin" + android:orientation="horizontal"> + <ImageView + android:id="@+id/int_silent" + android:src="@drawable/ic_notification_gentle" + android:layout_gravity="center" + android:layout_width="36dp" + android:layout_height="36dp" + android:background="@android:color/transparent" + android:contentDescription="@string/inline_silent_button_silent"/> <LinearLayout - android:layout_width="0dp" - android:layout_height="match_parent" - android:layout_weight="1" - android:gravity="center_horizontal" - android:orientation="vertical"> - <FrameLayout - android:id="@+id/int_silent_wrapper" - android:padding="4dp" - android:layout_width="48dp" - android:layout_height="48dp" - android:gravity="center"> - <ImageButton - android:id="@+id/int_silent" - android:background="@drawable/circle_white_40dp" - android:src="@drawable/ic_notifications_silence" - android:layout_gravity="center" - android:layout_width="40dp" - android:layout_height="40dp" - android:clickable="false" - android:contentDescription="@string/inline_silent_button_silent" - android:tint="@color/GM2_grey_400" - style="@style/TextAppearance.NotificationInfo.Button"/> - </FrameLayout> + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:paddingStart="@dimen/notification_guts_option_horizontal_padding" + android:paddingEnd="@dimen/notification_guts_option_horizontal_padding"> <TextView android:id="@+id/int_silent_label" android:text="@string/inline_silent_button_silent" - android:layout_gravity="center_horizontal" - android:gravity="center" android:layout_width="match_parent" android:layout_height="wrap_content" android:ellipsize="end" android:maxLines="1" - style="@style/TextAppearance.NotificationInfo.ButtonLabel"/> - </LinearLayout> - <LinearLayout - android:layout_width="0dp" - android:layout_height="match_parent" - android:layout_weight="1" - android:gravity="center_horizontal" - android:orientation="vertical"> - <FrameLayout - android:id="@+id/int_alert_wrapper" - android:padding="4dp" - android:layout_width="48dp" - android:layout_height="48dp" - android:gravity="center"> - <ImageButton - android:id="@+id/int_alert" - android:background="@drawable/circle_white_40dp" - android:src="@drawable/ic_notifications_alert" - android:layout_gravity="center" - android:layout_width="40dp" - android:layout_height="40dp" - android:contentDescription="@string/inline_silent_button_alert" - android:clickable="false" - android:tint="@color/GM2_grey_400" - style="@style/TextAppearance.NotificationInfo.Button"/> - </FrameLayout> + style="@style/TextAppearance.NotificationInfo.Primary"/> <TextView - android:id="@+id/int_alert_label" - android:text="@string/inline_silent_button_alert" - android:layout_gravity="center_horizontal" - android:gravity="center" + android:id="@+id/int_silent_summary" + android:text="@string/hint_text_silent" android:layout_width="match_parent" android:layout_height="wrap_content" android:ellipsize="end" - android:maxLines="1" - style="@style/TextAppearance.NotificationInfo.ButtonLabel"/> + style="@style/TextAppearance.NotificationInfo.Secondary"/> </LinearLayout> </LinearLayout> + </LinearLayout> + + <RelativeLayout + android:id="@+id/bottom_buttons" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingTop="@dimen/notification_guts_button_spacing" + android:paddingBottom="@dimen/notification_guts_button_spacing"> <TextView - android:id="@+id/hint_text" - android:layout_marginStart="@*android:dimen/notification_content_margin_start" - android:layout_marginEnd="@*android:dimen/notification_content_margin_start" + android:id="@+id/turn_off_notifications" + android:text="@string/inline_turn_off_notifications" android:layout_width="wrap_content" android:layout_height="wrap_content" - style="@style/TextAppearance.NotificationInfo.HintText" /> + android:layout_centerVertical="true" + android:layout_alignParentStart="true" + android:maxWidth="200dp" + style="@style/TextAppearance.NotificationInfo.Button"/> <TextView - android:id="@+id/done_button" + android:id="@+id/done" + android:text="@string/inline_ok_button" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_gravity="right" - android:paddingRight="24dp" - android:text="@string/inline_done_button" - style="@style/TextAppearance.NotificationInfo.Button" /> - </LinearLayout> + android:layout_centerVertical="true" + android:maxWidth="125dp" + android:layout_alignParentEnd="true" + style="@style/TextAppearance.NotificationInfo.Button"/> + </RelativeLayout> + </LinearLayout> <com.android.systemui.statusbar.notification.row.NotificationUndoLayout diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml index d74d25846e3c..8a0aaea77d52 100644 --- a/packages/SystemUI/res/values-night/colors.xml +++ b/packages/SystemUI/res/values-night/colors.xml @@ -42,6 +42,10 @@ <!-- The color of the text inside a notification --> <color name="notification_primary_text_color">@*android:color/notification_primary_text_color_dark</color> + <color name="notification_guts_selection_bg">#202124</color> + <color name="notification_guts_selection_border">#669DF6</color> + <color name="notification_guts_link_icon_tint">@color/GM2_grey_200</color> + <!-- The color of the background in the top part of QSCustomizer --> <color name="qs_customize_background">@color/GM2_grey_900</color> diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index 290d75b4de9c..b2a507549cce 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -88,6 +88,10 @@ <!-- The "inside" of a notification, reached via longpress --> <color name="notification_guts_bg_color">@color/GM2_grey_50</color> + <color name="notification_guts_selection_bg">#FFFFFF</color> + <color name="notification_guts_selection_border">#4285F4</color> + <color name="notification_guts_link_icon_tint">@color/GM2_grey_900</color> + <color name="assist_orb_color">#ffffff</color> <color name="keyguard_user_switcher_background_gradient_color">#77000000</color> @@ -163,4 +167,7 @@ <color name="GM2_red_300">#F28B82</color> <color name="GM2_red_500">#B71C1C</color> + + <color name="GM2_yellow_500">#FFFBBC04</color> + <color name="GM2_green_500">#FF34A853</color> </resources> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index f8295ebff027..b0afe75ebc68 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -109,12 +109,12 @@ <!-- The default tiles to display in QuickSettings --> <string name="quick_settings_tiles_default" translatable="false"> - wifi,bt,dnd,flashlight,rotation,battery,cell,airplane,cast,sensorprivacy + wifi,bt,dnd,flashlight,rotation,battery,cell,airplane,cast </string> <!-- Tiles native to System UI. Order should match "quick_settings_tiles_default" --> <string name="quick_settings_tiles_stock" translatable="false"> - wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location,hotspot,inversion,saver,work,cast,night,sensorprivacy + wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location,hotspot,inversion,saver,work,cast,night </string> <!-- The tiles to display in QuickSettings --> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 6eb279affc4f..a02469ee9fb9 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -38,6 +38,14 @@ <dimen name="navigation_handle_sample_horizontal_margin">10dp</dimen> <dimen name="navigation_home_handle_width">72dp</dimen> + + <!-- Size of the nav bar edge panels, should be greater to the + edge sensitivity + the drag threshold --> + <dimen name="navigation_edge_panel_width">52dp</dimen> + <dimen name="navigation_edge_panel_height">52dp</dimen> + <!-- The threshold to drag to trigger the edge action --> + <dimen name="navigation_edge_action_drag_threshold">16dp</dimen> + <!-- Luminance threshold to determine black/white contrast for the navigation affordances --> <item name="navigation_luminance_threshold" type="dimen" format="float">0.5</item> <!-- Luminance change threshold that allows applying new value if difference was exceeded --> @@ -171,7 +179,7 @@ <dimen name="notification_menu_icon_padding">20dp</dimen> <!-- The vertical space around the buttons in the inline settings --> - <dimen name="notification_guts_button_spacing">6dp</dimen> + <dimen name="notification_guts_button_spacing">12dp</dimen> <!-- Extra horizontal space for properly aligning guts buttons with the notification content --> <dimen name="notification_guts_button_side_margin">8dp</dimen> @@ -188,6 +196,18 @@ <!-- The height of the header in inline settings --> <dimen name="notification_guts_header_height">24dp</dimen> + <!-- The text size of the header in inline settings --> + <dimen name="notification_guts_header_text_size">16sp</dimen> + + <!-- The horizontal space between items in the alert selections in the inline settings --> + <dimen name="notification_guts_option_horizontal_padding">15dp</dimen> + + <!-- The vertical space between items in the alert selections in the inline settings --> + <dimen name="notification_guts_option_vertical_padding">15dp</dimen> + + <!-- The vertical space between the alert selections in the inline settings --> + <dimen name="notification_guts_option_vertical_margin">6dp</dimen> + <!-- The minimum height for the snackbar shown after the snooze option has been chosen. --> <dimen name="snooze_snackbar_min_height">56dp</dimen> @@ -1027,6 +1047,12 @@ <!-- How much each bubble is elevated. --> <dimen name="bubble_elevation">1dp</dimen> + <!-- How much the bubble flyout text container is elevated. --> + <dimen name="bubble_flyout_elevation">4dp</dimen> + <!-- How much padding is around the flyout text. --> + <dimen name="bubble_flyout_padding">16dp</dimen> + <!-- The maximum width of a bubble flyout. --> + <dimen name="bubble_flyout_maxwidth">200dp</dimen> <!-- Padding around a collapsed bubble --> <dimen name="bubble_view_padding">0dp</dimen> <!-- Padding between bubbles when displayed in expanded state --> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 0411d015fd63..f47d4b5aac6d 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -670,6 +670,9 @@ <item quantity="other"><xliff:g id="number" example="3">%s</xliff:g> more notifications inside.</item> </plurals> + <!-- Format to use to summarize a message from a contact in a single line of text. For example: "Julia: How's it going?". [CHAR LIMIT=NONE] --> + <string name="notification_summary_message_format"><xliff:g id="contact_name" example="Julia">%1$s</xliff:g>: <xliff:g id="message_content" example="How is it going?">%2$s</xliff:g></string> + <!-- Content description of button in notification inspector for system settings relating to notifications from this application [CHAR LIMIT=NONE] --> <string name="status_bar_notification_inspect_item_title">Notification settings</string> @@ -1599,7 +1602,7 @@ <string name="inline_done_button">Done</string> <!-- Notification Inline controls: button to dismiss the blocking helper [CHAR_LIMIT=20] --> - <string name="inline_ok_button">OK</string> + <string name="inline_ok_button">Apply</string> <!-- Notification Inline controls: continue receiving notifications prompt, channel level --> <string name="inline_keep_showing">Keep showing these notifications?</string> @@ -1620,17 +1623,20 @@ <string name="inline_minimize_button">Minimize</string> <!-- Notification inline controls: button to show notifications silently, without alerting the user [CHAR_LIMIT=35] --> - <string name="inline_silent_button_silent">Show silently</string> + <string name="inline_silent_button_silent">Gentle</string> <!-- Notification inline controls: button to continue showing notifications silently [CHAR_LIMIT=35] --> <string name="inline_silent_button_stay_silent">Stay silent</string> <!-- Notification inline controls: button to make notifications alert the user [CHAR_LIMIT=35] --> - <string name="inline_silent_button_alert">Alert</string> + <string name="inline_silent_button_alert">Interruptive</string> <!-- Notification inline controls: button to continue alerting the user when notifications arrive [CHAR_LIMIT=35] --> <string name="inline_silent_button_keep_alerting">Keep alerting</string> + <!-- Notification inline controls: button to show block screen [CHAR_LIMIT=35] --> + <string name="inline_turn_off_notifications">Turn off notifications</string> + <!-- Notification Inline controls: continue receiving notifications prompt, app level --> <string name="inline_keep_showing_app">Keep showing notifications from this app?</string> @@ -1641,11 +1647,14 @@ <string name="hint_text_silent">Silent notifications appear in the shade, but do not appear on the lock screen, present a banner, or play a sound.</string> <!-- Hint text for alert button in the interruptiveness settings [CHAR_LIMIT=NONE]--> - <string name="hint_text_alert">Alerted notifications appear in the shade, on the lock screen, present a banner, and play a sound.</string> + <string name="hint_text_alert">These notifications will make a sound and show in the notification drawer, status bar, and lock screen</string> <!-- Notification: Control panel: Label that displays when the app's notifications cannot be blocked. --> <string name="notification_unblockable_desc">These notifications can\'t be turned off</string> + <!-- Notification: Control panel: label that displays when viewing settings for a group of notifications posted to multiple channels. --> + <string name="notification_multichannel_desc">This group of notifications cannot be configured here</string> + <!-- Notification: Control panel: Label for the app that posted this notification, if it's not the package that the notification was posted for --> <string name="notification_delegate_header">via <xliff:g id="app_name" example="YouTube">%1$s</xliff:g></string> @@ -2401,5 +2410,4 @@ <string name="bubble_accessibility_action_move_bottom_left">Move bottom left</string> <!-- Action in accessibility menu to move the stack of bubbles to the bottom right of the screen. [CHAR LIMIT=30]--> <string name="bubble_accessibility_action_move_bottom_right">Move bottom right</string> - </resources> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 7c123eff2044..2ff481f7c8fd 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -435,6 +435,7 @@ </style> <style name="TextAppearance.NotificationInfo.Primary"> + <item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item> <item name="android:textSize">16sp</item> <item name="android:alpha">0.87</item> </style> @@ -471,16 +472,12 @@ </style> <style name="TextAppearance.NotificationInfo.Button"> - <item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item> - <item name="android:textSize">14sp</item> + <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item> + <item name="android:textSize">16sp</item> <item name="android:textColor">?android:attr/colorAccent</item> <item name="android:background">@drawable/btn_borderless_rect</item> <item name="android:gravity">center</item> <item name="android:focusable">true</item> - <item name="android:paddingTop">@dimen/notification_guts_button_vertical_padding</item> - <item name="android:paddingBottom">@dimen/notification_guts_button_vertical_padding</item> - <item name="android:paddingLeft">@dimen/notification_guts_button_horizontal_padding</item> - <item name="android:paddingRight">@dimen/notification_guts_button_horizontal_padding</item> </style> <style name="TextAppearance.HeadsUpStatusBarText" diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl index ce615b6dd3e4..04701bcf7c77 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl @@ -119,4 +119,14 @@ oneway interface IOverviewProxy { */ void onAssistantAvailable(boolean available) = 13; + /** + * Sent when the assistant changes how visible it is to the user. + */ + void onAssistantVisibilityChanged(float visibility) = 14; + + /* + * Sent when back is triggered. + */ + void onBackAction(boolean completed, int downX, int downY, boolean isButton, + boolean gestureSwipeLeft) = 15; } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl index 953816e8bf12..5764fe8cc42d 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl @@ -92,4 +92,9 @@ interface ISystemUiProxy { * Start the assistant. */ void startAssistant(in Bundle bundle) = 13; + + /** + * Creates a new gesture monitor + */ + Bundle monitorGestureInput(String name, int displayId) = 14; } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputMonitorCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputMonitorCompat.java new file mode 100644 index 000000000000..ddca7234c98f --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputMonitorCompat.java @@ -0,0 +1,66 @@ +/** + * Copyright (C) 2019 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.systemui.shared.system; + +import android.os.Bundle; +import android.os.Looper; +import android.view.Choreographer; +import android.view.InputMonitor; + +import com.android.systemui.shared.system.InputChannelCompat.InputEventListener; +import com.android.systemui.shared.system.InputChannelCompat.InputEventReceiver; + +/** + * @see android.view.InputMonitor + */ +public class InputMonitorCompat { + + private final InputMonitor mInputMonitor; + + private InputMonitorCompat(InputMonitor monitor) { + mInputMonitor = monitor; + } + + /** + * @see InputMonitor#pilferPointers() + */ + public void pilferPointers() { + mInputMonitor.pilferPointers(); + } + + /** + * @see InputMonitor#dispose() + */ + public void dispose() { + mInputMonitor.dispose(); + } + + /** + * @see InputMonitor#getInputChannel() + */ + public InputEventReceiver getInputReceiver(Looper looper, Choreographer choreographer, + InputEventListener listener) { + return new InputEventReceiver(mInputMonitor.getInputChannel(), looper, choreographer, + listener); + } + + /** + * Gets the input monitor stored in a bundle + */ + public static InputMonitorCompat fromBundle(Bundle bundle, String key) { + return new InputMonitorCompat((InputMonitor) bundle.getParcelable(key)); + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java index d0c17b7f6738..1076e73d1072 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java @@ -24,6 +24,8 @@ import android.content.Context; import android.content.res.Resources; import android.view.WindowManagerPolicyConstants; +import com.android.internal.policy.ScreenDecorationsUtils; + /** * Various shared constants between Launcher and SysUI as part of quickstep */ @@ -31,6 +33,7 @@ public class QuickStepContract { public static final String KEY_EXTRA_SYSUI_PROXY = "extra_sysui_proxy"; public static final String KEY_EXTRA_INPUT_CHANNEL = "extra_input_channel"; + public static final String KEY_EXTRA_INPUT_MONITOR = "extra_input_monitor"; public static final String KEY_EXTRA_WINDOW_CORNER_RADIUS = "extra_window_corner_radius"; public static final String KEY_EXTRA_SUPPORTS_WINDOW_CORNERS = "extra_supports_window_corners"; @@ -129,5 +132,19 @@ public class QuickStepContract { com.android.internal.R.dimen.config_backGestureInset); } + /** + * Corner radius that should be used on windows in order to cover the display. + * These values are expressed in pixels because they should not respect display or font + * scaling, this means that we don't have to reload them on config changes. + */ + public static float getWindowCornerRadius(Resources resources) { + return ScreenDecorationsUtils.getWindowCornerRadius(resources); + } + /** + * If live rounded corners are supported on windows. + */ + public static boolean supportsRoundedCornersOnWindows(Resources resources) { + return ScreenDecorationsUtils.supportsRoundedCornersOnWindows(resources); + } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java index fd92e9e72d2e..b738b576ef2f 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java @@ -187,6 +187,7 @@ public class KeyguardClockSwitch extends RelativeLayout { mBigClockContainer.removeAllViews(); updateBigClockVisibility(); } + mClockPlugin.onDestroyView(); mClockPlugin = null; } if (plugin == null) { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java index 8ebe1ae80d26..c1bf4d4dd78c 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java @@ -20,6 +20,8 @@ import static android.app.slice.Slice.HINT_LIST_ITEM; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; +import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT; + import android.animation.LayoutTransition; import android.animation.ObjectAnimator; import android.animation.PropertyValuesHolder; @@ -60,6 +62,7 @@ import com.android.systemui.Dependency; import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.keyguard.KeyguardSliceProvider; +import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.tuner.TunerService; import com.android.systemui.util.wakelock.KeepAwakeAnimationListener; @@ -70,6 +73,9 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import javax.inject.Inject; +import javax.inject.Named; + /** * View visible under the clock on the lock screen and AoD. */ @@ -80,6 +86,8 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe public static final int DEFAULT_ANIM_DURATION = 550; private final HashMap<View, PendingIntent> mClickActions; + private final ActivityStarter mActivityStarter; + private final ConfigurationController mConfigurationController; private Uri mKeyguardSliceUri; @VisibleForTesting TextView mTitle; @@ -99,16 +107,10 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe private final int mRowWithHeaderPadding; private final int mRowPadding; - public KeyguardSliceView(Context context) { - this(context, null, 0); - } - - public KeyguardSliceView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public KeyguardSliceView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + @Inject + public KeyguardSliceView(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs, + ActivityStarter activityStarter, ConfigurationController configurationController) { + super(context, attrs); TunerService tunerService = Dependency.get(TunerService.class); tunerService.addTunable(this, Settings.Secure.KEYGUARD_SLICE_URI); @@ -117,6 +119,8 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe mRowPadding = context.getResources().getDimensionPixelSize(R.dimen.subtitle_clock_padding); mRowWithHeaderPadding = context.getResources() .getDimensionPixelSize(R.dimen.header_subtitle_padding); + mActivityStarter = activityStarter; + mConfigurationController = configurationController; LayoutTransition transition = new LayoutTransition(); transition.setStagger(LayoutTransition.CHANGE_APPEARING, DEFAULT_ANIM_DURATION / 2); @@ -137,6 +141,7 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe mRow = findViewById(R.id.row); mTextColor = Utils.getColorAttrDefaultColor(mContext, R.attr.wallpaperTextColor); mIconSize = (int) mContext.getResources().getDimension(R.dimen.widget_icon_size); + mTitle.setOnClickListener(this); } @Override @@ -146,7 +151,7 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe mDisplayId = getDisplay().getDisplayId(); // Make sure we always have the most current slice mLiveData.observeForever(this); - Dependency.get(ConfigurationController.class).addCallback(this); + mConfigurationController.addCallback(this); } @Override @@ -157,7 +162,7 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe if (mDisplayId == DEFAULT_DISPLAY) { mLiveData.removeObserver(this); } - Dependency.get(ConfigurationController.class).removeCallback(this); + mConfigurationController.removeCallback(this); } /** @@ -179,6 +184,7 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe Trace.endSection(); return; } + mClickActions.clear(); ListContent lc = new ListContent(getContext(), mSlice); SliceContent headerContent = lc.getHeader(); @@ -201,9 +207,12 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe SliceItem mainTitle = header.getTitleItem(); CharSequence title = mainTitle != null ? mainTitle.getText() : null; mTitle.setText(title); + if (header.getPrimaryAction() != null + && header.getPrimaryAction().getAction() != null) { + mClickActions.put(mTitle, header.getPrimaryAction().getAction()); + } } - mClickActions.clear(); final int subItemsCount = subItems.size(); final int blendedColor = getTextColor(); final int startIndex = mHasHeader ? 1 : 0; // First item is header; skip it @@ -289,11 +298,7 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe public void onClick(View v) { final PendingIntent action = mClickActions.get(v); if (action != null) { - try { - action.send(); - } catch (PendingIntent.CanceledException e) { - Log.i(TAG, "Pending intent cancelled, nothing to launch", e); - } + mActivityStarter.startPendingIntentDismissingKeyguard(action); } } diff --git a/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java b/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java index 147def392594..d30f45f212b9 100644 --- a/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java +++ b/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java @@ -102,6 +102,16 @@ public class BubbleClockController implements ClockPlugin { } @Override + public void onDestroyView() { + mView = null; + mDigitalClock = null; + mAnalogClock = null; + mLockClockContainer = null; + mLockClock = null; + mDarkController = null; + } + + @Override public String getName() { return "bubble"; } diff --git a/packages/SystemUI/src/com/android/keyguard/clock/DefaultClockController.java b/packages/SystemUI/src/com/android/keyguard/clock/DefaultClockController.java index 73414b30432f..488cb2730d3b 100644 --- a/packages/SystemUI/src/com/android/keyguard/clock/DefaultClockController.java +++ b/packages/SystemUI/src/com/android/keyguard/clock/DefaultClockController.java @@ -93,6 +93,13 @@ public class DefaultClockController implements ClockPlugin { } @Override + public void onDestroyView() { + mView = null; + mTextTime = null; + mTextDate = null; + } + + @Override public String getName() { return "default"; } diff --git a/packages/SystemUI/src/com/android/keyguard/clock/StretchAnalogClockController.java b/packages/SystemUI/src/com/android/keyguard/clock/StretchAnalogClockController.java index ea9f0cd3c17d..81b6a600f398 100644 --- a/packages/SystemUI/src/com/android/keyguard/clock/StretchAnalogClockController.java +++ b/packages/SystemUI/src/com/android/keyguard/clock/StretchAnalogClockController.java @@ -101,6 +101,17 @@ public class StretchAnalogClockController implements ClockPlugin { mDarkController = new CrossFadeDarkController(mDigitalClock, mLockClock); } + + @Override + public void onDestroyView() { + mBigClockView = null; + mDigitalClock = null; + mAnalogClock = null; + mView = null; + mLockClock = null; + mDarkController = null; + } + @Override public String getName() { return "stretch"; diff --git a/packages/SystemUI/src/com/android/keyguard/clock/TypeClockController.java b/packages/SystemUI/src/com/android/keyguard/clock/TypeClockController.java index 67c0989b49c4..1c6b38b18b05 100644 --- a/packages/SystemUI/src/com/android/keyguard/clock/TypeClockController.java +++ b/packages/SystemUI/src/com/android/keyguard/clock/TypeClockController.java @@ -99,6 +99,14 @@ public class TypeClockController implements ClockPlugin { } @Override + public void onDestroyView() { + mView = null; + mTypeClock = null; + mLockClock = null; + mDarkController = null; + } + + @Override public String getName() { return "type"; } diff --git a/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java b/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java index 72ab02c21192..822538b0ac0a 100644 --- a/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java +++ b/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java @@ -561,4 +561,29 @@ public class HardwareUiLayout extends MultiListLayout implements Tunable { inoutInfo.contentInsets.set(mList.getLeft(), mList.getTop(), 0, getBottom() - mList.getBottom()); }; + + private float getAnimationDistance() { + return getContext().getResources().getDimension( + com.android.systemui.R.dimen.global_actions_panel_width) / 2; + } + + @Override + public float getAnimationOffsetX() { + if (RotationUtils.getRotation(mContext) == ROTATION_NONE) { + return getAnimationDistance(); + } + return 0; + } + + @Override + public float getAnimationOffsetY() { + switch (RotationUtils.getRotation(getContext())) { + case RotationUtils.ROTATION_LANDSCAPE: + return -getAnimationDistance(); + case RotationUtils.ROTATION_SEASCAPE: + return getAnimationDistance(); + default: // Portrait + return 0; + } + } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/MultiListLayout.java b/packages/SystemUI/src/com/android/systemui/MultiListLayout.java index d063a0f4086e..a30b681ed7cc 100644 --- a/packages/SystemUI/src/com/android/systemui/MultiListLayout.java +++ b/packages/SystemUI/src/com/android/systemui/MultiListLayout.java @@ -148,6 +148,16 @@ public abstract class MultiListLayout extends LinearLayout { } /** + * Get the X offset in pixels for use when animating the view onto or off of the screen. + */ + public abstract float getAnimationOffsetX(); + + /** + * Get the Y offset in pixels for use when animating the view onto or off of the screen. + */ + public abstract float getAnimationOffsetY(); + + /** * Adapter class for converting items into child views for MultiListLayout and handling * callbacks for input events. */ diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java index 3aa9f73939ac..a5aed87f5e21 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java @@ -395,7 +395,6 @@ public class BubbleController implements BubbleExpandedView.OnBubbleBlockedListe } if (shouldAutoBubbleForFlags(mContext, entry) || shouldBubble(entry)) { // TODO: handle group summaries - entry.setIsBubble(true); boolean suppressNotification = entry.getBubbleMetadata() != null && entry.getBubbleMetadata().getSuppressInitialNotification() && isForegroundApp(entry.notification.getPackageName()); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index be55829869eb..de4605b55272 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -42,7 +42,9 @@ import android.view.ViewTreeObserver; import android.view.WindowInsets; import android.view.WindowManager; import android.view.accessibility.AccessibilityNodeInfo; +import android.view.animation.AccelerateDecelerateInterpolator; import android.widget.FrameLayout; +import android.widget.TextView; import androidx.annotation.MainThread; import androidx.annotation.Nullable; @@ -70,6 +72,13 @@ public class BubbleStackView extends FrameLayout { private static final String TAG = "BubbleStackView"; private static final boolean DEBUG = false; + /** Duration of the flyout alpha animations. */ + private static final int FLYOUT_ALPHA_ANIMATION_DURATION = 100; + + /** How long to wait, in milliseconds, before hiding the flyout. */ + @VisibleForTesting + static final int FLYOUT_HIDE_AFTER = 5000; + /** * Interface to synchronize {@link View} state and the screen. * @@ -119,6 +128,14 @@ public class BubbleStackView extends FrameLayout { private FrameLayout mExpandedViewContainer; + private View mFlyout; + private TextView mFlyoutText; + /** Spring animation for the flyout. */ + private SpringAnimation mFlyoutSpring; + /** Runnable that fades out the flyout and then sets it to GONE. */ + private Runnable mHideFlyout = + () -> mFlyout.animate().alpha(0f).withEndAction(() -> mFlyout.setVisibility(GONE)); + private int mBubbleSize; private int mBubblePadding; private int mExpandedAnimateXDistance; @@ -131,6 +148,9 @@ public class BubbleStackView extends FrameLayout { private boolean mIsExpanded; private boolean mImeVisible; + /** Whether the stack is currently being dragged. */ + private boolean mIsDragging = false; + private BubbleTouchHandler mTouchHandler; private BubbleController.BubbleExpandListener mExpandListener; private BubbleExpandedView.OnBubbleBlockedListener mBlockedListener; @@ -221,6 +241,17 @@ public class BubbleStackView extends FrameLayout { mExpandedViewContainer.setClipChildren(false); addView(mExpandedViewContainer); + mFlyout = mInflater.inflate(R.layout.bubble_flyout, this, false); + mFlyout.setVisibility(GONE); + mFlyout.animate() + .setDuration(FLYOUT_ALPHA_ANIMATION_DURATION) + .setInterpolator(new AccelerateDecelerateInterpolator()); + addView(mFlyout); + + mFlyoutText = mFlyout.findViewById(R.id.bubble_flyout_text); + + mFlyoutSpring = new SpringAnimation(mFlyout, DynamicAnimation.TRANSLATION_X); + mExpandedViewXAnim = new SpringAnimation(mExpandedViewContainer, DynamicAnimation.TRANSLATION_X); mExpandedViewXAnim.setSpring( @@ -448,6 +479,8 @@ public class BubbleStackView extends FrameLayout { requestUpdate(); logBubbleEvent(b, StatsLog.BUBBLE_UICHANGED__ACTION__POSTED); + + animateInFlyoutForBubble(b); } /** @@ -549,6 +582,7 @@ public class BubbleStackView extends FrameLayout { mBubbleContainer.moveViewTo(b.iconView, 0); } requestUpdate(); + animateInFlyoutForBubble(b /* bubble */); } if (mIsExpanded && entry.equals(mExpandedBubble.entry)) { entry.setShowInShadeWhenBubble(false); @@ -577,11 +611,18 @@ public class BubbleStackView extends FrameLayout { } // Outside parts of view we care about. return null; + } else if (isIntersecting(mFlyout, x, y)) { + return mFlyout; } - // If we're collapsed, the stack is always the target. + + // If it wasn't an individual bubble in the expanded state, or the flyout, it's the stack. return this; } + public View getFlyoutView() { + return mFlyout; + } + /** * Collapses the stack of bubbles. * <p> @@ -622,6 +663,8 @@ public class BubbleStackView extends FrameLayout { */ private void animateExpansion(boolean shouldExpand) { if (mIsExpanded != shouldExpand) { + hideFlyoutImmediate(); + mIsExpanded = shouldExpand; updateExpandedBubble(); applyCurrentState(); @@ -735,6 +778,9 @@ public class BubbleStackView extends FrameLayout { mStackAnimationController.cancelStackPositionAnimations(); mBubbleContainer.setController(mStackAnimationController); + hideFlyoutImmediate(); + + mIsDragging = true; } void onDragged(float x, float y) { @@ -747,6 +793,7 @@ public class BubbleStackView extends FrameLayout { void onDragFinish(float x, float y, float velX, float velY) { // TODO: Add fling to bottom to dismiss. + mIsDragging = false; if (mIsExpanded || mIsExpansionAnimating) { return; @@ -797,6 +844,47 @@ public class BubbleStackView extends FrameLayout { } } + /** + * Animates in the flyout for the given bubble, if available, and then hides it after some time. + */ + @VisibleForTesting + void animateInFlyoutForBubble(Bubble bubble) { + final CharSequence updateMessage = bubble.entry.getUpdateMessage(getContext()); + + // Show the message if one exists, and we're not expanded or animating expansion. + if (updateMessage != null && !isExpanded() && !mIsExpansionAnimating && !mIsDragging) { + final PointF stackPos = mStackAnimationController.getStackPosition(); + + mFlyoutText.setText(updateMessage); + mFlyout.measure(WRAP_CONTENT, WRAP_CONTENT); + mFlyout.post(() -> { + final boolean onLeft = mStackAnimationController.isStackOnLeftSide(); + final float destinationX = onLeft + ? stackPos.x + mBubbleSize + mBubblePadding + : stackPos.x - mFlyout.getMeasuredWidth(); + + // Translate towards the stack slightly, then spring out from the stack. + mFlyout.setTranslationX(destinationX + (onLeft ? -mBubblePadding : mBubblePadding)); + mFlyout.setTranslationY(stackPos.y); + mFlyout.setAlpha(0f); + + mFlyout.setVisibility(VISIBLE); + + mFlyout.animate().alpha(1f); + mFlyoutSpring.animateToFinalPosition(destinationX); + + mFlyout.removeCallbacks(mHideFlyout); + mFlyout.postDelayed(mHideFlyout, FLYOUT_HIDE_AFTER); + }); + } + } + + /** Hide the flyout immediately and cancel any pending hide runnables. */ + private void hideFlyoutImmediate() { + mFlyout.removeCallbacks(mHideFlyout); + mHideFlyout.run(); + } + @Override public void getBoundsOnScreen(Rect outRect) { if (!mIsExpanded) { @@ -806,6 +894,12 @@ public class BubbleStackView extends FrameLayout { } else { mBubbleContainer.getBoundsOnScreen(outRect); } + + if (mFlyout.getVisibility() == View.VISIBLE) { + final Rect flyoutBounds = new Rect(); + mFlyout.getBoundsOnScreen(flyoutBounds); + outRect.union(flyoutBounds); + } } private int getStatusBarHeight() { diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java index a7170d0256e3..baeedaacdd95 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java @@ -86,6 +86,7 @@ class BubbleTouchHandler implements View.OnTouchListener { } final boolean isStack = mStack.equals(mTouchedView); + final boolean isFlyout = mStack.getFlyoutView().equals(mTouchedView); final float rawX = event.getRawX(); final float rawY = event.getRawY(); @@ -96,14 +97,18 @@ class BubbleTouchHandler implements View.OnTouchListener { case MotionEvent.ACTION_DOWN: trackMovement(event); - mDismissViewController.createDismissTarget(); - mHandler.postDelayed(mShowDismissAffordance, SHOW_TARGET_DELAY); - mTouchDown.set(rawX, rawY); + if (!isFlyout) { + mDismissViewController.createDismissTarget(); + mHandler.postDelayed(mShowDismissAffordance, SHOW_TARGET_DELAY); + } + if (isStack) { mViewPositionOnTouchDown.set(mStack.getStackPosition()); mStack.onDragStart(); + } else if (isFlyout) { + // TODO(b/129768381): Make the flyout dismissable with a gesture. } else { mViewPositionOnTouchDown.set( mTouchedView.getTranslationX(), mTouchedView.getTranslationY()); @@ -123,6 +128,8 @@ class BubbleTouchHandler implements View.OnTouchListener { if (mMovedEnough) { if (isStack) { mStack.onDragged(viewX, viewY); + } else if (isFlyout) { + // TODO(b/129768381): Make the flyout dismissable with a gesture. } else { mStack.onBubbleDragged(mTouchedView, viewX, viewY); } @@ -141,6 +148,11 @@ class BubbleTouchHandler implements View.OnTouchListener { trackMovement(event); if (mInDismissTarget && isStack) { mController.dismissStack(BubbleController.DISMISS_USER_GESTURE); + } else if (isFlyout) { + // TODO(b/129768381): Expand if tapped, dismiss if swiped away. + if (!mStack.isExpanded() && !mMovedEnough) { + mStack.expandStack(); + } } else if (mMovedEnough) { mVelocityTracker.computeCurrentVelocity(/* maxVelocity */ 1000); final float velX = mVelocityTracker.getXVelocity(); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java index 3b9164d60c5c..84b86bf9b69f 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java @@ -27,7 +27,6 @@ import android.graphics.drawable.Icon; import android.graphics.drawable.InsetDrawable; import android.util.AttributeSet; import android.widget.FrameLayout; -import android.widget.TextView; import com.android.internal.graphics.ColorUtils; import com.android.systemui.Interpolators; @@ -49,7 +48,6 @@ public class BubbleView extends FrameLayout { private Context mContext; private BadgedImageView mBadgedImageView; - private TextView mMessageView; private int mPadding; private int mIconInset; @@ -78,10 +76,7 @@ public class BubbleView extends FrameLayout { @Override protected void onFinishInflate() { super.onFinishInflate(); - mBadgedImageView = (BadgedImageView) findViewById(R.id.bubble_image); - mMessageView = (TextView) findViewById(R.id.message_view); - mMessageView.setVisibility(GONE); - mMessageView.setPivotX(0); + mBadgedImageView = findViewById(R.id.bubble_image); } @Override @@ -89,33 +84,6 @@ public class BubbleView extends FrameLayout { super.onAttachedToWindow(); } - @Override - protected void onMeasure(int widthSpec, int heightSpec) { - measureChild(mBadgedImageView, widthSpec, heightSpec); - measureChild(mMessageView, widthSpec, heightSpec); - boolean messageGone = mMessageView.getVisibility() == GONE; - int imageHeight = mBadgedImageView.getMeasuredHeight(); - int imageWidth = mBadgedImageView.getMeasuredWidth(); - int messageHeight = messageGone ? 0 : mMessageView.getMeasuredHeight(); - int messageWidth = messageGone ? 0 : mMessageView.getMeasuredWidth(); - setMeasuredDimension( - getPaddingStart() + imageWidth + mPadding + messageWidth + getPaddingEnd(), - getPaddingTop() + Math.max(imageHeight, messageHeight) + getPaddingBottom()); - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - left = getPaddingStart(); - top = getPaddingTop(); - int imageWidth = mBadgedImageView.getMeasuredWidth(); - int imageHeight = mBadgedImageView.getMeasuredHeight(); - int messageWidth = mMessageView.getMeasuredWidth(); - int messageHeight = mMessageView.getMeasuredHeight(); - mBadgedImageView.layout(left, top, left + imageWidth, top + imageHeight); - mMessageView.layout(left + imageWidth + mPadding, top, - left + imageWidth + mPadding + messageWidth, top + messageHeight); - } - /** * Populates this view with a notification. * <p> diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java index c39503129454..78c4fc17c655 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java @@ -157,6 +157,15 @@ public class StackAnimationController extends return mStackPosition; } + /** Whether the stack is on the left side of the screen. */ + public boolean isStackOnLeftSide() { + if (mLayout != null) { + return mStackPosition.x - mIndividualBubbleSize / 2 < mLayout.getWidth() / 2; + } else { + return false; + } + } + /** * Flings the stack starting with the given velocities, springing it to the nearest edge * afterward. diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index dcacd0fbc644..e22b24e2ed46 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -1127,10 +1127,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } protected int getActionLayoutId(Context context) { - if (isGridEnabled(context)) { - return com.android.systemui.R.layout.global_actions_grid_item; - } - return com.android.systemui.R.layout.global_actions_item; + return com.android.systemui.R.layout.global_actions_grid_item; } public View create( @@ -1540,26 +1537,28 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, initializeLayout(); } - private boolean initializePanel() { + private boolean shouldUsePanel() { if (!isPanelEnabled(mContext) || mPanelController == null) { return false; } - View panelView = mPanelController.getPanelContent(); - if (panelView == null) { + if (mPanelController.getPanelContent() == null) { return false; } + return true; + } + + private void initializePanel() { FrameLayout panelContainer = new FrameLayout(mContext); FrameLayout.LayoutParams panelParams = new FrameLayout.LayoutParams( FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT); - panelContainer.addView(panelView, panelParams); + panelContainer.addView(mPanelController.getPanelContent(), panelParams); addContentView( panelContainer, new ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); - return true; } private void initializeLayout() { @@ -1578,8 +1577,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, mGlobalActionsLayout.setRotationListener(this::onRotate); mGlobalActionsLayout.setAdapter(mAdapter); - boolean panelEnabled = initializePanel(); - if (!panelEnabled) { + if (!shouldUsePanel()) { if (mBackgroundDrawable == null) { mBackgroundDrawable = new GradientDrawable(mContext); } @@ -1589,12 +1587,12 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, com.android.systemui.R.drawable.global_action_panel_scrim); mScrimAlpha = 1f; } - mGlobalActionsLayout.setSnapToEdge(panelEnabled); + mGlobalActionsLayout.setSnapToEdge(true); getWindow().setBackgroundDrawable(mBackgroundDrawable); } private int getGlobalActionsLayoutId(Context context) { - if (isGridEnabled(context)) { + if (isForceGridEnabled(context) || shouldUsePanel()) { if (RotationUtils.getRotation(context) == RotationUtils.ROTATION_SEASCAPE) { return com.android.systemui.R.layout.global_actions_grid_seascape; } @@ -1653,11 +1651,13 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, super.show(); mShowing = true; mBackgroundDrawable.setAlpha(0); - mGlobalActionsLayout.setTranslationX(getAnimTranslation()); + mGlobalActionsLayout.setTranslationX(mGlobalActionsLayout.getAnimationOffsetX()); + mGlobalActionsLayout.setTranslationY(mGlobalActionsLayout.getAnimationOffsetY()); mGlobalActionsLayout.setAlpha(0); mGlobalActionsLayout.animate() .alpha(1) .translationX(0) + .translationY(0) .setDuration(300) .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) .setUpdateListener(animation -> { @@ -1675,10 +1675,12 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } mShowing = false; mGlobalActionsLayout.setTranslationX(0); + mGlobalActionsLayout.setTranslationY(0); mGlobalActionsLayout.setAlpha(1); mGlobalActionsLayout.animate() .alpha(0) - .translationX(getAnimTranslation()) + .translationX(mGlobalActionsLayout.getAnimationOffsetX()) + .translationY(mGlobalActionsLayout.getAnimationOffsetY()) .setDuration(300) .withEndAction(super::dismiss) .setInterpolator(new LogAccelerateInterpolator()) @@ -1701,11 +1703,6 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } } - private float getAnimTranslation() { - return getContext().getResources().getDimension( - com.android.systemui.R.dimen.global_actions_panel_width) / 2; - } - @Override public void onColorsChanged(ColorExtractor extractor, int which) { if (mKeyguardShowing) { @@ -1731,17 +1728,19 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } public void onRotate(int from, int to) { - if (mShowing && isGridEnabled(mContext)) { + if (mShowing && (shouldUsePanel() || isForceGridEnabled(mContext))) { refreshDialog(); } } } /** - * Determines whether or not the Global Actions menu should use the newer grid-style layout. + * Determines whether or not the Global Actions menu should be forced to + * use the newer grid-style layout. */ - private static boolean isGridEnabled(Context context) { - return FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.GLOBAL_ACTIONS_GRID_ENABLED); + private static boolean isForceGridEnabled(Context context) { + return FeatureFlagUtils.isEnabled(context, + FeatureFlagUtils.FORCE_GLOBAL_ACTIONS_GRID_ENABLED); } /** diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsGridLayout.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsGridLayout.java index 9a0759c70b5b..f8825690056e 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsGridLayout.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsGridLayout.java @@ -16,6 +16,10 @@ package com.android.systemui.globalactions; +import static com.android.systemui.util.leak.RotationUtils.ROTATION_LANDSCAPE; +import static com.android.systemui.util.leak.RotationUtils.ROTATION_NONE; +import static com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE; + import android.content.Context; import android.text.TextUtils; import android.util.AttributeSet; @@ -85,8 +89,8 @@ public class GlobalActionsGridLayout extends MultiListLayout { int rotation = RotationUtils.getRotation(mContext); boolean reverse = false; // should we add items to parents in the reverse order? - if (rotation == RotationUtils.ROTATION_NONE - || rotation == RotationUtils.ROTATION_SEASCAPE) { + if (rotation == ROTATION_NONE + || rotation == ROTATION_SEASCAPE) { reverse = !reverse; // if we're in portrait or seascape, reverse items } if (TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()) @@ -125,9 +129,9 @@ public class GlobalActionsGridLayout extends MultiListLayout { private void updateSnapPosition() { if (mSnapToEdge) { setPadding(0, 0, 0, 0); - if (mRotation == RotationUtils.ROTATION_LANDSCAPE) { + if (mRotation == ROTATION_LANDSCAPE) { setGravity(Gravity.RIGHT); - } else if (mRotation == RotationUtils.ROTATION_SEASCAPE) { + } else if (mRotation == ROTATION_SEASCAPE) { setGravity(Gravity.LEFT); } else { setGravity(Gravity.BOTTOM); @@ -157,9 +161,9 @@ public class GlobalActionsGridLayout extends MultiListLayout { return getSeparatedView(); } else { switch (rotation) { - case RotationUtils.ROTATION_LANDSCAPE: + case ROTATION_LANDSCAPE: return getListView().getParentView(index, false, true); - case RotationUtils.ROTATION_SEASCAPE: + case ROTATION_SEASCAPE: return getListView().getParentView(index, true, true); default: return getListView().getParentView(index, false, false); @@ -174,4 +178,31 @@ public class GlobalActionsGridLayout extends MultiListLayout { public void setDivisionView(View v) { // do nothing } + + private float getAnimationDistance() { + int rows = getListView().getRowCount(); + float gridItemSize = getContext().getResources().getDimension( + com.android.systemui.R.dimen.global_actions_grid_item_height); + return rows * gridItemSize / 2; + } + + @Override + public float getAnimationOffsetX() { + switch (RotationUtils.getRotation(getContext())) { + case ROTATION_LANDSCAPE: + return getAnimationDistance(); + case ROTATION_SEASCAPE: + return -getAnimationDistance(); + default: // Portrait + return 0; + } + } + + @Override + public float getAnimationOffsetY() { + if (RotationUtils.getRotation(mContext) == ROTATION_NONE) { + return getAnimationDistance(); + } + return 0; + } } diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/ListGridLayout.java b/packages/SystemUI/src/com/android/systemui/globalactions/ListGridLayout.java index 048f80196781..9c71ffc0e73b 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/ListGridLayout.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/ListGridLayout.java @@ -109,7 +109,10 @@ public class ListGridLayout extends LinearLayout { } } - private int getRowCount() { + /** + * Get the number of rows which will be used to render children. + */ + public int getRowCount() { // special case for 3 to use a single row if (mExpectedCount == 3) { return 1; @@ -117,7 +120,10 @@ public class ListGridLayout extends LinearLayout { return (int) Math.round(Math.sqrt(mExpectedCount)); } - private int getColumnCount() { + /** + * Get the number of columns which will be used to render children. + */ + public int getColumnCount() { // special case for 3 to use a single row if (mExpectedCount == 3) { return 3; diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java index 3140e6db2442..3ec6cb78ecc1 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java @@ -795,7 +795,10 @@ public class PipTouchHandler { ? mExpandedMovementBounds : mNormalMovementBounds; try { - mPinnedStackController.setMinEdgeSize(isMenuExpanded ? mExpandedShortestEdgeSize : 0); + if (mPinnedStackController != null) { + mPinnedStackController.setMinEdgeSize( + isMenuExpanded ? mExpandedShortestEdgeSize : 0); + } } catch (RemoteException e) { Log.e(TAG, "Could not set minimized state", e); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java index 10eacbaefd9a..644664ecaeec 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java @@ -22,7 +22,6 @@ import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEX import android.content.Context; import android.content.Intent; -import android.content.pm.UserInfo; import android.content.res.Configuration; import android.database.ContentObserver; import android.graphics.PorterDuff.Mode; @@ -320,29 +319,13 @@ public class QSFooterImpl extends FrameLayout implements QSFooter, mSettingsContainer.findViewById(R.id.tuner_icon).setVisibility( TunerService.isTunerEnabled(mContext) ? View.VISIBLE : View.INVISIBLE); final boolean isDemo = UserManager.isDeviceInDemoMode(mContext); - mMultiUserSwitch.setVisibility(showUserSwitcher(isDemo) ? View.VISIBLE : View.INVISIBLE); + mMultiUserSwitch.setVisibility(showUserSwitcher() ? View.VISIBLE : View.INVISIBLE); mEditContainer.setVisibility(isDemo || !mExpanded ? View.INVISIBLE : View.VISIBLE); mSettingsButton.setVisibility(isDemo || !mExpanded ? View.INVISIBLE : View.VISIBLE); } - private boolean showUserSwitcher(boolean isDemo) { - if (!mExpanded || isDemo || !UserManager.supportsMultipleUsers()) { - return false; - } - UserManager userManager = UserManager.get(mContext); - if (userManager.hasUserRestriction(UserManager.DISALLOW_USER_SWITCH)) { - return false; - } - int switchableUserCount = 0; - for (UserInfo user : userManager.getUsers(true)) { - if (user.supportsSwitchToByUser()) { - ++switchableUserCount; - if (switchableUserCount > 1) { - return true; - } - } - } - return getResources().getBoolean(R.bool.qs_show_user_switcher_for_single_user); + private boolean showUserSwitcher() { + return mExpanded && mMultiUserSwitch.isMultiUserEnabled(); } private void updateListeners() { 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 415870c590a3..b1dfbb575ad7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java @@ -21,7 +21,7 @@ import static android.media.MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY; import android.app.Dialog; import android.content.Context; import android.content.Intent; -import android.media.projection.MediaProjectionInfo; +import android.media.MediaRouter.RouteInfo; import android.provider.Settings; import android.service.quicksettings.Tile; import android.util.Log; @@ -48,8 +48,9 @@ import com.android.systemui.statusbar.policy.CastController.CastDevice; import com.android.systemui.statusbar.policy.KeyguardMonitor; import com.android.systemui.statusbar.policy.NetworkController; +import java.util.ArrayList; import java.util.LinkedHashMap; -import java.util.Set; +import java.util.List; import javax.inject.Inject; @@ -128,35 +129,30 @@ public class CastTile extends QSTileImpl<BooleanState> { return; } - CastDevice activeProjection = getActiveDeviceMediaProjection(); - if (activeProjection == null) { - if (mKeyguard.isSecure() && !mKeyguard.canSkipBouncer()) { - mActivityStarter.postQSRunnableDismissingKeyguard(() -> { - showDetail(true); - }); - } else { + List<CastDevice> activeDevices = getActiveDevices(); + // We want to pop up the media route selection dialog if we either have no active devices + // (neither routes nor projection), or if we have an active route. In other cases, we assume + // that a projection is active. This is messy, but this tile never correctly handled the + // case where multiple devices were active :-/. + if (activeDevices.isEmpty() || (activeDevices.get(0).tag instanceof RouteInfo)) { + mActivityStarter.postQSRunnableDismissingKeyguard(() -> { showDetail(true); - } + }); } else { - mController.stopCasting(activeProjection); + mController.stopCasting(activeDevices.get(0)); } } - private CastDevice getActiveDeviceMediaProjection() { - CastDevice activeDevice = null; + private List<CastDevice> getActiveDevices() { + ArrayList<CastDevice> activeDevices = new ArrayList<>(); for (CastDevice device : mController.getCastDevices()) { if (device.state == CastDevice.STATE_CONNECTED || device.state == CastDevice.STATE_CONNECTING) { - activeDevice = device; - break; + activeDevices.add(device); } } - if (activeDevice != null && activeDevice.tag instanceof MediaProjectionInfo) { - return activeDevice; - } - - return null; + return activeDevices; } @Override @@ -187,14 +183,18 @@ public class CastTile extends QSTileImpl<BooleanState> { state.label = mContext.getString(R.string.quick_settings_cast_title); state.contentDescription = state.label; state.value = false; - final Set<CastDevice> devices = mController.getCastDevices(); + final List<CastDevice> devices = mController.getCastDevices(); boolean connecting = false; + // We always choose the first device that's in the CONNECTED state in the case where + // multiple devices are CONNECTED at the same time. for (CastDevice device : devices) { if (device.state == CastDevice.STATE_CONNECTED) { state.value = true; state.secondaryLabel = getDeviceName(device); state.contentDescription = state.contentDescription + "," + mContext.getString(R.string.accessibility_cast_name, state.label); + connecting = false; + break; } else if (device.state == CastDevice.STATE_CONNECTING) { connecting = true; } @@ -326,7 +326,7 @@ public class CastTile extends QSTileImpl<BooleanState> { return mItems; } - private void updateItems(Set<CastDevice> devices) { + private void updateItems(List<CastDevice> devices) { if (mItems == null) return; Item[] items = null; if (devices != null && !devices.isEmpty()) { diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index ead39c69730e..4d6693ff6613 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -24,7 +24,7 @@ import static android.view.MotionEvent.ACTION_UP; import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_DISABLE_SWIPE_UP; import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_SHOW_OVERVIEW_BUTTON; import static com.android.systemui.shared.system.NavigationBarCompat.InteractionType; -import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_INPUT_CHANNEL; +import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_INPUT_MONITOR; import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SUPPORTS_WINDOW_CORNERS; import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY; import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_WINDOW_CORNER_RADIUS; @@ -38,6 +38,7 @@ import android.content.IntentFilter; import android.content.ServiceConnection; import android.graphics.Rect; import android.graphics.Region; +import android.hardware.input.InputManager; import android.os.Binder; import android.os.Bundle; import android.os.Handler; @@ -47,7 +48,7 @@ import android.os.PatternMatcher; import android.os.RemoteException; import android.os.UserHandle; import android.util.Log; -import android.view.InputChannel; +import android.view.InputMonitor; import android.view.MotionEvent; import com.android.internal.policy.ScreenDecorationsUtils; @@ -58,7 +59,6 @@ import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener; import com.android.systemui.shared.recents.IOverviewProxy; import com.android.systemui.shared.recents.ISystemUiProxy; import com.android.systemui.shared.system.ActivityManagerWrapper; -import com.android.systemui.shared.system.InputChannelCompat.InputEventDispatcher; import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.stackdivider.Divider; import com.android.systemui.statusbar.phone.StatusBar; @@ -115,8 +115,6 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis private float mWindowCornerRadius; private boolean mSupportsRoundedCornersOnWindows; - private InputEventDispatcher mInputEventDispatcher; - private ISystemUiProxy mSysUiProxy = new ISystemUiProxy.Stub() { public void startScreenPinning(int taskId) { @@ -295,6 +293,22 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis } } + public Bundle monitorGestureInput(String name, int displayId) { + if (!verifyCaller("monitorGestureInput")) { + return null; + } + long token = Binder.clearCallingIdentity(); + try { + InputMonitor monitor = + InputManager.getInstance().monitorGestureInput(name, displayId); + Bundle result = new Bundle(); + result.putParcelable(KEY_EXTRA_INPUT_MONITOR, monitor); + return result; + } finally { + Binder.restoreCallingIdentity(token); + } + } + private boolean verifyCaller(String reason) { final int callerId = Binder.getCallingUserHandle().getIdentifier(); if (callerId != mCurrentBoundedUserId) { @@ -341,23 +355,16 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis } catch (RemoteException e) { Log.e(TAG_OPS, "Lost connection to launcher service", e); } - try { - mOverviewProxy.onBind(mSysUiProxy); - } catch (RemoteException e) { - mCurrentBoundedUserId = -1; - Log.e(TAG_OPS, "Failed to call onBind()", e); - } Bundle params = new Bundle(); params.putBinder(KEY_EXTRA_SYSUI_PROXY, mSysUiProxy.asBinder()); - params.putParcelable(KEY_EXTRA_INPUT_CHANNEL, createNewInputDispatcher()); params.putFloat(KEY_EXTRA_WINDOW_CORNER_RADIUS, mWindowCornerRadius); params.putBoolean(KEY_EXTRA_SUPPORTS_WINDOW_CORNERS, mSupportsRoundedCornersOnWindows); try { mOverviewProxy.onInitialize(params); } catch (RemoteException e) { - // Ignore error until the migration is complete. - Log.e(TAG_OPS, "Failed to call onBind()", e); + mCurrentBoundedUserId = -1; + Log.e(TAG_OPS, "Failed to call onInitialize()", e); } dispatchNavButtonBounds(); @@ -369,7 +376,6 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis Log.w(TAG_OPS, "Null binding of '" + name + "', try reconnecting"); mCurrentBoundedUserId = -1; retryConnectionWithBackoff(); - disposeInputDispatcher(); } @Override @@ -377,32 +383,15 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis Log.w(TAG_OPS, "Binding died of '" + name + "', try reconnecting"); mCurrentBoundedUserId = -1; retryConnectionWithBackoff(); - disposeInputDispatcher(); } @Override public void onServiceDisconnected(ComponentName name) { // Do nothing mCurrentBoundedUserId = -1; - disposeInputDispatcher(); } }; - private void disposeInputDispatcher() { - if (mInputEventDispatcher != null) { - mInputEventDispatcher.dispose(); - mInputEventDispatcher = null; - } - } - - private InputChannel createNewInputDispatcher() { - disposeInputDispatcher(); - - InputChannel[] channels = InputChannel.openInputChannelPair("overview-proxy-service"); - mInputEventDispatcher = new InputEventDispatcher(channels[0], Looper.getMainLooper()); - return channels[1]; - } - private final DeviceProvisionedListener mDeviceProvisionedCallback = new DeviceProvisionedListener() { @Override @@ -455,6 +444,17 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis } } + public void notifyBackAction(boolean completed, int downX, int downY, boolean isButton, + boolean gestureSwipeLeft) { + try { + if (mOverviewProxy != null) { + mOverviewProxy.onBackAction(completed, downX, downY, isButton, gestureSwipeLeft); + } + } catch (RemoteException e) { + Log.e(TAG_OPS, "Failed to notify back action", e); + } + } + /** * Sets the navbar region which can receive touch inputs */ @@ -567,10 +567,6 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis return mOverviewProxy; } - public InputEventDispatcher getInputEventDispatcher() { - return mInputEventDispatcher; - } - public int getInteractionFlags() { return mInteractionFlags; } @@ -633,6 +629,14 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis } } + public void notifyAssistantVisibilityChanged(float visibility) { + try { + mOverviewProxy.onAssistantVisibilityChanged(visibility); + } catch (RemoteException e) { + Log.e(TAG_OPS, "Failed to call onAssistantVisibilityChanged()", e); + } + } + private void updateEnabledState() { mIsEnabled = mContext.getPackageManager().resolveServiceAsUser(mQuickStepIntent, MATCH_SYSTEM_ONLY, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java index a630e49d850a..2793b2a0f009 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java @@ -63,6 +63,7 @@ import com.android.systemui.statusbar.policy.RemoteInputView; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Objects; import java.util.Set; import javax.inject.Inject; @@ -142,7 +143,7 @@ public class NotificationRemoteInputManager implements Dumpable { if (DEBUG) { Log.v(TAG, "Notification click handler invoked for intent: " + pendingIntent); } - logActionClick(view); + logActionClick(view, pendingIntent); // The intent we are sending is for the application, which // won't have permission to immediately start an activity after // the user switches to home. We know it is safe to do at this @@ -159,11 +160,11 @@ public class NotificationRemoteInputManager implements Dumpable { }); } - private void logActionClick(View view) { + private void logActionClick(View view, PendingIntent actionIntent) { Integer actionIndex = (Integer) view.getTag(com.android.internal.R.id.notification_action_index_tag); if (actionIndex == null) { - Log.e(TAG, "Couldn't retrieve the actionIndex from the clicked button"); + // Custom action button, not logging. return; } ViewParent parent = view.getParent(); @@ -182,8 +183,20 @@ public class NotificationRemoteInputManager implements Dumpable { } final int count = mEntryManager.getNotificationData().getActiveNotifications().size(); final int rank = mEntryManager.getNotificationData().getRank(key); + + // Notification may be updated before this function is executed, and thus play safe + // here and verify that the action object is still the one that where the click happens. + Notification.Action[] actions = statusBarNotification.getNotification().actions; + if (actions == null || actionIndex >= actions.length) { + Log.w(TAG, "statusBarNotification.getNotification().actions is null or invalid"); + return; + } final Notification.Action action = statusBarNotification.getNotification().actions[actionIndex]; + if (Objects.equals(action.actionIntent, actionIntent)) { + Log.w(TAG, "actionIntent does not match"); + return; + } NotificationVisibility.NotificationLocation location = NotificationLogger.getNotificationLocation( mEntryManager.getNotificationData().get(key)); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java index f1373d142402..f69356ea14a0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java @@ -21,6 +21,7 @@ import static android.app.Notification.CATEGORY_CALL; import static android.app.Notification.CATEGORY_EVENT; import static android.app.Notification.CATEGORY_MESSAGE; import static android.app.Notification.CATEGORY_REMINDER; +import static android.app.Notification.FLAG_BUBBLE; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST; @@ -40,6 +41,7 @@ import android.os.SystemClock; import android.service.notification.NotificationListenerService; import android.service.notification.SnoozeCriterion; import android.service.notification.StatusBarNotification; +import android.text.TextUtils; import android.util.ArraySet; import android.view.View; import android.widget.ImageView; @@ -50,6 +52,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.statusbar.StatusBarIcon; import com.android.internal.util.ArrayUtils; import com.android.internal.util.ContrastColorUtil; +import com.android.systemui.R; import com.android.systemui.statusbar.InflationTask; import com.android.systemui.statusbar.StatusBarIconView; import com.android.systemui.statusbar.notification.InflationException; @@ -146,11 +149,6 @@ public final class NotificationEntry { private boolean hasSentReply; /** - * Whether this notification should be displayed as a bubble. - */ - private boolean mIsBubble; - - /** * Whether this notification has been approved globally, at the app level, and at the channel * level for bubbling. */ @@ -222,12 +220,8 @@ public final class NotificationEntry { this.mHighPriority = highPriority; } - public void setIsBubble(boolean bubbleable) { - mIsBubble = bubbleable; - } - public boolean isBubble() { - return mIsBubble; + return (notification.getNotification().flags & FLAG_BUBBLE) != 0; } public void setBubbleDismissed(boolean userDismissed) { @@ -401,6 +395,72 @@ public final class NotificationEntry { } /** + * Returns our best guess for the most relevant text summary of the latest update to this + * notification, based on its type. Returns null if there should not be an update message. + */ + public CharSequence getUpdateMessage(Context context) { + final Notification underlyingNotif = notification.getNotification(); + final Class<? extends Notification.Style> style = underlyingNotif.getNotificationStyle(); + + try { + if (Notification.BigTextStyle.class.equals(style)) { + // Return the big text, it is big so probably important. If it's not there use the + // normal text. + CharSequence bigText = + underlyingNotif.extras.getCharSequence(Notification.EXTRA_BIG_TEXT); + return !TextUtils.isEmpty(bigText) + ? bigText + : underlyingNotif.extras.getCharSequence(Notification.EXTRA_TEXT); + } else if (Notification.MessagingStyle.class.equals(style)) { + final List<Notification.MessagingStyle.Message> messages = + Notification.MessagingStyle.Message.getMessagesFromBundleArray( + (Parcelable[]) underlyingNotif.extras.get( + Notification.EXTRA_MESSAGES)); + + final Notification.MessagingStyle.Message latestMessage = + Notification.MessagingStyle.findLatestIncomingMessage(messages); + + if (latestMessage != null) { + final CharSequence personName = latestMessage.getSenderPerson() != null + ? latestMessage.getSenderPerson().getName() + : null; + + // Prepend the sender name if available since group chats also use messaging + // style. + if (!TextUtils.isEmpty(personName)) { + return context.getResources().getString( + R.string.notification_summary_message_format, + personName, + latestMessage.getText()); + } else { + return latestMessage.getText(); + } + } + } else if (Notification.InboxStyle.class.equals(style)) { + CharSequence[] lines = + underlyingNotif.extras.getCharSequenceArray(Notification.EXTRA_TEXT_LINES); + + // Return the last line since it should be the most recent. + if (lines != null && lines.length > 0) { + return lines[lines.length - 1]; + } + } else if (Notification.MediaStyle.class.equals(style)) { + // Return nothing, media updates aren't typically useful as a text update. + return null; + } else { + // Default to text extra. + return underlyingNotif.extras.getCharSequence(Notification.EXTRA_TEXT); + } + } catch (ClassCastException | NullPointerException | ArrayIndexOutOfBoundsException e) { + // No use crashing, we'll just return null and the caller will assume there's no update + // message. + e.printStackTrace(); + } + + return null; + } + + /** * Abort all existing inflation tasks */ public void abortTask() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java index 580e7024347c..2c15e87ee1e6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java @@ -85,11 +85,13 @@ public class NotificationBlockingHelperManager { // - User sentiment is negative (DEBUG flag can bypass) // - The notification shade is fully expanded (guarantees we're not touching a HUN). // - The row is blockable (i.e. not non-blockable) - // - The dismissed row is a valid group (>1 or 0 children) or the only child in the group + // - The dismissed row is a valid group (>1 or 0 children from the same channel) + // or the only child in the group if ((row.getEntry().userSentiment == USER_SENTIMENT_NEGATIVE || DEBUG) && mIsShadeExpanded && !row.getIsNonblockable() - && (!row.isChildInGroup() || row.isOnlyChildInGroup())) { + && ((!row.isChildInGroup() || row.isOnlyChildInGroup()) + && row.getNumUniqueChannels() <= 1)) { // Dismiss any current blocking helper before continuing forward (only one can be shown // at a given time). dismissCurrentBlockingHelper(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java index 54bdaa23ea1d..69e61201a4d0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java @@ -309,7 +309,6 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx mDeviceProvisionedController.isDeviceProvisioned(), row.getIsNonblockable(), isForBlockingHelper, - row.getEntry().userSentiment == USER_SENTIMENT_NEGATIVE, row.getEntry().importance, row.getEntry().isHighPriority()); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java index 2e4325b2f41f..622b869c9e4c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java @@ -39,6 +39,7 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.graphics.Color; import android.graphics.drawable.Drawable; +import android.graphics.drawable.GradientDrawable; import android.metrics.LogMaker; import android.os.Handler; import android.os.RemoteException; @@ -82,9 +83,13 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G public static final int ACTION_NONE = 0; static final int ACTION_UNDO = 1; + // standard controls static final int ACTION_TOGGLE_SILENT = 2; + // unused static final int ACTION_BLOCK = 3; + // blocking helper static final int ACTION_DELIVER_SILENTLY = 4; + // standard controls private static final int ACTION_ALERT = 5; private INotificationManager mINotificationManager; @@ -99,7 +104,6 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G private NotificationChannel mSingleNotificationChannel; private int mStartingChannelImportance; private boolean mWasShownHighPriority; - private int mNotificationBlockState = ACTION_NONE; /** * The last importance level chosen by the user. Null if the user has not chosen an importance * level; non-null once the user takes an action which indicates an explicit preference. @@ -109,13 +113,13 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G private boolean mIsNonblockable; private StatusBarNotification mSbn; private AnimatorSet mExpandAnimation; - private boolean mIsForeground; private boolean mIsDeviceProvisioned; private CheckSaveListener mCheckSaveListener; private OnSettingsClickListener mOnSettingsClickListener; private OnAppSettingsClickListener mAppSettingsClickListener; private NotificationGuts mGutsContainer; + private GradientDrawable mSelectedBackground; /** Whether this view is being shown as part of the blocking helper. */ private boolean mIsForBlockingHelper; @@ -125,40 +129,39 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G */ private String mExitReason = NotificationCounters.BLOCKING_HELPER_DISMISSED; - private OnClickListener mOnKeepShowing = v -> { - mExitReason = NotificationCounters.BLOCKING_HELPER_KEEP_SHOWING; - if (mIsForBlockingHelper) { - closeControls(v); - mMetricsLogger.write(getLogMaker().setCategory( - MetricsEvent.NOTIFICATION_BLOCKING_HELPER) - .setType(MetricsEvent.TYPE_ACTION) - .setSubtype(MetricsEvent.BLOCKING_HELPER_CLICK_STAY_SILENT)); - } - }; - + // used by standard ui private OnClickListener mOnAlert = v -> { mExitReason = NotificationCounters.BLOCKING_HELPER_KEEP_SHOWING; mChosenImportance = IMPORTANCE_DEFAULT; - updateButtonsAndHelpText(ACTION_ALERT); + updateButtons(ACTION_ALERT); + }; + + // used by standard ui + private OnClickListener mOnSilent = v -> { + mExitReason = NotificationCounters.BLOCKING_HELPER_DELIVER_SILENTLY; + mChosenImportance = IMPORTANCE_LOW; + updateButtons(ACTION_TOGGLE_SILENT); }; + // used by standard ui private OnClickListener mOnDismissSettings = v -> { closeControls(v); }; + // used by blocking helper + private OnClickListener mOnKeepShowing = v -> { + mExitReason = NotificationCounters.BLOCKING_HELPER_KEEP_SHOWING; + closeControls(v); + mMetricsLogger.write(getLogMaker().setCategory( + MetricsEvent.NOTIFICATION_BLOCKING_HELPER) + .setType(MetricsEvent.TYPE_ACTION) + .setSubtype(MetricsEvent.BLOCKING_HELPER_CLICK_STAY_SILENT)); + }; + + // used by blocking helper private OnClickListener mOnDeliverSilently = v -> { handleSaveImportance( ACTION_DELIVER_SILENTLY, MetricsEvent.BLOCKING_HELPER_CLICK_STAY_SILENT); - if (!mIsForBlockingHelper) { - updateButtonsAndHelpText(ACTION_DELIVER_SILENTLY); - } - }; - - private OnClickListener mOnStopOrMinimizeNotifications = v -> { - handleSaveImportance(ACTION_BLOCK, MetricsEvent.BLOCKING_HELPER_CLICK_BLOCKED); - if (!mIsForBlockingHelper) { - updateButtonsAndHelpText(ACTION_BLOCK); - } }; private void handleSaveImportance(int action, int metricsSubtype) { @@ -189,6 +192,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G .setType(MetricsEvent.TYPE_DISMISS) .setSubtype(MetricsEvent.BLOCKING_HELPER_CLICK_UNDO)); } else { + // TODO: this can't happen? mMetricsLogger.write(importanceChangeLogMaker().setType(MetricsEvent.TYPE_DISMISS)); } saveImportanceAndExitReason(ACTION_UNDO); @@ -233,7 +237,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G bindNotification(pm, iNotificationManager, pkg, notificationChannel, numUniqueChannelsInRow, sbn, checkSaveListener, onSettingsClick, onAppSettingsClick, isDeviceProvisioned, isNonblockable, - false /* isBlockingHelper */, false /* isUserSentimentNegative */, + false /* isBlockingHelper */, importance, wasShownHighPriority); } @@ -250,7 +254,6 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G boolean isDeviceProvisioned, boolean isNonblockable, boolean isForBlockingHelper, - boolean isUserSentimentNegative, int importance, boolean wasShownHighPriority) throws RemoteException { @@ -268,13 +271,20 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G mStartingChannelImportance = mSingleNotificationChannel.getImportance(); mWasShownHighPriority = wasShownHighPriority; mIsNonblockable = isNonblockable; - mIsForeground = - (mSbn.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE) != 0; mIsForBlockingHelper = isForBlockingHelper; mAppUid = mSbn.getUid(); mDelegatePkg = mSbn.getOpPkg(); mIsDeviceProvisioned = isDeviceProvisioned; + mSelectedBackground = new GradientDrawable(); + mSelectedBackground.setShape(GradientDrawable.RECTANGLE); + mSelectedBackground.setColor(mContext.getColor(R.color.notification_guts_selection_bg)); + final float cornerRadii = getResources().getDisplayMetrics().density * 8; + mSelectedBackground.setCornerRadii(new float[]{cornerRadii, cornerRadii, cornerRadii, + cornerRadii, cornerRadii, cornerRadii, cornerRadii, cornerRadii}); + mSelectedBackground.setStroke((int) (getResources().getDisplayMetrics().density * 2), + mContext.getColor(R.color.notification_guts_selection_border)); + int numTotalChannels = mINotificationManager.getNumNotificationChannelsForPackage( pkg, mAppUid, false /* includeDeleted */); if (mNumUniqueChannelsInRow == 0) { @@ -288,13 +298,74 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G } bindHeader(); - bindPrompt(); - bindButtons(); + bindChannelDetails(); + + if (mIsForBlockingHelper) { + bindBlockingHelper(); + } else { + bindInlineControls(); + } mMetricsLogger.write(notificationControlsLogMaker()); } - private void bindHeader() throws RemoteException { + private void bindBlockingHelper() { + findViewById(R.id.inline_controls).setVisibility(GONE); + findViewById(R.id.blocking_helper).setVisibility(VISIBLE); + + findViewById(R.id.undo).setOnClickListener(mOnUndo); + + View turnOffButton = findViewById(R.id.blocking_helper_turn_off_notifications); + turnOffButton.setOnClickListener(getSettingsOnClickListener()); + turnOffButton.setVisibility(turnOffButton.hasOnClickListeners() ? VISIBLE : GONE); + + TextView keepShowing = findViewById(R.id.keep_showing); + keepShowing.setOnClickListener(mOnKeepShowing); + + View deliverSilently = findViewById(R.id.deliver_silently); + deliverSilently.setOnClickListener(mOnDeliverSilently); + } + + private void bindInlineControls() { + findViewById(R.id.inline_controls).setVisibility(VISIBLE); + findViewById(R.id.blocking_helper).setVisibility(GONE); + + if (mIsNonblockable) { + findViewById(R.id.non_configurable_text).setVisibility(VISIBLE); + findViewById(R.id.non_configurable_multichannel_text).setVisibility(GONE); + findViewById(R.id.interruptiveness_settings).setVisibility(GONE); + } else if (mNumUniqueChannelsInRow > 1) { + findViewById(R.id.non_configurable_text).setVisibility(GONE); + findViewById(R.id.interruptiveness_settings).setVisibility(GONE); + findViewById(R.id.non_configurable_multichannel_text).setVisibility(VISIBLE); + } else { + findViewById(R.id.non_configurable_text).setVisibility(GONE); + findViewById(R.id.non_configurable_multichannel_text).setVisibility(GONE); + findViewById(R.id.interruptiveness_settings).setVisibility(VISIBLE); + } + + View turnOffButton = findViewById(R.id.turn_off_notifications); + turnOffButton.setOnClickListener(getSettingsOnClickListener()); + turnOffButton.setVisibility(turnOffButton.hasOnClickListeners() && !mIsNonblockable + ? VISIBLE : GONE); + + View done = findViewById(R.id.done); + done.setOnClickListener(mOnDismissSettings); + + + View silent = findViewById(R.id.silent_row); + View alert = findViewById(R.id.alert_row); + silent.setOnClickListener(mOnSilent); + alert.setOnClickListener(mOnAlert); + + if (mWasShownHighPriority) { + updateButtons(ACTION_ALERT); + } else { + updateButtons(ACTION_TOGGLE_SILENT); + } + } + + private void bindHeader() { // Package name Drawable pkgicon = null; ApplicationInfo info; @@ -319,31 +390,44 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G // Delegate bindDelegate(); - // Settings button. + // Set up app settings link (i.e. Customize) + View settingsLinkView = findViewById(R.id.app_settings); + Intent settingsIntent = getAppSettingsIntent(mPm, mPackageName, + mSingleNotificationChannel, + mSbn.getId(), mSbn.getTag()); + if (settingsIntent != null + && !TextUtils.isEmpty(mSbn.getNotification().getSettingsText())) { + settingsLinkView.setVisibility(VISIBLE); + settingsLinkView.setOnClickListener((View view) -> { + mAppSettingsClickListener.onClick(view, settingsIntent); + }); + } else { + settingsLinkView.setVisibility(View.GONE); + } + + // System Settings button. final View settingsButton = findViewById(R.id.info); + settingsButton.setOnClickListener(getSettingsOnClickListener()); + settingsButton.setVisibility(settingsButton.hasOnClickListeners() ? VISIBLE : GONE); + } + + private OnClickListener getSettingsOnClickListener() { if (mAppUid >= 0 && mOnSettingsClickListener != null && mIsDeviceProvisioned) { - settingsButton.setVisibility(View.VISIBLE); final int appUidF = mAppUid; - settingsButton.setOnClickListener( - (View view) -> { - logBlockingHelperCounter( - NotificationCounters.BLOCKING_HELPER_NOTIF_SETTINGS); - mOnSettingsClickListener.onClick(view, - mNumUniqueChannelsInRow > 1 ? null : mSingleNotificationChannel, - appUidF); - }); - } else { - settingsButton.setVisibility(View.GONE); + return ((View view) -> { + logBlockingHelperCounter( + NotificationCounters.BLOCKING_HELPER_NOTIF_SETTINGS); + mOnSettingsClickListener.onClick(view, + mNumUniqueChannelsInRow > 1 ? null : mSingleNotificationChannel, + appUidF); + }); } + return null; } - private void bindPrompt() throws RemoteException { - final TextView blockPrompt = findViewById(R.id.block_prompt); + private void bindChannelDetails() throws RemoteException { bindName(); bindGroup(); - if (mIsNonblockable) { - blockPrompt.setText(R.string.notification_unblockable_desc); - } } private void bindName() { @@ -450,110 +534,17 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G } } - private void bindButtons() { - findViewById(R.id.undo).setOnClickListener(mOnUndo); - - boolean showInterruptivenessSettings = - !mIsNonblockable - && !mIsForeground - && !mIsForBlockingHelper - && NotificationUtils.useNewInterruptionModel(mContext); - if (showInterruptivenessSettings) { - findViewById(R.id.block_or_minimize).setVisibility(GONE); - findViewById(R.id.interruptiveness_settings).setVisibility(VISIBLE); - View done = findViewById(R.id.done_button); - done.setOnClickListener(mOnDismissSettings); - View block = findViewById(R.id.int_block_wrapper); - View silent = findViewById(R.id.int_silent_wrapper); - View alert = findViewById(R.id.int_alert_wrapper); - block.setOnClickListener(mOnStopOrMinimizeNotifications); - silent.setOnClickListener(mOnDeliverSilently); - alert.setOnClickListener(mOnAlert); - if (mNotificationBlockState != ACTION_NONE) { - updateButtonsAndHelpText(mNotificationBlockState); - } else if (mWasShownHighPriority) { - updateButtonsAndHelpText(ACTION_ALERT); - } else { - updateButtonsAndHelpText(ACTION_DELIVER_SILENTLY); - } - } else { - findViewById(R.id.block_or_minimize).setVisibility(VISIBLE); - findViewById(R.id.interruptiveness_settings).setVisibility(GONE); - View block = findViewById(R.id.block); - TextView done = findViewById(R.id.done); - View minimize = findViewById(R.id.minimize); - View deliverSilently = findViewById(R.id.deliver_silently); - - - block.setOnClickListener(mOnStopOrMinimizeNotifications); - done.setOnClickListener(mOnKeepShowing); - minimize.setOnClickListener(mOnStopOrMinimizeNotifications); - deliverSilently.setOnClickListener(mOnDeliverSilently); - - if (mIsNonblockable) { - done.setText(android.R.string.ok); - block.setVisibility(GONE); - minimize.setVisibility(GONE); - deliverSilently.setVisibility(GONE); - } else if (mIsForeground) { - block.setVisibility(GONE); - minimize.setVisibility(VISIBLE); - } else { - block.setVisibility(VISIBLE); - minimize.setVisibility(GONE); - } - - // Set up app settings link (i.e. Customize) - View settingsLinkView = findViewById(R.id.app_settings); - Intent settingsIntent = getAppSettingsIntent(mPm, mPackageName, - mSingleNotificationChannel, - mSbn.getId(), mSbn.getTag()); - if (!mIsForBlockingHelper - && settingsIntent != null - && !TextUtils.isEmpty(mSbn.getNotification().getSettingsText())) { - settingsLinkView.setVisibility(VISIBLE); - settingsLinkView.setOnClickListener((View view) -> { - mAppSettingsClickListener.onClick(view, settingsIntent); - }); - } else { - settingsLinkView.setVisibility(View.GONE); - } - } - } - - private void updateButtonsAndHelpText(int blockState) { - mNotificationBlockState = blockState; - ImageView block = findViewById(R.id.int_block); - ImageView silent = findViewById(R.id.int_silent); - ImageView alert = findViewById(R.id.int_alert); - TextView hintText = findViewById(R.id.hint_text); + private void updateButtons(int blockState) { + View silent = findViewById(R.id.silent_row); + View alert = findViewById(R.id.alert_row); switch (blockState) { - case ACTION_BLOCK: - block.setBackgroundResource(R.drawable.circle_blue_40dp); - block.setColorFilter(Color.WHITE); - silent.setBackgroundResource(R.drawable.circle_white_40dp); - silent.setColorFilter(getResources().getColor(R.color.GM2_grey_400)); - alert.setBackgroundResource(R.drawable.circle_white_40dp); - alert.setColorFilter(getResources().getColor(R.color.GM2_grey_400)); - hintText.setText(R.string.hint_text_block); - break; - case ACTION_DELIVER_SILENTLY: - silent.setBackgroundResource(R.drawable.circle_blue_40dp); - silent.setColorFilter(Color.WHITE); - block.setBackgroundResource(R.drawable.circle_white_40dp); - block.setColorFilter(getResources().getColor(R.color.GM2_grey_400)); - alert.setBackgroundResource(R.drawable.circle_white_40dp); - alert.setColorFilter(getResources().getColor(R.color.GM2_grey_400)); - hintText.setText(R.string.hint_text_silent); + case ACTION_TOGGLE_SILENT: + silent.setBackground(mSelectedBackground); + alert.setBackground(null); break; case ACTION_ALERT: - alert.setBackgroundResource(R.drawable.circle_blue_40dp); - alert.setColorFilter(Color.WHITE); - block.setBackgroundResource(R.drawable.circle_white_40dp); - block.setColorFilter(getResources().getColor(R.color.GM2_grey_400)); - silent.setBackgroundResource(R.drawable.circle_white_40dp); - silent.setColorFilter(getResources().getColor(R.color.GM2_grey_400)); - hintText.setText(R.string.hint_text_alert); + alert.setBackground(mSelectedBackground); + silent.setBackground(null); break; } } @@ -575,28 +566,20 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G mChosenImportance = IMPORTANCE_DEFAULT; } break; - case ACTION_BLOCK: - mExitReason = NotificationCounters.BLOCKING_HELPER_STOP_NOTIFICATIONS; - if (mIsForeground) { - mChosenImportance = IMPORTANCE_MIN; - } else { - mChosenImportance = IMPORTANCE_NONE; - } - break; default: throw new IllegalArgumentException(); } } + // only used for blocking helper private void swapContent(@NotificationInfoAction int action, boolean animate) { if (mExpandAnimation != null) { mExpandAnimation.cancel(); } - View prompt = findViewById(R.id.prompt); + View blockingHelper = findViewById(R.id.blocking_helper); ViewGroup confirmation = findViewById(R.id.confirmation); TextView confirmationText = findViewById(R.id.confirmation_text); - View header = findViewById(R.id.header); saveImportanceAndExitReason(action); @@ -606,33 +589,20 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G case ACTION_DELIVER_SILENTLY: confirmationText.setText(R.string.notification_channel_silenced); break; - case ACTION_TOGGLE_SILENT: - if (mWasShownHighPriority) { - confirmationText.setText(R.string.notification_channel_silenced); - } else { - confirmationText.setText(R.string.notification_channel_unsilenced); - } - break; - case ACTION_BLOCK: - if (mIsForeground) { - confirmationText.setText(R.string.notification_channel_minimized); - } else { - confirmationText.setText(R.string.notification_channel_disabled); - } - break; default: throw new IllegalArgumentException(); } boolean isUndo = action == ACTION_UNDO; - prompt.setVisibility(isUndo ? VISIBLE : GONE); + blockingHelper.setVisibility(isUndo ? VISIBLE : GONE); + findViewById(R.id.channel_info).setVisibility(isUndo ? VISIBLE : GONE); + findViewById(R.id.header).setVisibility(isUndo ? VISIBLE : GONE); confirmation.setVisibility(isUndo ? GONE : VISIBLE); - header.setVisibility(isUndo ? VISIBLE : GONE); if (animate) { - ObjectAnimator promptAnim = ObjectAnimator.ofFloat(prompt, View.ALPHA, - prompt.getAlpha(), isUndo ? 1f : 0f); + ObjectAnimator promptAnim = ObjectAnimator.ofFloat(blockingHelper, View.ALPHA, + blockingHelper.getAlpha(), isUndo ? 1f : 0f); promptAnim.setInterpolator(isUndo ? Interpolators.ALPHA_IN : Interpolators.ALPHA_OUT); ObjectAnimator confirmAnim = ObjectAnimator.ofFloat(confirmation, View.ALPHA, confirmation.getAlpha(), isUndo ? 0f : 1f); @@ -652,7 +622,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G @Override public void onAnimationEnd(Animator animation) { if (!mCancelled) { - prompt.setVisibility(isUndo ? VISIBLE : GONE); + blockingHelper.setVisibility(isUndo ? VISIBLE : GONE); confirmation.setVisibility(isUndo ? GONE : VISIBLE); } } @@ -674,15 +644,11 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G } mExitReason = NotificationCounters.BLOCKING_HELPER_DISMISSED; - View prompt = findViewById(R.id.prompt); - ViewGroup confirmation = findViewById(R.id.confirmation); - View header = findViewById(R.id.header); - prompt.setVisibility(VISIBLE); - prompt.setAlpha(1f); - confirmation.setVisibility(GONE); - confirmation.setAlpha(1f); - header.setVisibility(VISIBLE); - header.setAlpha(1f); + if (mIsForBlockingHelper) { + bindBlockingHelper(); + } else { + bindInlineControls(); + } mMetricsLogger.write(notificationControlsLogMaker().setType(MetricsEvent.TYPE_CLOSE)); } @@ -730,8 +696,8 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G * commit the updated importance. * * <p><b>Note,</b> this will only get called once the view is dismissing. This means that the - * user does not have the ability to undo the action anymore. See {@link #swapContent(boolean)} - * for where undo is handled. + * user does not have the ability to undo the action anymore. See + * {@link #swapContent(boolean, boolean)} for where undo is handled. */ @VisibleForTesting void closeControls(View v) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java new file mode 100644 index 000000000000..212666f24b36 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java @@ -0,0 +1,358 @@ +/** + * Copyright (C) 2019 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.systemui.statusbar.phone; + +import android.content.Context; +import android.content.pm.ParceledListSlice; +import android.graphics.PixelFormat; +import android.graphics.Point; +import android.graphics.PointF; +import android.graphics.Rect; +import android.graphics.Region; +import android.hardware.display.DisplayManager; +import android.hardware.display.DisplayManager.DisplayListener; +import android.hardware.input.InputManager; +import android.os.Looper; +import android.os.RemoteException; +import android.os.SystemClock; +import android.util.Log; +import android.util.MathUtils; +import android.view.Choreographer; +import android.view.Gravity; +import android.view.IPinnedStackController; +import android.view.IPinnedStackListener; +import android.view.ISystemGestureExclusionListener; +import android.view.InputDevice; +import android.view.InputEvent; +import android.view.InputMonitor; +import android.view.KeyCharacterMap; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.ViewConfiguration; +import android.view.WindowManager; +import android.view.WindowManagerGlobal; + +import com.android.systemui.R; +import com.android.systemui.recents.OverviewProxyService; +import com.android.systemui.shared.system.InputChannelCompat.InputEventReceiver; +import com.android.systemui.shared.system.QuickStepContract; +import com.android.systemui.shared.system.WindowManagerWrapper; + +import java.util.concurrent.Executor; + +/** + * Utility class to handle edge swipes for back gesture + */ +public class EdgeBackGestureHandler implements DisplayListener { + + private static final String TAG = "EdgeBackGestureHandler"; + + private final IPinnedStackListener.Stub mImeChangedListener = new IPinnedStackListener.Stub() { + @Override + public void onListenerRegistered(IPinnedStackController controller) { + } + + @Override + public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) { + // No need to thread jump, assignments are atomic + mImeHeight = imeVisible ? imeHeight : 0; + // TODO: Probably cancel any existing gesture + } + + @Override + public void onShelfVisibilityChanged(boolean shelfVisible, int shelfHeight) { + } + + @Override + public void onMinimizedStateChanged(boolean isMinimized) { + } + + @Override + public void onMovementBoundsChanged(Rect insetBounds, Rect normalBounds, + Rect animatingBounds, boolean fromImeAdjustment, boolean fromShelfAdjustment, + int displayRotation) { + } + + @Override + public void onActionsChanged(ParceledListSlice actions) { + } + }; + + private ISystemGestureExclusionListener mGestureExclusionListener = + new ISystemGestureExclusionListener.Stub() { + @Override + public void onSystemGestureExclusionChanged(int displayId, + Region systemGestureExclusion) { + if (displayId == mDisplayId) { + mMainExecutor.execute(() -> mExcludeRegion.set(systemGestureExclusion)); + } + } + }; + + private final Context mContext; + private final OverviewProxyService mOverviewProxyService; + + private final Point mDisplaySize = new Point(); + private final int mDisplayId; + + private final Executor mMainExecutor; + + private final Region mExcludeRegion = new Region(); + // The edge width where touch down is allowed + private final int mEdgeWidth; + // The slop to distinguish between horizontal and vertical motion + private final float mTouchSlop; + // Minimum distance to move so that is can be considerd as a back swipe + private final float mSwipeThreshold; + + private final int mNavBarHeight; + + private final PointF mDownPoint = new PointF(); + private boolean mThresholdCrossed = false; + private boolean mIgnoreThisGesture = false; + private boolean mIsOnLeftEdge; + + private int mImeHeight = 0; + + private boolean mIsAttached; + private boolean mIsGesturalModeEnabled; + private boolean mIsEnabled; + + private InputMonitor mInputMonitor; + private InputEventReceiver mInputEventReceiver; + + private final WindowManager mWm; + + private NavigationBarEdgePanel mEdgePanel; + private WindowManager.LayoutParams mEdgePanelLp; + + public EdgeBackGestureHandler(Context context, OverviewProxyService overviewProxyService) { + mContext = context; + mDisplayId = context.getDisplayId(); + mMainExecutor = context.getMainExecutor(); + mWm = context.getSystemService(WindowManager.class); + mOverviewProxyService = overviewProxyService; + + mEdgeWidth = QuickStepContract.getEdgeSensitivityWidth(context); + mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); + mSwipeThreshold = context.getResources() + .getDimension(R.dimen.navigation_edge_action_drag_threshold); + + mNavBarHeight = context.getResources().getDimensionPixelSize(R.dimen.navigation_bar_height); + } + + /** + * @see NavigationBarView#onAttachedToWindow() + */ + public void onNavBarAttached() { + mIsAttached = true; + onOverlaysChanged(); + } + + /** + * @see NavigationBarView#onDetachedFromWindow() + */ + public void onNavBarDetached() { + mIsAttached = false; + updateIsEnabled(); + } + + /** + * Called when system overlays has changed + */ + public void onOverlaysChanged() { + mIsGesturalModeEnabled = QuickStepContract.isGesturalMode(mContext); + updateIsEnabled(); + } + + private void disposeInputChannel() { + if (mInputEventReceiver != null) { + mInputEventReceiver.dispose(); + mInputEventReceiver = null; + } + if (mInputMonitor != null) { + mInputMonitor.dispose(); + mInputMonitor = null; + } + } + + private void updateIsEnabled() { + boolean isEnabled = mIsAttached && mIsGesturalModeEnabled; + if (isEnabled == mIsEnabled) { + return; + } + mIsEnabled = isEnabled; + disposeInputChannel(); + + if (mEdgePanel != null) { + mWm.removeView(mEdgePanel); + mEdgePanel = null; + } + + if (!mIsEnabled) { + WindowManagerWrapper.getInstance().removePinnedStackListener(mImeChangedListener); + + try { + WindowManagerGlobal.getWindowManagerService() + .unregisterSystemGestureExclusionListener( + mGestureExclusionListener, mDisplayId); + } catch (RemoteException e) { + Log.e(TAG, "Failed to unregister window manager callbacks", e); + } + + } else { + updateDisplaySize(); + + try { + WindowManagerWrapper.getInstance().addPinnedStackListener(mImeChangedListener); + WindowManagerGlobal.getWindowManagerService() + .registerSystemGestureExclusionListener( + mGestureExclusionListener, mDisplayId); + } catch (RemoteException e) { + Log.e(TAG, "Failed to register window manager callbacks", e); + } + + // Register input event receiver + mInputMonitor = InputManager.getInstance().monitorGestureInput( + "edge-swipe", mDisplayId); + mInputEventReceiver = new InputEventReceiver(mInputMonitor.getInputChannel(), + Looper.getMainLooper(), Choreographer.getMainThreadInstance(), + this::onInputEvent); + + // Add a nav bar panel window + mEdgePanel = new NavigationBarEdgePanel(mContext); + mEdgePanelLp = new WindowManager.LayoutParams( + mContext.getResources() + .getDimensionPixelSize(R.dimen.navigation_edge_panel_width), + mContext.getResources() + .getDimensionPixelSize(R.dimen.navigation_edge_panel_height), + WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL + | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH + | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN, + PixelFormat.TRANSLUCENT); + mEdgePanelLp.setTitle(TAG + mDisplayId); + mEdgePanelLp.accessibilityTitle = mContext.getString(R.string.nav_bar_edge_panel); + mEdgePanelLp.windowAnimations = 0; + mEdgePanel.setLayoutParams(mEdgePanelLp); + mWm.addView(mEdgePanel, mEdgePanelLp); + } + } + + private void onInputEvent(InputEvent ev) { + if (ev instanceof MotionEvent) { + onMotionEvent((MotionEvent) ev); + } + } + + private boolean isWithinTouchRegion(int x, int y) { + if (y > (mDisplaySize.y - Math.max(mImeHeight, mNavBarHeight))) { + return false; + } + + if (x > mEdgeWidth && x < (mDisplaySize.x - mEdgeWidth)) { + return false; + } + return !mExcludeRegion.contains(x, y); + } + + private void onMotionEvent(MotionEvent ev) { + if (ev.getAction() == MotionEvent.ACTION_DOWN) { + // Verify if this is in within the touch region + mIgnoreThisGesture = !isWithinTouchRegion((int) ev.getX(), (int) ev.getY()); + if (!mIgnoreThisGesture) { + mIsOnLeftEdge = ev.getX() < mEdgeWidth; + mEdgePanelLp.gravity = mIsOnLeftEdge + ? (Gravity.LEFT | Gravity.TOP) + : (Gravity.RIGHT | Gravity.TOP); + mEdgePanel.setIsLeftPanel(mIsOnLeftEdge); + mEdgePanelLp.y = MathUtils.constrain( + (int) (ev.getY() - mEdgePanelLp.height / 2), + 0, mDisplaySize.y); + mWm.updateViewLayout(mEdgePanel, mEdgePanelLp); + + mDownPoint.set(ev.getX(), ev.getY()); + mThresholdCrossed = false; + mEdgePanel.handleTouch(ev); + } + } else if (!mIgnoreThisGesture) { + if (!mThresholdCrossed && ev.getAction() == MotionEvent.ACTION_MOVE) { + float dx = Math.abs(ev.getX() - mDownPoint.x); + float dy = Math.abs(ev.getY() - mDownPoint.y); + if (dy > dx && dy > mTouchSlop) { + // Send action cancel to reset all the touch events + mIgnoreThisGesture = true; + MotionEvent cancelEv = MotionEvent.obtain(ev); + cancelEv.setAction(MotionEvent.ACTION_CANCEL); + mEdgePanel.handleTouch(cancelEv); + cancelEv.recycle(); + return; + + } else if (dx > dy && dx > mTouchSlop) { + mThresholdCrossed = true; + // Capture inputs + mInputMonitor.pilferPointers(); + } + } + + // forward touch + mEdgePanel.handleTouch(ev); + + if (ev.getAction() == MotionEvent.ACTION_UP) { + float xDiff = ev.getX() - mDownPoint.x; + boolean exceedsThreshold = mIsOnLeftEdge + ? (xDiff > mSwipeThreshold) : (-xDiff > mSwipeThreshold); + boolean performAction = exceedsThreshold + && Math.abs(xDiff) > Math.abs(ev.getY() - mDownPoint.y); + if (performAction) { + // Perform back + sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK); + sendEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK); + } + mOverviewProxyService.notifyBackAction(performAction, (int) mDownPoint.x, + (int) mDownPoint.y, false /* isButton */, !mIsOnLeftEdge); + } + } + } + + @Override + public void onDisplayAdded(int displayId) { } + + @Override + public void onDisplayRemoved(int displayId) { } + + @Override + public void onDisplayChanged(int displayId) { + if (displayId == mDisplayId) { + updateDisplaySize(); + } + } + + private void updateDisplaySize() { + mContext.getSystemService(DisplayManager.class) + .getDisplay(mDisplayId).getRealSize(mDisplaySize); + } + + private void sendEvent(int action, int code) { + long when = SystemClock.uptimeMillis(); + final KeyEvent ev = new KeyEvent(when, when, action, code, 0 /* repeat */, + 0 /* metaState */, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */, + KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY, + InputDevice.SOURCE_KEYBOARD); + InputManager.getInstance().injectInputEvent(ev, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java index 2bd8d41a24e3..6ee031a2f505 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java @@ -187,9 +187,9 @@ public class KeyguardStatusBarView extends RelativeLayout } if (mKeyguardUserSwitcher == null) { // If we have no keyguard switcher, the screen width is under 600dp. In this case, - // we don't show the multi-user avatar unless there is more than 1 user on the device. - if (mUserSwitcherController != null - && mUserSwitcherController.getSwitchableUserCount() > 1) { + // we only show the multi-user switch if it's enabled through UserManager as well as + // by the user. + if (mMultiUserSwitch.isMultiUserEnabled()) { mMultiUserSwitch.setVisibility(View.VISIBLE); } else { mMultiUserSwitch.setVisibility(View.GONE); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java index 27394d92d672..6ebd6b3f04cd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java @@ -263,8 +263,7 @@ public class LockIcon extends KeyguardAffordanceView implements OnUserInfoChange return STATE_BIOMETRICS_ERROR; } else if (mUnlockMethodCache.canSkipBouncer()) { return STATE_LOCK_OPEN; - } else if (mUnlockMethodCache.isFaceUnlockRunning() - || updateMonitor.isFaceDetectionRunning()) { + } else if (updateMonitor.isFaceDetectionRunning()) { return STATE_SCANNING_FACE; } else { return STATE_LOCKED; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java index f393dcd368cd..1d87a8b439e8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java @@ -16,10 +16,10 @@ package com.android.systemui.statusbar.phone; +import android.app.admin.DevicePolicyManager; import android.content.Context; -import android.content.Intent; import android.os.UserManager; -import android.provider.ContactsContract; +import android.provider.Settings; import android.text.TextUtils; import android.util.AttributeSet; import android.view.View; @@ -33,7 +33,6 @@ import com.android.systemui.Dependency; import com.android.systemui.Prefs; import com.android.systemui.Prefs.Key; import com.android.systemui.R; -import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.qs.DetailAdapter; import com.android.systemui.qs.QSPanel; import com.android.systemui.statusbar.policy.KeyguardUserSwitcher; @@ -95,6 +94,26 @@ public class MultiUserSwitch extends FrameLayout implements View.OnClickListener registerListener(); } + public boolean isMultiUserEnabled() { + // Short-circuiting from UserManager. Needs to be extracted because of SystemUI boolean flag + // qs_show_user_switcher_for_single_user + + final boolean userSwitcherEnabled = Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.USER_SWITCHER_ENABLED, 1) != 0; + + if (!UserManager.supportsMultipleUsers() + || mUserManager.hasUserRestriction(UserManager.DISALLOW_USER_SWITCH) + || UserManager.isDeviceInDemoMode(mContext) + || !userSwitcherEnabled) { + return false; + } + + final boolean guestEnabled = !mContext.getSystemService(DevicePolicyManager.class) + .getGuestUserDisabled(null); + return mUserSwitcherController.getSwitchableUserCount() > 1 || guestEnabled + || mContext.getResources().getBoolean(R.bool.qs_show_user_switcher_for_single_user); + } + private void registerListener() { if (mUserManager.isUserSwitcherEnabled() && mUserListener == null) { @@ -118,29 +137,20 @@ public class MultiUserSwitch extends FrameLayout implements View.OnClickListener @Override public void onClick(View v) { - if (mUserManager.isUserSwitcherEnabled()) { - if (mKeyguardMode) { - if (mKeyguardUserSwitcher != null) { - mKeyguardUserSwitcher.show(true /* animate */); - } - } else if (mQsPanel != null && mUserSwitcherController != null) { - View center = getChildCount() > 0 ? getChildAt(0) : this; - - center.getLocationInWindow(mTmpInt2); - mTmpInt2[0] += center.getWidth() / 2; - mTmpInt2[1] += center.getHeight() / 2; - - mQsPanel.showDetailAdapter(true, - getUserDetailAdapter(), - mTmpInt2); - } - } else { - if (mQsPanel != null) { - Intent intent = ContactsContract.QuickContact.composeQuickContactsIntent( - getContext(), v, ContactsContract.Profile.CONTENT_URI, - ContactsContract.QuickContact.MODE_LARGE, null); - Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(intent, 0); + if (mKeyguardMode) { + if (mKeyguardUserSwitcher != null) { + mKeyguardUserSwitcher.show(true /* animate */); } + } else if (mQsPanel != null && mUserSwitcherController != null) { + View center = getChildCount() > 0 ? getChildAt(0) : this; + + center.getLocationInWindow(mTmpInt2); + mTmpInt2[0] += center.getWidth() / 2; + mTmpInt2[1] += center.getHeight() / 2; + + mQsPanel.showDetailAdapter(true, + getUserDetailAdapter(), + mTmpInt2); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationAssistantAction.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationAssistantAction.java deleted file mode 100644 index 323e7761b475..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationAssistantAction.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2019 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.systemui.statusbar.phone; - -import android.annotation.NonNull; -import android.os.Bundle; -import android.view.MotionEvent; - -import com.android.systemui.assist.AssistManager; -import com.android.systemui.recents.OverviewProxyService; - -/** - * Assistant is triggered with this action - */ -public class NavigationAssistantAction extends NavigationGestureAction { - private static final String TAG = "NavigationAssistantActions"; - - private final AssistManager mAssistManager; - - public NavigationAssistantAction(@NonNull NavigationBarView navigationBarView, - @NonNull OverviewProxyService service, AssistManager assistManager) { - super(navigationBarView, service); - mAssistManager = assistManager; - } - - @Override - public boolean isEnabled() { - return true; - } - - @Override - public boolean disableProxyEvents() { - return true; - } - - @Override - public void onGestureStart(MotionEvent event) { - mAssistManager.startAssist(new Bundle()); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBackAction.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBackAction.java deleted file mode 100644 index c77b16b23e67..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBackAction.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright (C) 2018 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.systemui.statusbar.phone; - -import android.annotation.NonNull; -import android.hardware.input.InputManager; -import android.os.Handler; -import android.os.SystemClock; -import android.view.InputDevice; -import android.view.KeyCharacterMap; -import android.view.KeyEvent; -import android.view.MotionEvent; - -import com.android.systemui.recents.OverviewProxyService; - -/** - * A back action when triggered will execute a back command - */ -public class NavigationBackAction extends NavigationGestureAction { - - private static final String BACK_AFTER_END_PROP = - "quickstepcontroller_homegoesbackwhenend"; - private static final long BACK_BUTTON_FADE_OUT_ALPHA = 60; - private static final long BACK_GESTURE_POLL_TIMEOUT = 1000; - - private final Handler mHandler = new Handler(); - - private final Runnable mExecuteBackRunnable = new Runnable() { - @Override - public void run() { - if (isEnabled() && canPerformAction()) { - performBack(); - mHandler.postDelayed(this, BACK_GESTURE_POLL_TIMEOUT); - } - } - }; - - public NavigationBackAction(@NonNull NavigationBarView navigationBarView, - @NonNull OverviewProxyService service) { - super(navigationBarView, service); - } - - @Override - public boolean allowHitTargetToMoveOverDrag() { - return true; - } - - @Override - public boolean canPerformAction() { - return mProxySender.getBackButtonAlpha() > 0; - } - - @Override - public boolean isEnabled() { - return true; - } - - @Override - protected void onGestureStart(MotionEvent event) { - if (!QuickStepController.shouldHideBackButton(getContext())) { - mNavigationBarView.getBackButton().setAlpha(0 /* alpha */, true /* animate */, - BACK_BUTTON_FADE_OUT_ALPHA); - } - mHandler.removeCallbacks(mExecuteBackRunnable); - if (!shouldExecuteBackOnUp()) { - performBack(); - mHandler.postDelayed(mExecuteBackRunnable, BACK_GESTURE_POLL_TIMEOUT); - } - } - - @Override - protected void onGestureEnd() { - mHandler.removeCallbacks(mExecuteBackRunnable); - if (!QuickStepController.shouldHideBackButton(getContext())) { - mNavigationBarView.getBackButton().setAlpha( - mProxySender.getBackButtonAlpha(), true /* animate */); - } - if (shouldExecuteBackOnUp()) { - performBack(); - } - } - - @Override - public boolean disableProxyEvents() { - return true; - } - - private void performBack() { - sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK); - sendEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK); - } - - private boolean shouldExecuteBackOnUp() { - return getGlobalBoolean(BACK_AFTER_END_PROP); - } - - private void sendEvent(int action, int code) { - long when = SystemClock.uptimeMillis(); - final KeyEvent ev = new KeyEvent(when, when, action, code, 0 /* repeat */, - 0 /* metaState */, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */, - KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY, - InputDevice.SOURCE_KEYBOARD); - InputManager.getInstance().injectInputEvent(ev, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarEdgePanel.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarEdgePanel.java index 48974649191a..86b53445468c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarEdgePanel.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarEdgePanel.java @@ -17,25 +17,22 @@ package com.android.systemui.statusbar.phone; import android.animation.ObjectAnimator; -import android.annotation.NonNull; import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; -import android.graphics.PixelFormat; import android.os.SystemClock; +import android.os.VibrationEffect; import android.util.FloatProperty; import android.util.MathUtils; -import android.view.Gravity; import android.view.HapticFeedbackConstants; import android.view.MotionEvent; import android.view.View; -import android.view.WindowManager; +import com.android.systemui.Dependency; import com.android.systemui.R; -import com.android.systemui.shared.system.QuickStepContract; +import com.android.systemui.statusbar.VibratorHelper; public class NavigationBarEdgePanel extends View { - private static final String TAG = "NavigationBarEdgePanel"; // TODO: read from resources once drawing is finalized. private static final boolean SHOW_PROTECTION_STROKE = true; @@ -52,6 +49,8 @@ public class NavigationBarEdgePanel extends View { private static final int ANIM_DURATION_MS = 150; private static final long HAPTIC_TIMEOUT_MS = 200; + private final VibratorHelper mVibratorHelper; + private final Paint mPaint = new Paint(); private final Paint mProtectionPaint = new Paint(); @@ -63,9 +62,11 @@ public class NavigationBarEdgePanel extends View { private final float mPointExtent; private final float mHeight; private final float mStrokeThickness; - private final boolean mIsLeftPanel; - private float mStartY; + private final float mSwipeThreshold; + + private boolean mIsLeftPanel; + private float mStartX; private boolean mDragSlopPassed; @@ -105,28 +106,11 @@ public class NavigationBarEdgePanel extends View { } }; - public static NavigationBarEdgePanel create(@NonNull Context context, int width, int height, - int gravity) { - final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(width, height, - WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, - WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING - | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE - | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL - | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, - PixelFormat.TRANSLUCENT); - lp.gravity = gravity; - lp.setTitle(TAG + context.getDisplayId()); - lp.accessibilityTitle = context.getString(R.string.nav_bar_edge_panel); - lp.windowAnimations = 0; - NavigationBarEdgePanel panel = new NavigationBarEdgePanel( - context, (gravity & Gravity.LEFT) == Gravity.LEFT); - panel.setLayoutParams(lp); - return panel; - } - - private NavigationBarEdgePanel(Context context, boolean isLeftPanel) { + public NavigationBarEdgePanel(Context context) { super(context); + mVibratorHelper = Dependency.get(VibratorHelper.class); + mEndAnimator = ObjectAnimator.ofFloat(this, DRAG_PROGRESS, 1f); mEndAnimator.setAutoCancel(true); mEndAnimator.setDuration(ANIM_DURATION_MS); @@ -154,43 +138,14 @@ public class NavigationBarEdgePanel extends View { // Both panels arrow point the same way mArrowsPointLeft = getLayoutDirection() == LAYOUT_DIRECTION_LTR; - mIsLeftPanel = isLeftPanel; - } - public void setWindowFlag(int flags, boolean enable) { - WindowManager.LayoutParams lp = (WindowManager.LayoutParams) getLayoutParams(); - if (lp == null || enable == ((lp.flags & flags) != 0)) { - return; - } - if (enable) { - lp.flags |= flags; - } else { - lp.flags &= ~flags; - } - updateLayout(lp); + mSwipeThreshold = context.getResources() + .getDimension(R.dimen.navigation_edge_action_drag_threshold); + setVisibility(GONE); } - @Override - public boolean onTouchEvent(MotionEvent event) { - switch (event.getActionMasked()) { - case MotionEvent.ACTION_DOWN : { - mDragSlopPassed = false; - show(event.getX(), event.getY()); - break; - } - case MotionEvent.ACTION_MOVE: { - handleNewSwipePoint(event.getX()); - break; - } - // Fall through - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: { - hide(); - break; - } - } - - return false; + public void setIsLeftPanel(boolean isLeftPanel) { + mIsLeftPanel = isLeftPanel; } @Override @@ -200,7 +155,7 @@ public class NavigationBarEdgePanel extends View { canvas.save(); canvas.translate( mIsLeftPanel ? edgeOffset : getWidth() - edgeOffset, - mStartY - mHeight * 0.5f); + (getHeight() - mHeight) * 0.5f); float outsideX = mArrowsPointLeft ? animatedOffset : 0; float middleX = mArrowsPointLeft ? 0 : animatedOffset; @@ -223,15 +178,6 @@ public class NavigationBarEdgePanel extends View { mGestureLength = getWidth(); } - public void setDimensions(int width, int height) { - final WindowManager.LayoutParams lp = (WindowManager.LayoutParams) getLayoutParams(); - if (lp.width != width || lp.height != height) { - lp.width = width; - lp.height = height; - updateLayout(lp); - } - } - private void setLegProgress(float progress) { mLegProgress = progress; invalidate(); @@ -251,29 +197,48 @@ public class NavigationBarEdgePanel extends View { } private void hide() { - animate().alpha(0f).setDuration(ANIM_DURATION_MS); + animate().alpha(0f).setDuration(ANIM_DURATION_MS) + .withEndAction(() -> setVisibility(GONE)); } - private void show(float x, float y) { - mEndAnimator.cancel(); - mLegAnimator.cancel(); - setLegProgress(0f); - setDragProgress(0f); - setAlpha(1f); - - float halfHeight = mHeight * 0.5f; - mStartY = MathUtils.constrain(y, halfHeight, getHeight() - halfHeight); - mStartX = x; + /** + * Updates the UI based on the motion events passed in device co-ordinates + */ + public void handleTouch(MotionEvent event) { + switch (event.getActionMasked()) { + case MotionEvent.ACTION_DOWN : { + mDragSlopPassed = false; + mEndAnimator.cancel(); + mLegAnimator.cancel(); + animate().cancel(); + setLegProgress(0f); + setDragProgress(0f); + mStartX = event.getX(); + setVisibility(VISIBLE); + break; + } + case MotionEvent.ACTION_MOVE: { + handleNewSwipePoint(event.getX()); + break; + } + // Fall through + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: { + hide(); + break; + } + } } private void handleNewSwipePoint(float x) { float dist = MathUtils.abs(x - mStartX); // Apply a haptic on drag slop passed - if (!mDragSlopPassed && dist > QuickStepContract.getQuickStepDragSlopPx()) { + if (!mDragSlopPassed && dist > mSwipeThreshold) { mDragSlopPassed = true; - performHapticFeedback(HapticFeedbackConstants.CLOCK_TICK); + mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK); mLastSlopHapticTime = SystemClock.uptimeMillis(); + setAlpha(1f); } setDragProgress(MathUtils.constrainedMap( @@ -311,11 +276,6 @@ public class NavigationBarEdgePanel extends View { } } - private void updateLayout(WindowManager.LayoutParams lp) { - WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE); - wm.updateViewLayout(this, lp); - } - private float dp(float dp) { return mDensity * dp; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java index 94856234503c..3dcadf1d5071 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java @@ -91,6 +91,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.recents.Recents; import com.android.systemui.shared.system.ActivityManagerWrapper; +import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.stackdivider.Divider; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.CommandQueue.Callbacks; @@ -201,7 +202,7 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback @Override public void onBackButtonAlphaChanged(float alpha, boolean animate) { final ButtonDispatcher backButton = mNavigationBarView.getBackButton(); - if (QuickStepController.shouldHideBackButton(getContext())) { + if (QuickStepContract.isGesturalMode(getContext())) { // If property was changed to hide/show back button, going home will trigger // launcher to to change the back button alpha to reflect property change backButton.setVisibility(View.GONE); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java index f22ecf6792bc..7abdbd03a56b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -17,19 +17,9 @@ package com.android.systemui.statusbar.phone; import static android.content.Intent.ACTION_OVERLAY_CHANGED; -import static android.view.MotionEvent.ACTION_DOWN; import static android.view.WindowManagerPolicyConstants.NAV_BAR_INVALID; -import static android.view.WindowManagerPolicyConstants.NAV_BAR_LEFT; -import static android.view.WindowManagerPolicyConstants.NAV_BAR_RIGHT; -import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_DISABLE_QUICK_SCRUB; import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_SHOW_OVERVIEW_BUTTON; -import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_BACK; -import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_DEAD_ZONE; -import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_HOME; -import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_NONE; -import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_OVERVIEW; -import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_ROTATION; import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE; import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.NAV_BAR_VIEWS; @@ -40,14 +30,11 @@ import android.animation.PropertyValuesHolder; import android.animation.TimeInterpolator; import android.animation.ValueAnimator; import android.annotation.DrawableRes; -import android.annotation.IntDef; -import android.annotation.SuppressLint; import android.app.StatusBarManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.pm.ParceledListSlice; import android.content.res.Configuration; import android.graphics.Canvas; import android.graphics.Point; @@ -56,15 +43,11 @@ import android.graphics.Region; import android.graphics.Region.Op; import android.os.Bundle; import android.os.RemoteException; -import android.os.SystemProperties; import android.util.AttributeSet; import android.util.Log; import android.util.Slog; import android.util.SparseArray; import android.view.Display; -import android.view.Gravity; -import android.view.IPinnedStackController; -import android.view.IPinnedStackListener; import android.view.MotionEvent; import android.view.Surface; import android.view.View; @@ -92,31 +75,19 @@ import com.android.systemui.recents.Recents; import com.android.systemui.recents.RecentsOnboarding; import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.shared.system.ActivityManagerWrapper; -import com.android.systemui.shared.system.NavigationBarCompat; import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.shared.system.WindowManagerWrapper; -import com.android.systemui.statusbar.phone.NavigationPrototypeController.GestureAction; -import com.android.systemui.statusbar.phone.NavigationPrototypeController.OnPrototypeChangedListener; import com.android.systemui.statusbar.policy.DeadZone; import com.android.systemui.statusbar.policy.KeyButtonDrawable; import java.io.FileDescriptor; import java.io.PrintWriter; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.util.function.Consumer; public class NavigationBarView extends FrameLayout implements PluginListener<NavGesture> { final static boolean DEBUG = false; final static String TAG = "StatusBar/NavBarView"; - @Retention(RetentionPolicy.SOURCE) - @IntDef({WINDOW_TARGET_BOTTOM, WINDOW_TARGET_LEFT, WINDOW_TARGET_RIGHT}) - public @interface WindowTarget{} - public static final int WINDOW_TARGET_BOTTOM = 0; - public static final int WINDOW_TARGET_LEFT = 1; - public static final int WINDOW_TARGET_RIGHT = 2; - // slippery nav bar when everything is disabled, e.g. during setup final static boolean SLIPPERY_WHEN_DISABLED = true; @@ -134,8 +105,6 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav int mDisabledFlags = 0; int mNavigationIconHints = 0; - private @NavigationBarCompat.HitTarget int mDownHitTarget = HIT_TARGET_NONE; - private @WindowTarget int mWindowHitTarget = WINDOW_TARGET_BOTTOM; private Rect mHomeButtonBounds = new Rect(); private Rect mBackButtonBounds = new Rect(); private Rect mRecentsButtonBounds = new Rect(); @@ -148,6 +117,7 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav private KeyButtonDrawable mRecentIcon; private KeyButtonDrawable mDockedIcon; + private final EdgeBackGestureHandler mEdgeBackGestureHandler; private GestureHelper mGestureHelper; private final DeadZone mDeadZone; private boolean mDeadZoneConsuming = false; @@ -175,16 +145,6 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav private NavBarTintController mTintController; private boolean mAssistantAvailable; - private NavigationPrototypeController mPrototypeController; - private NavigationGestureAction[] mDefaultGestureMap; - private QuickScrubAction mQuickScrubAction; - private QuickStepAction mQuickStepAction; - private NavigationBackAction mBackAction; - private QuickSwitchAction mQuickSwitchAction; - private NavigationAssistantAction mAssistantAction; - - private NavigationBarEdgePanel mLeftEdgePanel; - private NavigationBarEdgePanel mRightEdgePanel; /** * Helper that is responsible for showing the right toast when a disallowed activity operation @@ -248,18 +208,6 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav } }; - private final OnTouchListener mEdgePanelTouchListener = new OnTouchListener() { - @SuppressLint("ClickableViewAccessibility") - @Override - public boolean onTouch(View v, MotionEvent event) { - if (event.getActionMasked() == ACTION_DOWN) { - mWindowHitTarget = v == mLeftEdgePanel ? WINDOW_TARGET_LEFT : WINDOW_TARGET_RIGHT; - mDownHitTarget = HIT_TARGET_NONE; - } - return mGestureHelper.onTouchEvent(event); - } - }; - private final AccessibilityDelegate mQuickStepAccessibilityDelegate = new AccessibilityDelegate() { private AccessibilityAction mToggleOverviewAction; @@ -286,104 +234,10 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav } }; - // TODO(b/112934365): To be removed - private OnPrototypeChangedListener mPrototypeListener = new OnPrototypeChangedListener() { - @Override - public void onGestureRemap(int[] actions) { - updateNavigationGestures(); - } - - @Override - public void onBackButtonVisibilityChanged(boolean visible) { - if (!inScreenPinning()) { - getBackButton().setVisibility(QuickStepController.shouldHideBackButton(getContext()) - ? GONE : VISIBLE); - } - } - - @Override - public void onHomeButtonVisibilityChanged(boolean visible) { - getHomeButton().setVisibility(QuickStepController.shouldHideHomeButton(getContext()) - ? GONE : VISIBLE); - } - - @Override - public void onColorAdaptChanged(boolean enabled) { - if (enabled) { - mTintController.start(); - } else { - mTintController.stop(); - } - } - - @Override - public void onEdgeSensitivityChanged(int width, int height) { - if (mLeftEdgePanel != null) { - mLeftEdgePanel.setDimensions(width, height); - } - if (mRightEdgePanel != null) { - mRightEdgePanel.setDimensions(width, height); - } - } - - @Override - public void onHomeHandleVisiblilityChanged(boolean visible) { - showHomeHandle(QuickStepController.showHomeHandle(getContext())); - } - - @Override - public void onAssistantGestureEnabled(boolean enabled) { - updateAssistantAvailability(); - } - }; - - private final IPinnedStackListener.Stub mImeChangedListener = new IPinnedStackListener.Stub() { - @Override - public void onListenerRegistered(IPinnedStackController controller) { - } - - @Override - public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) { - post(() -> { - // When the ime changes visibility, resize the edge panels to not cover the ime - final int width = mPrototypeController.getEdgeSensitivityWidth(); - int height = mContext.getDisplay().getHeight() - imeHeight; - if (!imeVisible) { - // Hide the navigation bar area at the bottom for gestures - height -= getResources().getDimensionPixelOffset(R.dimen.navigation_bar_height); - } - if (mLeftEdgePanel != null) { - mLeftEdgePanel.setDimensions(width, height); - } - if (mRightEdgePanel != null) { - mRightEdgePanel.setDimensions(width, height); - } - }); - } - - @Override - public void onShelfVisibilityChanged(boolean shelfVisible, int shelfHeight) { - } - - @Override - public void onMinimizedStateChanged(boolean isMinimized) { - } - - @Override - public void onMovementBoundsChanged(Rect insetBounds, Rect normalBounds, - Rect animatingBounds, boolean fromImeAdjustment, boolean fromShelfAdjustment, - int displayRotation) { - } - - @Override - public void onActionsChanged(ParceledListSlice actions) { - } - }; - private BroadcastReceiver mOverlaysChangedReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - showHomeHandle(QuickStepController.showHomeHandle(getContext())); + onOverlaysChanged(); } }; @@ -431,23 +285,9 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav mButtonDispatchers.put(R.id.menu_container, mContextualButtonGroup); mDeadZone = new DeadZone(this); - mQuickScrubAction = new QuickScrubAction(this, mOverviewProxyService); - mQuickStepAction = new QuickStepAction(this, mOverviewProxyService); - mBackAction = new NavigationBackAction(this, mOverviewProxyService); - mQuickSwitchAction = new QuickSwitchAction(this, mOverviewProxyService); - mDefaultGestureMap = new NavigationGestureAction[] { - mQuickStepAction, null /* swipeDownAction*/, null /* swipeLeftAction */, - mQuickScrubAction, null /* swipeLeftEdgeAction */, null /* swipeRightEdgeAction */ - }; - - mPrototypeController = new NavigationPrototypeController(context); - mPrototypeController.register(); - mPrototypeController.setOnPrototypeChangedListener(mPrototypeListener); + mEdgeBackGestureHandler = new EdgeBackGestureHandler(context, mOverviewProxyService); mTintController = new NavBarTintController(this, getLightTransitionsController()); - IntentFilter filter = new IntentFilter(ACTION_OVERLAY_CHANGED); - filter.addDataScheme("package"); - context.registerReceiver(mOverlaysChangedReceiver, filter); } public NavBarTintController getTintController() { @@ -464,14 +304,6 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav public void setComponents(NotificationPanelView panel, AssistManager assistManager) { mPanelView = panel; - if (mAssistantAction == null) { - mAssistantAction = new NavigationAssistantAction(this, mOverviewProxyService, - assistManager); - } - if (mGestureHelper instanceof QuickStepController) { - ((QuickStepController) mGestureHelper).setComponents(this); - updateNavigationGestures(); - } } @Override @@ -480,44 +312,6 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav mTintController.onDraw(); } - private void updateNavigationGestures() { - if (mGestureHelper instanceof QuickStepController) { - // TODO: Clarify this when we remove the prototype controller - final int[] gesturalMap = {0, 7, 1, 1, 3, 3}; - final int[] normalMap = {0, 0, 0, 0, 0, 0}; - final int[] assignedMap = QuickStepContract.isGesturalMode(getContext()) - ? gesturalMap - : normalMap; - ((QuickStepController) mGestureHelper).setGestureActions( - getNavigationActionFromType(assignedMap[0], mDefaultGestureMap[0]), - getNavigationActionFromType(assignedMap[1], mDefaultGestureMap[1]), - getNavigationActionFromType(assignedMap[2], mDefaultGestureMap[2]), - getNavigationActionFromType(assignedMap[3], mDefaultGestureMap[3]), - getNavigationActionFromType(assignedMap[4], mDefaultGestureMap[4]), - getNavigationActionFromType(assignedMap[5], mDefaultGestureMap[5])); - } - } - - private NavigationGestureAction getNavigationActionFromType(@GestureAction int actionType, - NavigationGestureAction defaultAction) { - switch(actionType) { - case NavigationPrototypeController.ACTION_QUICKSTEP: - return mQuickStepAction; - case NavigationPrototypeController.ACTION_QUICKSCRUB: - return mQuickScrubAction; - case NavigationPrototypeController.ACTION_BACK: - return mBackAction; - case NavigationPrototypeController.ACTION_QUICKSWITCH: - return mQuickSwitchAction; - case NavigationPrototypeController.ACTION_ASSISTANT: - return mAssistantAction; - case NavigationPrototypeController.ACTION_NOTHING: - return null; - default: - return defaultAction; - } - } - public void setOnVerticalChangedListener(OnVerticalChangedListener onVerticalChangedListener) { mOnVerticalChangedListener = onVerticalChangedListener; notifyVerticalChangedListener(mIsVertical); @@ -525,28 +319,7 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav @Override public boolean onInterceptTouchEvent(MotionEvent event) { - final boolean deadZoneConsumed = shouldDeadZoneConsumeTouchEvents(event); - switch (event.getActionMasked()) { - case ACTION_DOWN: - int x = (int) event.getX(); - int y = (int) event.getY(); - mDownHitTarget = HIT_TARGET_NONE; - mWindowHitTarget = WINDOW_TARGET_BOTTOM; - if (deadZoneConsumed) { - mDownHitTarget = HIT_TARGET_DEAD_ZONE; - } else if (getBackButton().isVisible() && mBackButtonBounds.contains(x, y)) { - mDownHitTarget = HIT_TARGET_BACK; - } else if (getHomeButton().isVisible() && mHomeButtonBounds.contains(x, y)) { - mDownHitTarget = HIT_TARGET_HOME; - } else if (getRecentsButton().isVisible() && mRecentsButtonBounds.contains(x, y)) { - mDownHitTarget = HIT_TARGET_OVERVIEW; - } else if (getRotateSuggestionButton().isVisible() - && mRotationButtonBounds.contains(x, y)) { - mDownHitTarget = HIT_TARGET_ROTATION; - } - break; - } - return mGestureHelper.onInterceptTouchEvent(event); + return shouldDeadZoneConsumeTouchEvents(event) || super.onInterceptTouchEvent(event); } @Override @@ -570,8 +343,12 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav } private boolean shouldDeadZoneConsumeTouchEvents(MotionEvent event) { + int action = event.getActionMasked(); + if (action == MotionEvent.ACTION_DOWN) { + mDeadZoneConsuming = false; + } if (mDeadZone.onTouchEvent(event) || mDeadZoneConsuming) { - switch (event.getActionMasked()) { + switch (action) { case MotionEvent.ACTION_DOWN: // Allow gestures starting in the deadzone to be slippery setSlippery(true); @@ -589,14 +366,6 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav return false; } - public @NavigationBarCompat.HitTarget int getDownHitTarget() { - return mDownHitTarget; - } - - public @WindowTarget int getWindowTarget() { - return mWindowHitTarget; - } - public void abortCurrentGesture() { getHomeButton().abortCurrentGesture(); } @@ -654,13 +423,6 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav return mOverviewProxyService.shouldShowSwipeUpUI() && isOverviewEnabled(); } - public boolean isQuickScrubEnabled() { - // TODO(b/112934365): Remove this sys prop flag - return SystemProperties.getBoolean("persist.quickstep.scrub.enabled", true) - && mOverviewProxyService.isEnabled() && isOverviewEnabled() - && ((mOverviewProxyService.getInteractionFlags() & FLAG_DISABLE_QUICK_SCRUB) == 0); - } - private void reloadNavIcons() { updateIcons(Configuration.EMPTY); } @@ -802,7 +564,7 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav mBarTransitions.reapplyDarkIntensity(); - boolean disableHome = QuickStepController.shouldHideHomeButton(getContext()) + boolean disableHome = QuickStepContract.isGesturalMode(getContext()) || ((mDisabledFlags & View.STATUS_BAR_DISABLE_HOME) != 0); // TODO(b/113914868): investigation log for disappearing home button @@ -812,7 +574,7 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav // Always disable recents when alternate car mode UI is active and for secondary displays. boolean disableRecent = isRecentsButtonDisabled(); - boolean disableBack = QuickStepController.shouldHideBackButton(getContext()) + boolean disableBack = QuickStepContract.isGesturalMode(getContext()) || (((mDisabledFlags & View.STATUS_BAR_DISABLE_BACK) != 0) && !useAltBack); // When screen pinning, don't hide back and home when connected service or back and @@ -921,10 +683,6 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav final boolean showSwipeUpUI = mOverviewProxyService.shouldShowSwipeUpUI(); if (mNavigationInflaterView != null) { - if (mPrototypeController.showHomeHandle()) { - showHomeHandle(true /* visible */); - } - // Reinflate the navbar if needed, no-op unless the swipe up state changes mNavigationInflaterView.onLikelyDefaultLayoutChange(); } @@ -957,16 +715,6 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav setWindowFlag(WindowManager.LayoutParams.FLAG_SLIPPERY, slippery); } - public void setWindowTouchable(boolean flag) { - setWindowFlag(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE, !flag); - if (mLeftEdgePanel != null) { - mLeftEdgePanel.setWindowFlag(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE, !flag); - } - if (mRightEdgePanel != null) { - mRightEdgePanel.setWindowFlag(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE, !flag); - } - } - private void setWindowFlag(int flags, boolean enable) { final ViewGroup navbarView = ((ViewGroup) getParent()); if (navbarView == null) { @@ -985,15 +733,16 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav wm.updateViewLayout(navbarView, lp); } - private void showHomeHandle(boolean visible) { + private void onOverlaysChanged() { mNavigationInflaterView.onTuningChanged(NAV_BAR_VIEWS, null); // Color adaption is tied with showing home handle, only avaliable if visible - if (visible) { + if (QuickStepContract.isGesturalMode(getContext())) { mTintController.start(); } else { mTintController.stop(); } + mEdgeBackGestureHandler.onOverlaysChanged(); } public void setAssistantAvailable(boolean available) { @@ -1186,17 +935,6 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav } catch (RemoteException e) { Slog.e(TAG, "Failed to get nav bar position.", e); } - - // For landscape, hide the panel that would interfere with navigation bar layout - if (mLeftEdgePanel != null && mRightEdgePanel != null) { - mLeftEdgePanel.setVisibility(VISIBLE); - mRightEdgePanel.setVisibility(VISIBLE); - if (navBarPos == NAV_BAR_LEFT) { - mLeftEdgePanel.setVisibility(GONE); - } else if (navBarPos == NAV_BAR_RIGHT) { - mRightEdgePanel.setVisibility(GONE); - } - } mGestureHelper.setBarState(isRtl, navBarPos); } @@ -1320,27 +1058,10 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav NavGesture.class, false /* Only one */); setUpSwipeUpOnboarding(isQuickStepSwipeUpEnabled()); - if (QuickStepContract.isGesturalMode(getContext())) { - WindowManager wm = (WindowManager) getContext() - .getSystemService(Context.WINDOW_SERVICE); - int width = mPrototypeController.getEdgeSensitivityWidth(); - int height = mPrototypeController.getEdgeSensitivityHeight(); - // Explicitly left and right, not start and end as this is device relative. - mLeftEdgePanel = NavigationBarEdgePanel.create(getContext(), width, height, - Gravity.LEFT | Gravity.TOP); - mRightEdgePanel = NavigationBarEdgePanel.create(getContext(), width, height, - Gravity.RIGHT | Gravity.TOP); - mLeftEdgePanel.setOnTouchListener(mEdgePanelTouchListener); - mRightEdgePanel.setOnTouchListener(mEdgePanelTouchListener); - wm.addView(mLeftEdgePanel, mLeftEdgePanel.getLayoutParams()); - wm.addView(mRightEdgePanel, mRightEdgePanel.getLayoutParams()); - - try { - WindowManagerWrapper.getInstance().addPinnedStackListener(mImeChangedListener); - } catch (RemoteException e) { - Log.e(TAG, "Failed to register pinned stack listener", e); - } - } + IntentFilter filter = new IntentFilter(ACTION_OVERLAY_CHANGED); + filter.addDataScheme("package"); + getContext().registerReceiver(mOverlaysChangedReceiver, filter); + mEdgeBackGestureHandler.onNavBarAttached(); } @Override @@ -1350,21 +1071,13 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav if (mGestureHelper != null) { mGestureHelper.destroy(); } - mPrototypeController.unregister(); - getContext().unregisterReceiver(mOverlaysChangedReceiver); setUpSwipeUpOnboarding(false); for (int i = 0; i < mButtonDispatchers.size(); ++i) { mButtonDispatchers.valueAt(i).onDestroy(); } - WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE); - if (mLeftEdgePanel != null) { - wm.removeView(mLeftEdgePanel); - } - if (mRightEdgePanel != null) { - wm.removeView(mRightEdgePanel); - } - WindowManagerWrapper.getInstance().removePinnedStackListener(mImeChangedListener); + getContext().unregisterReceiver(mOverlaysChangedReceiver); + mEdgeBackGestureHandler.onNavBarDetached(); } private void setUpSwipeUpOnboarding(boolean connectedToOverviewProxy) { @@ -1383,12 +1096,10 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav @Override public void onPluginDisconnected(NavGesture plugin) { - QuickStepController defaultHelper = new QuickStepController(getContext()); - defaultHelper.setComponents(this); if (mGestureHelper != null) { mGestureHelper.destroy(); + mGestureHelper = null; } - mGestureHelper = defaultHelper; updateTaskSwitchHelper(); } @@ -1431,14 +1142,6 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav mContextualButtonGroup.dump(pw); if (mGestureHelper != null) { - pw.println("Navigation Gesture Actions {"); - pw.print(" "); pw.println("QuickScrub Enabled=" + mQuickScrubAction.isEnabled()); - pw.print(" "); pw.println("QuickScrub Active=" + mQuickScrubAction.isActive()); - pw.print(" "); pw.println("QuickStep Enabled=" + mQuickStepAction.isEnabled()); - pw.print(" "); pw.println("QuickStep Active=" + mQuickStepAction.isActive()); - pw.print(" "); pw.println("Back Gesture Enabled=" + mBackAction.isEnabled()); - pw.print(" "); pw.println("Back Gesture Active=" + mBackAction.isActive()); - pw.println("}"); mGestureHelper.dump(pw); } mRecentsOnboarding.dump(pw); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationGestureAction.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationGestureAction.java deleted file mode 100644 index eca14eb7402d..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationGestureAction.java +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Copyright (C) 2018 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.systemui.statusbar.phone; - -import static android.view.WindowManagerPolicyConstants.NAV_BAR_LEFT; -import static android.view.WindowManagerPolicyConstants.NAV_BAR_RIGHT; - -import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_NONE; - -import android.annotation.NonNull; -import android.content.Context; -import android.graphics.Canvas; -import android.view.MotionEvent; - -import com.android.systemui.recents.OverviewProxyService; - -/** - * A gesture action that would be triggered and reassigned by {@link QuickStepController} - */ -public abstract class NavigationGestureAction { - protected final NavigationBarView mNavigationBarView; - protected final OverviewProxyService mProxySender; - - protected int mNavigationBarPosition; - protected boolean mDragHorizontalPositive; - protected boolean mDragVerticalPositive; - private boolean mIsActive; - - public NavigationGestureAction(@NonNull NavigationBarView navigationBarView, - @NonNull OverviewProxyService service) { - mNavigationBarView = navigationBarView; - mProxySender = service; - } - - /** - * Pass event that the state of the bar (such as rotation) has changed - * @param changed if rotation or drag positive direction (such as ltr) has changed - * @param navBarPos position of navigation bar - * @param dragHorPositive direction of positive horizontal drag, could change with ltr changes - * @param dragVerPositive direction of positive vertical drag, could change with ltr changes - */ - public void setBarState(boolean changed, int navBarPos, boolean dragHorPositive, - boolean dragVerPositive) { - mNavigationBarPosition = navBarPos; - mDragHorizontalPositive = dragHorPositive; - mDragVerticalPositive = dragVerPositive; - } - - /** - * Resets the state of the action. Called when touch down occurs over the Navigation Bar. - */ - public void reset() { - mIsActive = false; - } - - /** - * Start the gesture and the action will be active - * @param event the event that caused the gesture - */ - public void startGesture(MotionEvent event) { - mIsActive = true; - onGestureStart(event); - } - - /** - * Gesture has ended with action cancel or up and this action will not be active - */ - public void endGesture() { - mIsActive = false; - onGestureEnd(); - } - - /** - * If the action is currently active based on the gesture that triggered it. Only one action - * can occur at a time - * @return whether or not if this action has been triggered - */ - public boolean isActive() { - return mIsActive; - } - - /** - * @return whether or not this action can run if notification shade is shown - */ - public boolean canRunWhenNotificationsShowing() { - return true; - } - - /** - * @return whether or not this action triggers when starting a gesture from a certain hit target - * If {@link HIT_TARGET_NONE} is specified then action does not need to be triggered by button - */ - public int requiresTouchDownHitTarget() { - return HIT_TARGET_NONE; - } - - /** - * @return whether or not to move the button that started gesture over with user input drag - */ - public boolean allowHitTargetToMoveOverDrag() { - return false; - } - - /** - * Tell if the action is able to execute. Note that {@link #isEnabled()} must be true for this - * to be checked. The difference between this and {@link #isEnabled()} is that this dependent - * on the state of the navigation bar - * @return true if action can execute after gesture activates based on current states - */ - public boolean canPerformAction() { - return true; - } - - /** - * Decide if the controller should not send the current motion event to launcher via - * {@link OverviewProxyService} - * @return if controller should not proxy - */ - public boolean disableProxyEvents() { - return false; - } - - /** - * Tell if action is enabled. Compared to {@link #canPerformAction()} this is based on settings - * if the action is disabled for a particular gesture. For example a back action can be enabled - * however if there is nothing to back to then {@link #canPerformAction()} should return false. - * In this way if the action requires {@link #allowHitTargetToMoveOverDrag()} then if enabled, - * the button can be dragged with a large dampening factor during the gesture but will not - * activate the action. - * @return true if this action is enabled and can run - */ - public abstract boolean isEnabled(); - - protected void onDarkIntensityChange(float intensity) { - } - - protected void onDraw(Canvas canvas) { - } - - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - } - - /** - * When gesture starts, this will run to execute the action - * @param event the event that triggered the gesture - */ - protected abstract void onGestureStart(MotionEvent event); - - /** - * Channels motion move events to the action to track the user inputs - * @param x the x position - * @param y the y position - */ - public void onGestureMove(int x, int y) { - } - - /** - * When gesture ends, this will run from action up or cancel - */ - protected void onGestureEnd() { - } - - protected Context getContext() { - return mNavigationBarView.getContext(); - } - - protected boolean isNavBarVertical() { - return mNavigationBarPosition == NAV_BAR_LEFT || mNavigationBarPosition == NAV_BAR_RIGHT; - } - - protected boolean getGlobalBoolean(@NonNull String key) { - return QuickStepController.getBoolGlobalSetting(getContext(), key); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickScrubAction.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickScrubAction.java deleted file mode 100644 index bbfd51a5bfe6..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickScrubAction.java +++ /dev/null @@ -1,286 +0,0 @@ -/* - * Copyright (C) 2018 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.systemui.statusbar.phone; - -import static com.android.systemui.Interpolators.ALPHA_IN; -import static com.android.systemui.Interpolators.ALPHA_OUT; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; -import android.animation.PropertyValuesHolder; -import android.annotation.NonNull; -import android.content.res.Resources; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.RadialGradient; -import android.graphics.Shader; -import android.util.FloatProperty; -import android.view.MotionEvent; -import android.view.View; - -import com.android.systemui.R; -import com.android.systemui.recents.OverviewProxyService; -import com.android.systemui.shared.recents.utilities.Utilities; - -/** - * QuickScrub action to send to launcher to start quickscrub gesture - */ -public class QuickScrubAction extends QuickSwitchAction { - private static final String TAG = "QuickScrubAction"; - - private static final float TRACK_SCALE = 0.95f; - private static final float GRADIENT_WIDTH = .75f; - private static final int ANIM_IN_DURATION_MS = 150; - private static final int ANIM_OUT_DURATION_MS = 134; - - private AnimatorSet mTrackAnimator; - private View mCurrentNavigationBarView; - - private float mTrackScale = TRACK_SCALE; - private float mTrackAlpha; - private float mHighlightCenter; - private float mDarkIntensity; - - private final int mTrackThickness; - private final int mTrackEndPadding; - private final Paint mTrackPaint = new Paint(); - - private final FloatProperty<QuickScrubAction> mTrackAlphaProperty = - new FloatProperty<QuickScrubAction>("TrackAlpha") { - @Override - public void setValue(QuickScrubAction action, float alpha) { - mTrackAlpha = alpha; - mNavigationBarView.invalidate(); - } - - @Override - public Float get(QuickScrubAction action) { - return mTrackAlpha; - } - }; - - private final FloatProperty<QuickScrubAction> mTrackScaleProperty = - new FloatProperty<QuickScrubAction>("TrackScale") { - @Override - public void setValue(QuickScrubAction action, float scale) { - mTrackScale = scale; - mNavigationBarView.invalidate(); - } - - @Override - public Float get(QuickScrubAction action) { - return mTrackScale; - } - }; - - private final FloatProperty<QuickScrubAction> mNavBarAlphaProperty = - new FloatProperty<QuickScrubAction>("NavBarAlpha") { - @Override - public void setValue(QuickScrubAction action, float alpha) { - if (mCurrentNavigationBarView != null) { - mCurrentNavigationBarView.setAlpha(alpha); - } - } - - @Override - public Float get(QuickScrubAction action) { - if (mCurrentNavigationBarView != null) { - return mCurrentNavigationBarView.getAlpha(); - } - return 1f; - } - }; - - private AnimatorListenerAdapter mQuickScrubEndListener = new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - if (mCurrentNavigationBarView != null) { - mCurrentNavigationBarView.setAlpha(1f); - } - mCurrentNavigationBarView = null; - updateHighlight(); - } - }; - - public QuickScrubAction(@NonNull NavigationBarView navigationBarView, - @NonNull OverviewProxyService service) { - super(navigationBarView, service); - mTrackPaint.setAntiAlias(true); - mTrackPaint.setDither(true); - - final Resources res = navigationBarView.getResources(); - mTrackThickness = res.getDimensionPixelSize(R.dimen.nav_quick_scrub_track_thickness); - mTrackEndPadding = res.getDimensionPixelSize(R.dimen.nav_quick_scrub_track_edge_padding); - } - - @Override - public void setBarState(boolean changed, int navBarPos, boolean dragHorPositive, - boolean dragVerPositive) { - super.setBarState(changed, navBarPos, dragHorPositive, dragVerPositive); - if (changed && isActive()) { - // End quickscrub if the state changes mid-transition - endQuickScrub(false /* animate */); - } - } - - @Override - public void reset() { - super.reset(); - - // End any existing quickscrub animations before starting the new transition - if (mTrackAnimator != null) { - mTrackAnimator.end(); - mTrackAnimator = null; - } - mCurrentNavigationBarView = mNavigationBarView.getCurrentView(); - } - - @Override - public void onLayout(boolean changed, int left, int top, int right, int bottom) { - final int paddingLeft = mNavigationBarView.getPaddingLeft(); - final int paddingTop = mNavigationBarView.getPaddingTop(); - final int paddingRight = mNavigationBarView.getPaddingRight(); - final int paddingBottom = mNavigationBarView.getPaddingBottom(); - final int width = (right - left) - paddingRight - paddingLeft; - final int height = (bottom - top) - paddingBottom - paddingTop; - final int x1, x2, y1, y2; - if (isNavBarVertical()) { - x1 = (width - mTrackThickness) / 2 + paddingLeft; - x2 = x1 + mTrackThickness; - y1 = paddingTop + mTrackEndPadding; - y2 = y1 + height - 2 * mTrackEndPadding; - } else { - y1 = (height - mTrackThickness) / 2 + paddingTop; - y2 = y1 + mTrackThickness; - x1 = mNavigationBarView.getPaddingStart() + mTrackEndPadding; - x2 = x1 + width - 2 * mTrackEndPadding; - } - mDragOverRect.set(x1, y1, x2, y2); - } - - @Override - public void onDarkIntensityChange(float intensity) { - mDarkIntensity = intensity; - updateHighlight(); - } - - @Override - public void onDraw(Canvas canvas) { - if (!isEnabled()) { - return; - } - mTrackPaint.setAlpha(Math.round(255f * mTrackAlpha)); - - // Scale the track, but apply the inverse scale from the nav bar - final float radius = mDragOverRect.height() / 2; - canvas.save(); - float translate = Utilities.clamp(mHighlightCenter, mDragOverRect.left, - mDragOverRect.right); - canvas.translate(translate, 0); - canvas.scale(mTrackScale / mNavigationBarView.getScaleX(), - 1f / mNavigationBarView.getScaleY(), - mDragOverRect.centerX(), mDragOverRect.centerY()); - canvas.drawRoundRect(mDragOverRect.left - translate, mDragOverRect.top, - mDragOverRect.right - translate, mDragOverRect.bottom, radius, radius, mTrackPaint); - canvas.restore(); - } - - @Override - public boolean isEnabled() { - return mNavigationBarView.isQuickScrubEnabled(); - } - - @Override - protected void onGestureStart(MotionEvent event) { - updateHighlight(); - ObjectAnimator trackAnimator = ObjectAnimator.ofPropertyValuesHolder(this, - PropertyValuesHolder.ofFloat(mTrackAlphaProperty, 1f), - PropertyValuesHolder.ofFloat(mTrackScaleProperty, 1f)); - trackAnimator.setInterpolator(ALPHA_IN); - trackAnimator.setDuration(ANIM_IN_DURATION_MS); - ObjectAnimator navBarAnimator = ObjectAnimator.ofFloat(this, mNavBarAlphaProperty, 0f); - navBarAnimator.setInterpolator(ALPHA_OUT); - navBarAnimator.setDuration(ANIM_OUT_DURATION_MS); - mTrackAnimator = new AnimatorSet(); - mTrackAnimator.playTogether(trackAnimator, navBarAnimator); - mTrackAnimator.start(); - - startQuickGesture(event); - } - - @Override - public void onGestureMove(int x, int y) { - super.onGestureMove(x, y); - mHighlightCenter = x; - mNavigationBarView.invalidate(); - } - - @Override - protected void onGestureEnd() { - endQuickScrub(true /* animate */); - } - - private void endQuickScrub(boolean animate) { - animateEnd(); - endQuickGesture(animate); - if (!animate) { - if (mTrackAnimator != null) { - mTrackAnimator.end(); - mTrackAnimator = null; - } - } - } - - private void updateHighlight() { - if (mDragOverRect.isEmpty()) { - return; - } - int colorBase, colorGrad; - if (mDarkIntensity > 0.5f) { - colorBase = getContext().getColor(R.color.quick_step_track_background_background_dark); - colorGrad = getContext().getColor(R.color.quick_step_track_background_foreground_dark); - } else { - colorBase = getContext().getColor(R.color.quick_step_track_background_background_light); - colorGrad = getContext().getColor(R.color.quick_step_track_background_foreground_light); - } - final RadialGradient mHighlight = new RadialGradient(0, mDragOverRect.height() / 2, - mDragOverRect.width() * GRADIENT_WIDTH, colorGrad, colorBase, - Shader.TileMode.CLAMP); - mTrackPaint.setShader(mHighlight); - } - - private void animateEnd() { - if (mTrackAnimator != null) { - mTrackAnimator.cancel(); - } - - ObjectAnimator trackAnimator = ObjectAnimator.ofPropertyValuesHolder(this, - PropertyValuesHolder.ofFloat(mTrackAlphaProperty, 0f), - PropertyValuesHolder.ofFloat(mTrackScaleProperty, TRACK_SCALE)); - trackAnimator.setInterpolator(ALPHA_OUT); - trackAnimator.setDuration(ANIM_OUT_DURATION_MS); - ObjectAnimator navBarAnimator = ObjectAnimator.ofFloat(this, mNavBarAlphaProperty, 1f); - navBarAnimator.setInterpolator(ALPHA_IN); - navBarAnimator.setDuration(ANIM_IN_DURATION_MS); - mTrackAnimator = new AnimatorSet(); - mTrackAnimator.playTogether(trackAnimator, navBarAnimator); - mTrackAnimator.addListener(mQuickScrubEndListener); - mTrackAnimator.start(); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepAction.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepAction.java deleted file mode 100644 index b18b79e0e6d6..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepAction.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2018 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.systemui.statusbar.phone; - -import static com.android.systemui.recents.OverviewProxyService.DEBUG_OVERVIEW_PROXY; -import static com.android.systemui.recents.OverviewProxyService.TAG_OPS; - -import android.annotation.NonNull; -import android.os.RemoteException; -import android.util.Log; -import android.view.MotionEvent; - -import com.android.systemui.recents.OverviewProxyService; - -/** - * QuickStep action to send to launcher to start overview - */ -public class QuickStepAction extends NavigationGestureAction { - private static final String TAG = "QuickStepAction"; - - public QuickStepAction(@NonNull NavigationBarView navigationBarView, - @NonNull OverviewProxyService service) { - super(navigationBarView, service); - } - - @Override - public boolean canRunWhenNotificationsShowing() { - return false; - } - - @Override - public boolean isEnabled() { - return mNavigationBarView.isQuickStepSwipeUpEnabled(); - } - - @Override - public void onGestureStart(MotionEvent event) { - try { - mProxySender.getProxy().onQuickStep(event); - if (DEBUG_OVERVIEW_PROXY) { - Log.d(TAG_OPS, "Quick Step Start"); - } - } catch (RemoteException e) { - Log.e(TAG, "Failed to send quick step started.", e); - } - mProxySender.notifyQuickStepStarted(); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java deleted file mode 100644 index 8053ec7e5838..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java +++ /dev/null @@ -1,731 +0,0 @@ -/* - * Copyright (C) 2018 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.systemui.statusbar.phone; - -import static android.view.WindowManagerPolicyConstants.NAV_BAR_BOTTOM; -import static android.view.WindowManagerPolicyConstants.NAV_BAR_INVALID; -import static android.view.WindowManagerPolicyConstants.NAV_BAR_LEFT; -import static android.view.WindowManagerPolicyConstants.NAV_BAR_RIGHT; - -import static com.android.systemui.recents.OverviewProxyService.DEBUG_OVERVIEW_PROXY; -import static com.android.systemui.recents.OverviewProxyService.TAG_OPS; -import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_BACK; -import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_DEAD_ZONE; -import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_HOME; -import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_NONE; -import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_OVERVIEW; -import static com.android.systemui.statusbar.phone.NavigationBarView.WINDOW_TARGET_BOTTOM; - -import android.annotation.Nullable; -import android.content.Context; -import android.content.res.Resources; -import android.graphics.Canvas; -import android.graphics.Matrix; -import android.graphics.Rect; -import android.hardware.input.InputManager; -import android.os.RemoteException; -import android.os.SystemClock; -import android.provider.Settings; -import android.util.Log; -import android.view.InputDevice; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewConfiguration; -import android.view.ViewPropertyAnimator; - -import com.android.systemui.Dependency; -import com.android.systemui.Interpolators; -import com.android.systemui.R; -import com.android.systemui.SysUiServiceProvider; -import com.android.systemui.plugins.statusbar.phone.NavGesture.GestureHelper; -import com.android.systemui.recents.OverviewProxyService; -import com.android.systemui.shared.recents.IOverviewProxy; -import com.android.systemui.shared.recents.utilities.Utilities; -import com.android.systemui.shared.system.InputChannelCompat.InputEventDispatcher; -import com.android.systemui.shared.system.NavigationBarCompat; -import com.android.systemui.shared.system.QuickStepContract; - -import java.io.PrintWriter; - -/** - * Class to detect gestures on the navigation bar and implement quick scrub. - * Note that the variables in this class horizontal and vertical represents horizontal always - * aligned with along the navigation bar. - */ -public class QuickStepController implements GestureHelper { - - private static final String TAG = "QuickStepController"; - - /** Experiment to swipe home button left to execute a back key press */ - private static final String HIDE_BACK_BUTTON_PROP = "quickstepcontroller_hideback"; - private static final String HIDE_HOME_BUTTON_PROP = "quickstepcontroller_hidehome"; - private static final String ENABLE_CLICK_THROUGH_NAV_PROP = "quickstepcontroller_clickthrough"; - private static final String GESTURE_REGION_THRESHOLD_SETTING = "gesture_region_threshold"; - private static final long BACK_BUTTON_FADE_IN_ALPHA = 150; - private static final long CLICK_THROUGH_TAP_DELAY = 70; - private static final long CLICK_THROUGH_TAP_RESET_DELAY = 100; - - /** When the home-swipe-back gesture is disallowed, make it harder to pull */ - private static final float HORIZONTAL_GESTURE_DAMPING = 0.3f; - private static final float VERTICAL_GESTURE_DAMPING = 0.15f; - private static final float HORIZONTAL_DISABLED_GESTURE_DAMPING = 0.16f; - private static final float VERTICAL_DISABLED_GESTURE_DAMPING = 0.06f; - - private static final int ACTION_SWIPE_UP_INDEX = 0; - private static final int ACTION_SWIPE_DOWN_INDEX = 1; - private static final int ACTION_SWIPE_LEFT_INDEX = 2; - private static final int ACTION_SWIPE_RIGHT_INDEX = 3; - private static final int ACTION_SWIPE_LEFT_FROM_EDGE_INDEX = 4; - private static final int ACTION_SWIPE_RIGHT_FROM_EDGE_INDEX = 5; - private static final int MAX_GESTURES = 6; - - private NavigationBarView mNavigationBarView; - - private boolean mAllowGestureDetection; - private boolean mNotificationsVisibleOnDown; - private int mTouchDownX; - private int mTouchDownY; - private boolean mDragHPositive; - private boolean mDragVPositive; - private boolean mIsRTL; - private int mNavBarPosition; - private float mDarkIntensity; - private ViewPropertyAnimator mDragBtnAnimator; - private ButtonDispatcher mHitTarget; - private boolean mIsInScreenPinning; - private boolean mGestureHorizontalDragsButton; - private boolean mGestureVerticalDragsButton; - private float mMaxDragLimit; - private float mMinDragLimit; - private float mDragDampeningFactor; - private boolean mClickThroughPressed; - private float mClickThroughPressX; - private float mClickThroughPressY; - private int mGestureRegionThreshold; - - private NavigationGestureAction mCurrentAction; - private NavigationGestureAction[] mGestureActions = new NavigationGestureAction[MAX_GESTURES]; - - private final Rect mLastLayoutRect = new Rect(); - private final OverviewProxyService mOverviewEventSender; - private final Context mContext; - private final StatusBar mStatusBar; - private final Matrix mTransformGlobalMatrix = new Matrix(); - private final Matrix mTransformLocalMatrix = new Matrix(); - - public QuickStepController(Context context) { - mContext = context; - mStatusBar = SysUiServiceProvider.getComponent(context, StatusBar.class); - mOverviewEventSender = Dependency.get(OverviewProxyService.class); - } - - private final Runnable mClickThroughSendTap = new Runnable() { - @Override - public void run() { - sendTap(mClickThroughPressX, mClickThroughPressY); - mNavigationBarView.postDelayed(mClickThroughResetTap, CLICK_THROUGH_TAP_RESET_DELAY); - } - }; - - private final Runnable mClickThroughResetTap = () -> { - mNavigationBarView.setWindowTouchable(true); - mClickThroughPressed = false; - }; - - public void setComponents(NavigationBarView navigationBarView) { - mNavigationBarView = navigationBarView; - - mNavigationBarView.getBackButton().setVisibility(shouldHideBackButton(mContext) - ? View.GONE - : View.VISIBLE); - } - - /** - * Set each gesture an action. After set the gestures triggered will run the actions attached. - * @param swipeUpAction action after swiping up - * @param swipeDownAction action after swiping down - * @param swipeLeftAction action after swiping left - * @param swipeRightAction action after swiping right - * @param swipeLeftFromEdgeAction action swiping left starting from the right side - * @param swipeRightFromEdgeAction action swiping right starting from the left side - */ - public void setGestureActions(@Nullable NavigationGestureAction swipeUpAction, - @Nullable NavigationGestureAction swipeDownAction, - @Nullable NavigationGestureAction swipeLeftAction, - @Nullable NavigationGestureAction swipeRightAction, - @Nullable NavigationGestureAction swipeLeftFromEdgeAction, - @Nullable NavigationGestureAction swipeRightFromEdgeAction) { - mGestureActions[ACTION_SWIPE_UP_INDEX] = swipeUpAction; - mGestureActions[ACTION_SWIPE_DOWN_INDEX] = swipeDownAction; - mGestureActions[ACTION_SWIPE_LEFT_INDEX] = swipeLeftAction; - mGestureActions[ACTION_SWIPE_RIGHT_INDEX] = swipeRightAction; - mGestureActions[ACTION_SWIPE_LEFT_FROM_EDGE_INDEX] = swipeLeftFromEdgeAction; - mGestureActions[ACTION_SWIPE_RIGHT_FROM_EDGE_INDEX] = swipeRightFromEdgeAction; - - // Set the current state to all actions - for (NavigationGestureAction action: mGestureActions) { - if (action != null) { - action.setBarState(true, mNavBarPosition, mDragHPositive, mDragVPositive); - action.onDarkIntensityChange(mDarkIntensity); - action.onLayout(true /* changed */, mLastLayoutRect.left, mLastLayoutRect.top, - mLastLayoutRect.right, mLastLayoutRect.bottom); - } - } - } - - /** - * @return true if we want to intercept touch events for quick scrub and prevent proxying the - * event to the overview service. - */ - @Override - public boolean onInterceptTouchEvent(MotionEvent event) { - return handleTouchEvent(event); - } - - /** - * @return true if we want to handle touch events for quick scrub or if down event (that will - * get consumed and ignored). No events will be proxied to the overview service. - */ - @Override - public boolean onTouchEvent(MotionEvent event) { - // The same down event was just sent on intercept and therefore can be ignored here - final boolean ignoreProxyDownEvent = event.getAction() == MotionEvent.ACTION_DOWN - && mOverviewEventSender.getProxy() != null - && mNavigationBarView.getWindowTarget() == WINDOW_TARGET_BOTTOM; - return ignoreProxyDownEvent || handleTouchEvent(event); - } - - private boolean handleTouchEvent(MotionEvent event) { - final boolean deadZoneConsumed = - mNavigationBarView.getDownHitTarget() == HIT_TARGET_DEAD_ZONE; - - // Requires proxy and an active gesture or able to perform any gesture to continue - if (mOverviewEventSender.getProxy() == null - || !mOverviewEventSender.shouldShowSwipeUpUI() - || (mCurrentAction == null && !canPerformAnyAction())) { - return deadZoneConsumed; - } - mNavigationBarView.requestUnbufferedDispatch(event); - - int action = event.getActionMasked(); - switch (action) { - case MotionEvent.ACTION_DOWN: { - int x = (int) event.getX(); - int y = (int) event.getY(); - mIsInScreenPinning = mNavigationBarView.inScreenPinning(); - - for (NavigationGestureAction gestureAction: mGestureActions) { - if (gestureAction != null) { - gestureAction.reset(); - } - } - - // Valid buttons to drag over - switch (mNavigationBarView.getDownHitTarget()) { - case HIT_TARGET_BACK: - mHitTarget = mNavigationBarView.getBackButton(); - break; - case HIT_TARGET_HOME: - mHitTarget = mNavigationBarView.getHomeButton(); - break; - case HIT_TARGET_OVERVIEW: - mHitTarget = mNavigationBarView.getRecentsButton(); - break; - default: - mHitTarget = null; - break; - } - if (mHitTarget != null) { - // Pre-emptively delay the touch feedback for the button that we just touched - mHitTarget.setDelayTouchFeedback(true); - } - mTouchDownX = x; - mTouchDownY = y; - mGestureHorizontalDragsButton = false; - mGestureVerticalDragsButton = false; - mTransformGlobalMatrix.set(Matrix.IDENTITY_MATRIX); - mTransformLocalMatrix.set(Matrix.IDENTITY_MATRIX); - mNavigationBarView.transformMatrixToGlobal(mTransformGlobalMatrix); - mNavigationBarView.transformMatrixToLocal(mTransformLocalMatrix); - mAllowGestureDetection = true; - mNotificationsVisibleOnDown = !mNavigationBarView.isNotificationsFullyCollapsed(); - mGestureRegionThreshold = QuickStepContract.getEdgeSensitivityWidth(mContext); - break; - } - case MotionEvent.ACTION_MOVE: { - if (!mAllowGestureDetection - || mNavigationBarView.getWindowTarget() != WINDOW_TARGET_BOTTOM) { - break; - } - int x = (int) event.getX(); - int y = (int) event.getY(); - int xDiff = Math.abs(x - mTouchDownX); - int yDiff = Math.abs(y - mTouchDownY); - - boolean exceededSwipeHorizontalTouchSlop, exceededSwipeVerticalTouchSlop, - exceededSwipeVerticalDragSlop; - int posH, touchDownH, posV, touchDownV; - - if (isNavBarVertical()) { - exceededSwipeHorizontalTouchSlop = - yDiff > NavigationBarCompat.getQuickScrubTouchSlopPx() && yDiff > xDiff; - exceededSwipeVerticalTouchSlop = - xDiff > NavigationBarCompat.getQuickStepTouchSlopPx() && xDiff > yDiff; - exceededSwipeVerticalDragSlop = - xDiff > NavigationBarCompat.getQuickStepDragSlopPx() && xDiff > yDiff; - posH = y; - touchDownH = mTouchDownY; - posV = x; - touchDownV = mTouchDownX; - } else { - exceededSwipeHorizontalTouchSlop = - xDiff > NavigationBarCompat.getQuickScrubTouchSlopPx() && xDiff > yDiff; - exceededSwipeVerticalTouchSlop = - yDiff > NavigationBarCompat.getQuickStepTouchSlopPx() && yDiff > xDiff; - exceededSwipeVerticalDragSlop = - yDiff > NavigationBarCompat.getQuickStepDragSlopPx() && yDiff > xDiff; - posH = x; - touchDownH = mTouchDownX; - posV = y; - touchDownV = mTouchDownY; - } - - if (mCurrentAction != null) { - // Gesture started, provide positions to the current action - mCurrentAction.onGestureMove(x, y); - } else { - // Detect gesture and try to execute an action, only one can run at a time - if (exceededSwipeVerticalTouchSlop || exceededSwipeVerticalDragSlop) { - if (mDragVPositive ? (posV < touchDownV) : (posV > touchDownV)) { - // Swipe up gesture must use the larger slop - if (exceededSwipeVerticalTouchSlop) { - // Swiping up gesture - tryToStartGesture(mGestureActions[ACTION_SWIPE_UP_INDEX], - false /* alignedWithNavBar */, event); - } - } else { - // Swiping down gesture - tryToStartGesture(mGestureActions[ACTION_SWIPE_DOWN_INDEX], - false /* alignedWithNavBar */, event); - } - } else if (exceededSwipeHorizontalTouchSlop) { - if (mDragHPositive ? (posH < touchDownH) : (posH > touchDownH)) { - // Swiping left (rtl) gesture - tryToStartGesture(mGestureActions[ACTION_SWIPE_LEFT_INDEX], - true /* alignedWithNavBar */, event); - } else { - // Swiping right (ltr) gesture - tryToStartGesture(mGestureActions[ACTION_SWIPE_RIGHT_INDEX], - true /* alignedWithNavBar */, event); - } - } - } - - handleDragHitTarget(mGestureHorizontalDragsButton ? posH : posV, - mGestureHorizontalDragsButton ? touchDownH : touchDownV); - break; - } - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: - if (mCurrentAction != null) { - mCurrentAction.endGesture(); - } else if (action == MotionEvent.ACTION_UP) { - if (canTriggerEdgeSwipe(event)) { - int index = mNavigationBarView.getWindowTarget() == NAV_BAR_LEFT - ? ACTION_SWIPE_RIGHT_FROM_EDGE_INDEX - : ACTION_SWIPE_LEFT_FROM_EDGE_INDEX; - tryToStartGesture(mGestureActions[index], false /* alignedWithNavBar */, - event); - if (mCurrentAction != null) { - mCurrentAction.endGesture(); - } - } else if (QuickStepContract.isNavBarClickThrough(mContext) - && !mClickThroughPressed) { - // Enable click through functionality where no gesture has been detected and - // not passed the drag slop so inject a touch event at the same location - // after making the navigation bar window untouchable. After a some time, - // the navigation bar will be able to take input events again - float diffX = Math.abs(event.getX() - mTouchDownX); - float diffY = Math.abs(event.getY() - mTouchDownY); - - if ((diffX <= NavigationBarCompat.getQuickStepDragSlopPx() - && diffY <= NavigationBarCompat.getQuickStepDragSlopPx())) { - mNavigationBarView.setWindowTouchable(false); - mClickThroughPressX = event.getRawX(); - mClickThroughPressY = event.getRawY(); - mClickThroughPressed = true; - mNavigationBarView.postDelayed(mClickThroughSendTap, - CLICK_THROUGH_TAP_DELAY); - } - } - } - - // Return the hit target back to its original position - if (mHitTarget != null) { - final View button = mHitTarget.getCurrentView(); - if (mGestureHorizontalDragsButton || mGestureVerticalDragsButton) { - mDragBtnAnimator = button.animate().setDuration(BACK_BUTTON_FADE_IN_ALPHA) - .setInterpolator(Interpolators.FAST_OUT_SLOW_IN); - if (mGestureVerticalDragsButton ^ isNavBarVertical()) { - mDragBtnAnimator.translationY(0); - } else { - mDragBtnAnimator.translationX(0); - } - mDragBtnAnimator.start(); - } - } - break; - } - - if (shouldProxyEvents(action)) { - proxyMotionEvents(event); - } - - // Clear action when gesture and event proxy finishes - if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { - mCurrentAction = null; - } - return mCurrentAction != null || deadZoneConsumed; - } - - private void handleDragHitTarget(int position, int touchDown) { - // Drag the hit target if gesture action requires it - if (mHitTarget != null && (mGestureVerticalDragsButton || mGestureHorizontalDragsButton)) { - final View button = mHitTarget.getCurrentView(); - if (mDragBtnAnimator != null) { - mDragBtnAnimator.cancel(); - mDragBtnAnimator = null; - } - - // Clamp drag to the bounding box of the navigation bar - float diff = (position - touchDown) * mDragDampeningFactor; - diff = Utilities.clamp(diff, mMinDragLimit, mMaxDragLimit); - if (mGestureVerticalDragsButton ^ isNavBarVertical()) { - button.setTranslationY(diff); - } else { - button.setTranslationX(diff); - } - } - } - - private boolean shouldProxyEvents(int action) { - // Do not send events for side navigation bar panels - if (mNavigationBarView.getWindowTarget() != WINDOW_TARGET_BOTTOM) { - return false; - } - final boolean actionValid = (mCurrentAction == null - || !mCurrentAction.disableProxyEvents()); - if (actionValid && !mIsInScreenPinning) { - // Allow down, cancel and up events, move and other events are passed if notifications - // are not showing and disabled gestures (such as long press) are not executed - switch (action) { - case MotionEvent.ACTION_DOWN: - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: - return true; - default: - return !mNotificationsVisibleOnDown && mAllowGestureDetection; - } - } - return false; - } - - @Override - public void onDraw(Canvas canvas) { - if (mCurrentAction != null) { - mCurrentAction.onDraw(canvas); - } - } - - @Override - public void onLayout(boolean changed, int left, int top, int right, int bottom) { - for (NavigationGestureAction action: mGestureActions) { - if (action != null) { - action.onLayout(changed, left, top, right, bottom); - } - } - mLastLayoutRect.set(left, top, right, bottom); - } - - @Override - public void onDarkIntensityChange(float intensity) { - final float oldIntensity = mDarkIntensity; - mDarkIntensity = intensity; - - // When in quick scrub, invalidate gradient if changing intensity from black to white and - // vice-versa - if (mCurrentAction != null && mNavigationBarView.isQuickScrubEnabled() - && Math.round(intensity) != Math.round(oldIntensity)) { - mCurrentAction.onDarkIntensityChange(mDarkIntensity); - } - mNavigationBarView.invalidate(); - } - - @Override - public void setBarState(boolean isRTL, int navBarPosition) { - final boolean changed = (mIsRTL != isRTL) || (mNavBarPosition != navBarPosition); - mIsRTL = isRTL; - mNavBarPosition = navBarPosition; - - // Determine the drag directions depending on location of nav bar - switch (navBarPosition) { - case NAV_BAR_LEFT: - mDragHPositive = !isRTL; - mDragVPositive = false; - break; - case NAV_BAR_RIGHT: - mDragHPositive = isRTL; - mDragVPositive = true; - break; - case NAV_BAR_BOTTOM: - mDragHPositive = !isRTL; - mDragVPositive = true; - break; - case NAV_BAR_INVALID: - Log.e(TAG, "Invalid nav bar position"); - break; - } - - for (NavigationGestureAction action: mGestureActions) { - if (action != null) { - action.setBarState(changed, mNavBarPosition, mDragHPositive, mDragVPositive); - } - } - } - - @Override - public void onNavigationButtonLongPress(View v) { - mAllowGestureDetection = false; - } - - @Override - public void dump(PrintWriter pw) { - pw.println("QuickStepController {"); - pw.print(" "); pw.println("mAllowGestureDetection=" + mAllowGestureDetection); - pw.print(" "); pw.println("mNotificationsVisibleOnDown=" + mNotificationsVisibleOnDown); - pw.print(" "); pw.println("mNavBarPosition=" + mNavBarPosition); - pw.print(" "); pw.println("mIsRTL=" + mIsRTL); - pw.print(" "); pw.println("mIsInScreenPinning=" + mIsInScreenPinning); - pw.println("}"); - } - - public NavigationGestureAction getCurrentAction() { - return mCurrentAction; - } - - private void tryToStartGesture(NavigationGestureAction action, boolean alignedWithNavBar, - MotionEvent event) { - if (action == null) { - return; - } - if (mIsInScreenPinning) { - mNavigationBarView.showPinningEscapeToast(); - mAllowGestureDetection = false; - return; - } - - // Start new action from gesture if is able to start and depending on notifications - // visibility and starting touch down target. If the action is enabled, then also check if - // can perform the action so that if action requires the button to be dragged, then the - // gesture will have a large dampening factor and prevent action from running. - final boolean validHitTarget = action.requiresTouchDownHitTarget() == HIT_TARGET_NONE - || action.requiresTouchDownHitTarget() == mNavigationBarView.getDownHitTarget(); - if (mCurrentAction == null && validHitTarget && action.isEnabled() - && (!mNotificationsVisibleOnDown || action.canRunWhenNotificationsShowing())) { - if (action.canPerformAction()) { - mCurrentAction = action; - event.transform(mTransformGlobalMatrix); - action.startGesture(event); - event.transform(mTransformLocalMatrix); - - // Calculate the bounding limits of drag to avoid dragging off nav bar's window - if (action.allowHitTargetToMoveOverDrag() && mHitTarget != null) { - final int[] buttonCenter = new int[2]; - View button = mHitTarget.getCurrentView(); - button.getLocationInWindow(buttonCenter); - buttonCenter[0] += button.getWidth() / 2; - buttonCenter[1] += button.getHeight() / 2; - final int x = isNavBarVertical() ? buttonCenter[1] : buttonCenter[0]; - final int y = isNavBarVertical() ? buttonCenter[0] : buttonCenter[1]; - final int iconHalfSize = mContext.getResources() - .getDimensionPixelSize(R.dimen.navigation_icon_size) / 2; - - if (alignedWithNavBar) { - mMinDragLimit = iconHalfSize - x; - mMaxDragLimit = -x - iconHalfSize + (isNavBarVertical() - ? mNavigationBarView.getHeight() : mNavigationBarView.getWidth()); - } else { - mMinDragLimit = iconHalfSize - y; - mMaxDragLimit = -y - iconHalfSize + (isNavBarVertical() - ? mNavigationBarView.getWidth() : mNavigationBarView.getHeight()); - } - } - } - - // Handle direction of the hit target drag from the axis that started the gesture - // Also calculate the dampening factor, weaker dampening if there is an active action - if (action.allowHitTargetToMoveOverDrag()) { - if (alignedWithNavBar) { - mGestureHorizontalDragsButton = true; - mGestureVerticalDragsButton = false; - mDragDampeningFactor = action.isActive() - ? HORIZONTAL_GESTURE_DAMPING : HORIZONTAL_DISABLED_GESTURE_DAMPING; - } else { - mGestureVerticalDragsButton = true; - mGestureHorizontalDragsButton = false; - mDragDampeningFactor = action.isActive() - ? VERTICAL_GESTURE_DAMPING : VERTICAL_DISABLED_GESTURE_DAMPING; - } - } - - if (mHitTarget != null) { - mHitTarget.abortCurrentGesture(); - } - } - } - - /** - * To trigger an edge swipe, the user must start from the left or right edges of certain height - * from the bottom then past the drag slope towards the center of the screen, followed by either - * a timed trigger for fast swipes or distance if held on the screen longer. - * For time, user must swipe up quickly before the Tap Timeout (typically 100ms) and for - * distance, the user can drag back to cancel if the touch up has not past the threshold. - * @param event Touch up event - * @return whether or not edge swipe gesture occurs - */ - private boolean canTriggerEdgeSwipe(MotionEvent event) { - if (mNavigationBarView.getWindowTarget() == WINDOW_TARGET_BOTTOM) { - return false; - } - int x = (int) event.getX(); - int y = (int) event.getY(); - int xDiff = Math.abs(x - mTouchDownX); - int yDiff = Math.abs(y - mTouchDownY); - final boolean exceededSwipeTouchSlop = xDiff > NavigationBarCompat.getQuickStepDragSlopPx() - && xDiff > yDiff; - if (exceededSwipeTouchSlop) { - long timeDiff = event.getEventTime() - event.getDownTime(); - return xDiff > mGestureRegionThreshold || timeDiff < ViewConfiguration.getTapTimeout(); - } - return false; - } - - private boolean canPerformAnyAction() { - for (NavigationGestureAction action: mGestureActions) { - if (action != null && action.isEnabled()) { - return true; - } - } - return false; - } - - private void sendTap(float x, float y) { - long now = SystemClock.uptimeMillis(); - injectMotionEvent(InputDevice.SOURCE_TOUCHSCREEN, MotionEvent.ACTION_DOWN, now, x, y, 1.0f); - injectMotionEvent(InputDevice.SOURCE_TOUCHSCREEN, MotionEvent.ACTION_UP, now, x, y, 0.0f); - } - - private int getInputDeviceId(int inputSource) { - int[] devIds = InputDevice.getDeviceIds(); - for (int devId : devIds) { - InputDevice inputDev = InputDevice.getDevice(devId); - if (inputDev.supportsSource(inputSource)) { - return devId; - } - } - return 0; - } - - private void injectMotionEvent(int inputSource, int action, long when, float x, float y, - float pressure) { - final float defaultSize = 1.0f; - final int defaultMetaState = 0; - final float defaultPrecisionX = 1.0f; - final float defaultPrecisionY = 1.0f; - final int defaultEdgeFlags = 0; - MotionEvent event = MotionEvent.obtain(when, when, action, x, y, pressure, defaultSize, - defaultMetaState, defaultPrecisionX, defaultPrecisionY, - getInputDeviceId(inputSource), defaultEdgeFlags); - event.setSource(inputSource); - InputManager.getInstance().injectInputEvent(event, - InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); - } - - private boolean proxyMotionEvents(MotionEvent event) { - event.transform(mTransformGlobalMatrix); - InputEventDispatcher dispatcher = mOverviewEventSender.getInputEventDispatcher(); - if (dispatcher != null) { - dispatcher.dispatch(event); - } - - final IOverviewProxy overviewProxy = mOverviewEventSender.getProxy(); - try { - if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { - overviewProxy.onPreMotionEvent(mNavigationBarView.getDownHitTarget()); - } - overviewProxy.onMotionEvent(event); - if (DEBUG_OVERVIEW_PROXY) { - Log.d(TAG_OPS, "Send MotionEvent: " + event.toString()); - } - return true; - } catch (RemoteException e) { - Log.e(TAG, "Callback failed", e); - } finally { - event.transform(mTransformLocalMatrix); - } - return false; - } - - protected boolean isNavBarVertical() { - return mNavBarPosition == NAV_BAR_LEFT || mNavBarPosition == NAV_BAR_RIGHT; - } - - private static int convertDpToPixel(float dp) { - return (int) (dp * Resources.getSystem().getDisplayMetrics().density); - } - - // TODO(112934365): Clean up following methods when cleaning up nav bar experiments - - static boolean getBoolGlobalSetting(Context context, String key) { - return Settings.Global.getInt(context.getContentResolver(), key, 0) != 0; - } - - static int getIntGlobalSetting(Context context, String key, int defaultValue) { - return Settings.Global.getInt(context.getContentResolver(), key, defaultValue); - } - - /** - * @return whether to hide the back button. - */ - public static boolean shouldHideBackButton(Context context) { - return QuickStepContract.isGesturalMode(context); - } - - /** - * @return whether to hide the home button. - */ - public static boolean shouldHideHomeButton(Context context) { - return QuickStepContract.isGesturalMode(context); - } - - /** - * @return whether to show the home handle. - */ - public static boolean showHomeHandle(Context context) { - return QuickStepContract.isGesturalMode(context); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSwitchAction.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSwitchAction.java deleted file mode 100644 index 974de4b87ebc..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSwitchAction.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (C) 2018 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.systemui.statusbar.phone; - -import static com.android.systemui.recents.OverviewProxyService.DEBUG_OVERVIEW_PROXY; -import static com.android.systemui.recents.OverviewProxyService.TAG_OPS; - -import android.annotation.NonNull; -import android.graphics.Rect; -import android.os.RemoteException; -import android.util.Log; -import android.view.MotionEvent; - -import com.android.systemui.recents.OverviewProxyService; -import com.android.systemui.shared.recents.utilities.Utilities; - -/** - * QuickSwitch action to send to launcher - */ -public class QuickSwitchAction extends NavigationGestureAction { - private static final String TAG = "QuickSwitchAction"; - - protected final Rect mDragOverRect = new Rect(); - - public QuickSwitchAction(@NonNull NavigationBarView navigationBar, - @NonNull OverviewProxyService service) { - super(navigationBar, service); - } - - @Override - public void setBarState(boolean changed, int navBarPos, boolean dragHorPositive, - boolean dragVerPositive) { - super.setBarState(changed, navBarPos, dragHorPositive, dragVerPositive); - if (changed && isActive()) { - // End quickscrub if the state changes mid-transition - endQuickGesture(false /* animate */); - } - } - - @Override - public boolean isEnabled() { - return mNavigationBarView.isQuickScrubEnabled(); - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - super.onLayout(changed, left, top, right, bottom); - mDragOverRect.set(top, left, right, bottom); - } - - @Override - public boolean disableProxyEvents() { - return true; - } - - @Override - protected void onGestureStart(MotionEvent event) { - startQuickGesture(event); - } - - @Override - public void onGestureMove(int x, int y) { - int dragLength, offset; - if (isNavBarVertical()) { - dragLength = mDragOverRect.height(); - offset = y - mDragOverRect.top; - } else { - offset = x - mDragOverRect.left; - dragLength = mDragOverRect.width(); - } - if (!mDragHorizontalPositive || !mDragVerticalPositive) { - offset -= dragLength; - } - float scrubFraction = Utilities.clamp(Math.abs(offset) * 1f / dragLength, 0, 1); - try { - mProxySender.getProxy().onQuickScrubProgress(scrubFraction); - if (DEBUG_OVERVIEW_PROXY) { - Log.d(TAG_OPS, "Quick Switch Progress:" + scrubFraction); - } - } catch (RemoteException e) { - Log.e(TAG, "Failed to send progress of quick switch.", e); - } - } - - @Override - protected void onGestureEnd() { - endQuickGesture(true /* animate */); - } - - protected void startQuickGesture(MotionEvent event) { - // Disable slippery for quick scrub to not cancel outside the nav bar - mNavigationBarView.updateSlippery(); - - try { - mProxySender.getProxy().onQuickScrubStart(); - if (DEBUG_OVERVIEW_PROXY) { - Log.d(TAG_OPS, "Quick Scrub Start"); - } - } catch (RemoteException e) { - Log.e(TAG, "Failed to send start of quick scrub.", e); - } - mProxySender.notifyQuickScrubStarted(); - } - - protected void endQuickGesture(boolean animate) { - try { - mProxySender.getProxy().onQuickScrubEnd(); - if (DEBUG_OVERVIEW_PROXY) { - Log.d(TAG_OPS, "Quick Scrub End"); - } - } catch (RemoteException e) { - Log.e(TAG, "Failed to send end of quick scrub.", e); - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index ed66b4b86748..e5defae1f159 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -2484,6 +2484,14 @@ public class StatusBar extends SystemUI implements DemoMode, options.setRotationAnimationHint( WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS); } + if (intent.getAction() == Settings.Panel.ACTION_VOLUME) { + // Settings Panel is implemented as activity(not a dialog), so + // underlying app is paused and may enter picture-in-picture mode + // as a result. + // So we need to disable picture-in-picture mode here + // if it is volume panel. + options.setDisallowEnterPictureInPictureWhileLaunching(true); + } try { result = ActivityTaskManager.getService().startActivityAsUser( null, mContext.getBasePackageName(), diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockMethodCache.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockMethodCache.java index bdd76c8ea05a..44c82c80d65b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockMethodCache.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockMethodCache.java @@ -43,7 +43,6 @@ public class UnlockMethodCache { /** Whether the unlock method is currently insecure (insecure method or trusted environment) */ private boolean mCanSkipBouncer; private boolean mTrustManaged; - private boolean mFaceUnlockRunning; private boolean mTrusted; private UnlockMethodCache(Context ctx) { @@ -93,16 +92,13 @@ public class UnlockMethodCache { boolean canSkipBouncer = !secure || mKeyguardUpdateMonitor.getUserCanSkipBouncer(user); boolean trustManaged = mKeyguardUpdateMonitor.getUserTrustIsManaged(user); boolean trusted = mKeyguardUpdateMonitor.getUserHasTrust(user); - boolean faceUnlockRunning = mKeyguardUpdateMonitor.isFaceUnlockRunning(user) - && trustManaged; boolean changed = secure != mSecure || canSkipBouncer != mCanSkipBouncer || - trustManaged != mTrustManaged || faceUnlockRunning != mFaceUnlockRunning; + trustManaged != mTrustManaged; if (changed || updateAlways) { mSecure = secure; mCanSkipBouncer = canSkipBouncer; mTrusted = trusted; mTrustManaged = trustManaged; - mFaceUnlockRunning = faceUnlockRunning; notifyListeners(); } Trace.endSection(); @@ -171,10 +167,6 @@ public class UnlockMethodCache { return mTrustManaged; } - public boolean isFaceUnlockRunning() { - return mFaceUnlockRunning; - } - public static interface OnUnlockMethodChangedListener { void onUnlockMethodStateChanged(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastController.java index 97be6ed10bf9..98cde2a049da 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastController.java @@ -19,12 +19,12 @@ package com.android.systemui.statusbar.policy; import com.android.systemui.Dumpable; import com.android.systemui.statusbar.policy.CastController.Callback; -import java.util.Set; +import java.util.List; public interface CastController extends CallbackController<Callback>, Dumpable { void setDiscovering(boolean request); void setCurrentUserId(int currentUserId); - Set<CastDevice> getCastDevices(); + List<CastDevice> getCastDevices(); void startCasting(CastDevice device); void stopCasting(CastDevice device); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java index c7d337ad46d6..505dd169839e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java @@ -29,7 +29,6 @@ import android.media.projection.MediaProjectionManager; import android.os.Handler; import android.text.TextUtils; import android.util.ArrayMap; -import android.util.ArraySet; import android.util.Log; import androidx.annotation.VisibleForTesting; @@ -40,8 +39,8 @@ import com.android.systemui.R; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.List; import java.util.Objects; -import java.util.Set; import java.util.UUID; import javax.inject.Inject; @@ -150,20 +149,8 @@ public class CastControllerImpl implements CastController { } @Override - public Set<CastDevice> getCastDevices() { - final ArraySet<CastDevice> devices = new ArraySet<CastDevice>(); - synchronized (mProjectionLock) { - if (mProjection != null) { - final CastDevice device = new CastDevice(); - device.id = mProjection.getPackageName(); - device.name = getAppName(mProjection.getPackageName()); - device.description = mContext.getString(R.string.quick_settings_casting); - device.state = CastDevice.STATE_CONNECTED; - device.tag = mProjection; - devices.add(device); - return devices; - } - } + public List<CastDevice> getCastDevices() { + final ArrayList<CastDevice> devices = new ArrayList<>(); synchronized(mRoutes) { for (RouteInfo route : mRoutes.values()) { final CastDevice device = new CastDevice(); @@ -172,13 +159,33 @@ public class CastControllerImpl implements CastController { device.name = name != null ? name.toString() : null; final CharSequence description = route.getDescription(); device.description = description != null ? description.toString() : null; - device.state = route.isConnecting() ? CastDevice.STATE_CONNECTING - : route.isSelected() ? CastDevice.STATE_CONNECTED - : CastDevice.STATE_DISCONNECTED; + + int statusCode = route.getStatusCode(); + if (statusCode == RouteInfo.STATUS_CONNECTING) { + device.state = CastDevice.STATE_CONNECTING; + } else if (route.isSelected() || statusCode == RouteInfo.STATUS_CONNECTED) { + device.state = CastDevice.STATE_CONNECTED; + } else { + device.state = CastDevice.STATE_DISCONNECTED; + } + device.tag = route; devices.add(device); } } + + synchronized (mProjectionLock) { + if (mProjection != null) { + final CastDevice device = new CastDevice(); + device.id = mProjection.getPackageName(); + device.name = getAppName(mProjection.getPackageName()); + device.description = mContext.getString(R.string.quick_settings_casting); + device.state = CastDevice.STATE_CONNECTED; + device.tag = mProjection; + devices.add(device); + } + } + return devices; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java index 1e059835f086..06fc745b0832 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java @@ -308,6 +308,10 @@ public class KeyButtonView extends ImageView implements ButtonInterface { // TODO(b/122195391): Added logs to make sure sysui is sending back button events if (mCode == KeyEvent.KEYCODE_BACK && flags != KeyEvent.FLAG_LONG_PRESS) { Log.i(TAG, "Back button event: " + KeyEvent.actionToString(action)); + if (action == MotionEvent.ACTION_UP) { + mOverviewProxyService.notifyBackAction((flags & KeyEvent.FLAG_CANCELED) == 0, + -1, -1, true /* isButton */, false /* gestureSwipeLeft */); + } } final int repeatCount = (flags & KeyEvent.FLAG_LONG_PRESS) != 0 ? 1 : 0; final KeyEvent ev = new KeyEvent(mDownTime, when, action, mCode, repeatCount, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java index 1e090630efdb..c1950a21d10f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java @@ -146,7 +146,11 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene Intent fillInIntent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND); RemoteInput.addResultsToIntent(mRemoteInputs, fillInIntent, results); - RemoteInput.setResultsSource(fillInIntent, RemoteInput.SOURCE_FREE_FORM_INPUT); + if (mEntry.editedSuggestionInfo == null) { + RemoteInput.setResultsSource(fillInIntent, RemoteInput.SOURCE_FREE_FORM_INPUT); + } else { + RemoteInput.setResultsSource(fillInIntent, RemoteInput.SOURCE_CHOICE); + } mEditText.setEnabled(false); mSendButton.setVisibility(INVISIBLE); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java index 9343bf12cc7e..f726321db963 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java @@ -403,7 +403,7 @@ public class UserSwitcherController implements Dumpable { final int N = mUsers.size(); for (int i = 0; i < N; ++i) { UserRecord record = mUsers.get(i); - if (record.info != null && record.info.supportsSwitchTo()) { + if (record.info != null && record.info.supportsSwitchToByUser()) { count++; } } diff --git a/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java index 7705e4ecfaea..376c328af72d 100644 --- a/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java +++ b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java @@ -24,6 +24,7 @@ import android.view.LayoutInflater; import android.view.View; import com.android.keyguard.KeyguardClockSwitch; +import com.android.keyguard.KeyguardSliceView; import com.android.systemui.SystemUIFactory; import com.android.systemui.qs.QSCarrierGroup; import com.android.systemui.qs.QSFooterImpl; @@ -136,6 +137,11 @@ public class InjectionInflationController { * Creates the KeyguardClockSwitch. */ KeyguardClockSwitch createKeyguardClockSwitch(); + + /** + * Creates the KeyguardSliceView. + */ + KeyguardSliceView createKeyguardSliceView(); } /** diff --git a/packages/SystemUI/src/com/android/systemui/volume/CaptionsToggleImageButton.java b/packages/SystemUI/src/com/android/systemui/volume/CaptionsToggleImageButton.java index 8ec66e4bcc30..45fc7563a5c3 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/CaptionsToggleImageButton.java +++ b/packages/SystemUI/src/com/android/systemui/volume/CaptionsToggleImageButton.java @@ -17,7 +17,10 @@ package com.android.systemui.volume; import android.content.Context; +import android.os.Handler; import android.util.AttributeSet; +import android.view.GestureDetector; +import android.view.MotionEvent; import com.android.keyguard.AlphaOptimizedImageButton; import com.android.systemui.R; @@ -27,14 +30,34 @@ public class CaptionsToggleImageButton extends AlphaOptimizedImageButton { private static final int[] OPTED_OUT_STATE = new int[] { R.attr.optedOut }; + private ConfirmedTapListener mConfirmedTapListener; private boolean mComponentEnabled = false; private boolean mOptedOut = false; + private GestureDetector mGestureDetector; + private GestureDetector.SimpleOnGestureListener mGestureListener = + new GestureDetector.SimpleOnGestureListener() { + @Override + public boolean onSingleTapConfirmed(MotionEvent e) { + if (mConfirmedTapListener != null) { + mConfirmedTapListener.onConfirmedTap(); + return true; + } + return false; + } + }; + public CaptionsToggleImageButton(Context context, AttributeSet attrs) { super(context, attrs); } @Override + public boolean onTouchEvent(MotionEvent event) { + if (mGestureDetector != null) mGestureDetector.onTouchEvent(event); + return super.onTouchEvent(event); + } + + @Override public int[] onCreateDrawableState(int extraSpace) { int[] state = super.onCreateDrawableState(extraSpace + 1); if (mOptedOut) { @@ -64,4 +87,17 @@ public class CaptionsToggleImageButton extends AlphaOptimizedImageButton { boolean getOptedOut() { return this.mOptedOut; } + + void setOnConfirmedTapListener(ConfirmedTapListener listener, Handler handler) { + mConfirmedTapListener = listener; + + if (mGestureDetector == null) { + this.mGestureDetector = new GestureDetector(getContext(), mGestureListener, handler); + } + } + + /** Listener for confirmed taps rather than normal on click listener. */ + interface ConfirmedTapListener { + void onConfirmedTap(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index 35a245000628..2094b36d8294 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -419,8 +419,9 @@ public class VolumeDialogImpl implements VolumeDialog, row.header.setFilters(new InputFilter[] {new InputFilter.LengthFilter(13)}); } row.dndIcon = row.view.findViewById(R.id.dnd_icon); - row.slider = row.view.findViewById(R.id.volume_row_slider); + row.slider = row.view.findViewById(R.id.volume_row_slider); row.slider.setOnSeekBarChangeListener(new VolumeSeekBarChangeListener(row)); + row.anim = null; row.icon = row.view.findViewById(R.id.volume_row_icon); @@ -515,11 +516,11 @@ public class VolumeDialogImpl implements VolumeDialog, private void initODICaptionsH() { if (mODICaptionsIcon != null) { - mODICaptionsIcon.setOnClickListener(v -> { + mODICaptionsIcon.setOnConfirmedTapListener(() -> { onCaptionIconClicked(); Events.writeEvent(mContext, Events.EVENT_ODI_CAPTIONS_CLICK); dismissH(DISMISS_REASON_ODI_CAPTIONS_CLICKED); - }); + }, mHandler); } mController.getCaptionsComponentState(false); @@ -1393,6 +1394,7 @@ public class VolumeDialogImpl implements VolumeDialog, if (mRow.ss.level != userLevel || mRow.ss.muted && userLevel > 0) { mRow.userAttempt = SystemClock.uptimeMillis(); if (mRow.requestedLevel != userLevel) { + mController.setActiveStream(mRow.stream); mController.setStreamVolume(mRow.stream, userLevel); mRow.requestedLevel = userLevel; Events.writeEvent(mContext, Events.EVENT_TOUCH_LEVEL_CHANGED, mRow.stream, diff --git a/packages/SystemUI/tests/Android.mk b/packages/SystemUI/tests/Android.mk index 83ec33c69629..5412cde0df55 100644 --- a/packages/SystemUI/tests/Android.mk +++ b/packages/SystemUI/tests/Android.mk @@ -71,8 +71,8 @@ local_space := $(local_empty) $(local_empty) # This appends a * to all classes and replace the space separators with commas. jacoco_exclude := $(subst $(space),$(comma),$(patsubst %,%*,$(local_classes))) -LOCAL_JACK_COVERAGE_INCLUDE_FILTER := com.android.systemui.* -LOCAL_JACK_COVERAGE_EXCLUDE_FILTER := com.android.systemui.tests.*,$(jacoco_exclude) +LOCAL_JACK_COVERAGE_INCLUDE_FILTER := com.android.systemui.*,com.android.keyguard.* +LOCAL_JACK_COVERAGE_EXCLUDE_FILTER := $(jacoco_exclude) ifeq ($(EXCLUDE_SYSTEMUI_TESTS),) include $(BUILD_PACKAGE) diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java index 632b0c05e2c2..f01c0b421ef7 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java @@ -211,6 +211,19 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { } @Test + public void onPluginDisconnected_onDestroyView() { + // GIVEN a plugin is connected + ClockPlugin clockPlugin = mock(ClockPlugin.class); + when(clockPlugin.getView()).thenReturn(new TextClock(getContext())); + ClockManager.ClockChangedListener listener = mKeyguardClockSwitch.getClockChangedListener(); + listener.onClockChanged(clockPlugin); + // WHEN the plugin is disconnected + listener.onClockChanged(null); + // THEN onDestroyView is called on the plugin + verify(clockPlugin).onDestroyView(); + } + + @Test public void setTextColor_defaultClockSetTextColor() { mKeyguardClockSwitch.setTextColor(Color.YELLOW); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java index 190ce7550511..b3accbc3b4bb 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java @@ -27,8 +27,10 @@ import androidx.slice.SliceProvider; import androidx.slice.SliceSpecs; import androidx.slice.builders.ListBuilder; +import com.android.systemui.SystemUIFactory; import com.android.systemui.SysuiTestCase; import com.android.systemui.keyguard.KeyguardSliceProvider; +import com.android.systemui.util.InjectionInflationController; import org.junit.Assert; import org.junit.Before; @@ -49,7 +51,11 @@ public class KeyguardSliceViewTest extends SysuiTestCase { @Before public void setUp() throws Exception { com.android.systemui.util.Assert.sMainLooper = TestableLooper.get(this).getLooper(); - mKeyguardSliceView = (KeyguardSliceView) LayoutInflater.from(getContext()) + InjectionInflationController inflationController = new InjectionInflationController( + SystemUIFactory.getInstance().getRootComponent()); + LayoutInflater layoutInflater = inflationController + .injectable(LayoutInflater.from(getContext())); + mKeyguardSliceView = (KeyguardSliceView) layoutInflater .inflate(R.layout.keyguard_status_area, null); mSliceUri = Uri.parse(KeyguardSliceProvider.KEYGUARD_SLICE_URI); SliceProvider.setSpecs(new HashSet<>(Collections.singletonList(SliceSpecs.LIST))); diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java index 9fa85d307d2a..5e16721fe548 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java @@ -18,9 +18,12 @@ package com.android.systemui.bubbles; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; @@ -42,7 +45,6 @@ import android.widget.FrameLayout; import androidx.test.filters.SmallTest; -import com.android.internal.statusbar.NotificationVisibility; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.statusbar.NotificationTestHelper; @@ -96,9 +98,9 @@ public class BubbleControllerTest extends SysuiTestCase { private NotificationTestHelper mNotificationTestHelper; private ExpandableNotificationRow mRow; private ExpandableNotificationRow mRow2; - private ExpandableNotificationRow mNoChannelRow; private ExpandableNotificationRow mAutoExpandRow; private ExpandableNotificationRow mSuppressNotifRow; + private ExpandableNotificationRow mNonBubbleNotifRow; @Mock private NotificationData mNotificationData; @@ -107,9 +109,6 @@ public class BubbleControllerTest extends SysuiTestCase { @Mock private BubbleController.BubbleExpandListener mBubbleExpandListener; @Mock - NotificationVisibility mNotificationVisibility; - - @Mock private PendingIntent mDeleteIntent; private BubbleData mBubbleData; @@ -129,7 +128,7 @@ public class BubbleControllerTest extends SysuiTestCase { mNotificationTestHelper = new NotificationTestHelper(mContext); mRow = mNotificationTestHelper.createBubble(mDeleteIntent); mRow2 = mNotificationTestHelper.createBubble(mDeleteIntent); - mNoChannelRow = mNotificationTestHelper.createBubble(mDeleteIntent); + mNonBubbleNotifRow = mNotificationTestHelper.createRow(); // Some bubbles want to auto expand Notification.BubbleMetadata autoExpandMetadata = @@ -146,7 +145,6 @@ public class BubbleControllerTest extends SysuiTestCase { // Return non-null notification data from the NEM when(mNotificationEntryManager.getNotificationData()).thenReturn(mNotificationData); when(mNotificationData.getChannel(mRow.getEntry().key)).thenReturn(mRow.getEntry().channel); - when(mNotificationData.getChannel(mNoChannelRow.getEntry().key)).thenReturn(null); mBubbleData = new BubbleData(); mBubbleController = new TestableBubbleController(mContext, mStatusBarWindowController, @@ -391,8 +389,7 @@ public class BubbleControllerTest extends SysuiTestCase { mEntryListener.onPendingEntryAdded(mSuppressNotifRow.getEntry()); mBubbleController.updateBubble(mSuppressNotifRow.getEntry(), true /* updatePosition */); - // Should be a bubble & should show in shade because we weren't forground - assertTrue(mSuppressNotifRow.getEntry().isBubble()); + // Should show in shade because we weren't forground assertTrue(mSuppressNotifRow.getEntry().showInShadeWhenBubble()); // # of bubbles should change @@ -428,8 +425,7 @@ public class BubbleControllerTest extends SysuiTestCase { mEntryListener.onPendingEntryAdded(mSuppressNotifRow.getEntry()); mBubbleController.updateBubble(mSuppressNotifRow.getEntry(), true /* updatePosition */); - // Should be a bubble & should NOT show in shade because we were foreground - assertTrue(mSuppressNotifRow.getEntry().isBubble()); + // Should NOT show in shade because we were foreground assertFalse(mSuppressNotifRow.getEntry().showInShadeWhenBubble()); // # of bubbles should change @@ -444,8 +440,6 @@ public class BubbleControllerTest extends SysuiTestCase { mEntryListener.onPendingEntryAdded(mRow.getEntry()); mBubbleController.updateBubble(mRow.getEntry(), true /* updatePosition */); - assertTrue(mRow.getEntry().isBubble()); - // Simulate notification cancellation. mEntryListener.onEntryRemoved(mRow.getEntry(), null /* notificationVisibility (unused) */, false /* removedbyUser */); @@ -454,15 +448,18 @@ public class BubbleControllerTest extends SysuiTestCase { } @Test - public void testMarkNewNotificationAsBubble() { + public void testMarkNewNotificationAsShowInShade() { mEntryListener.onPendingEntryAdded(mRow.getEntry()); - assertTrue(mRow.getEntry().isBubble()); + assertTrue(mRow.getEntry().showInShadeWhenBubble()); } @Test - public void testMarkNewNotificationAsShowInShade() { - mEntryListener.onPendingEntryAdded(mRow.getEntry()); - assertTrue(mRow.getEntry().showInShadeWhenBubble()); + public void testAddNotif_notBubble() { + mEntryListener.onPendingEntryAdded(mNonBubbleNotifRow.getEntry()); + mEntryListener.onPreEntryUpdated(mNonBubbleNotifRow.getEntry()); + + verify(mBubbleStateChangeListener, never()).onHasBubblesChanged(anyBoolean()); + assertThat(mBubbleController.hasBubbles()).isFalse(); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleStackViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleStackViewTest.java new file mode 100644 index 000000000000..801308fc77da --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleStackViewTest.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2019 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.systemui.bubbles; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.view.View; +import android.widget.TextView; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.R; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +public class BubbleStackViewTest extends SysuiTestCase { + private BubbleStackView mStackView; + @Mock private Bubble mBubble; + @Mock private NotificationEntry mNotifEntry; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + mStackView = new BubbleStackView(mContext, new BubbleData(), null); + mBubble.entry = mNotifEntry; + } + + @Test + public void testAnimateInFlyoutForBubble() throws InterruptedException { + when(mNotifEntry.getUpdateMessage(any())).thenReturn("Test Flyout Message."); + mStackView.animateInFlyoutForBubble(mBubble); + + // Wait for the fade in. + Thread.sleep(200); + + // Flyout should be visible and showing our text. + assertEquals(1f, mStackView.findViewById(R.id.bubble_flyout).getAlpha(), .01f); + assertEquals("Test Flyout Message.", + ((TextView) mStackView.findViewById(R.id.bubble_flyout_text)).getText()); + + // Wait until it should have gone away. + Thread.sleep(BubbleStackView.FLYOUT_HIDE_AFTER + 200); + + // Flyout should be gone. + assertEquals(View.GONE, mStackView.findViewById(R.id.bubble_flyout).getVisibility()); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java index 50fadef8aaf2..910cee3574dd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java @@ -57,6 +57,7 @@ public class StackAnimationControllerTest extends PhysicsAnimationLayoutTestCase * direction is correct. */ @Test + @Ignore("Flaking") public void testMoveFirstBubbleWithStackFollowing() throws InterruptedException { mStackController.moveFirstBubbleWithStackFollowing(200, 100); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java index ea8d4b2f8f60..818db878cdc0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java @@ -14,13 +14,19 @@ package com.android.systemui.qs.tiles; +import static junit.framework.Assert.assertTrue; import static junit.framework.TestCase.assertEquals; +import static org.mockito.ArgumentMatchers.same; import static org.mockito.Mockito.any; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.media.MediaRouter; +import android.media.MediaRouter.RouteInfo; +import android.media.projection.MediaProjectionInfo; import android.service.quicksettings.Tile; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -33,6 +39,7 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.qs.QSTileHost; import com.android.systemui.statusbar.policy.CastController; +import com.android.systemui.statusbar.policy.CastController.CastDevice; import com.android.systemui.statusbar.policy.KeyguardMonitor; import com.android.systemui.statusbar.policy.NetworkController; @@ -43,8 +50,9 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import java.util.HashSet; -import java.util.Set; +import java.util.ArrayList; +import java.util.List; + @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper @@ -93,7 +101,6 @@ public class CastTileTest extends SysuiTestCase { verify(mNetworkController).observe(any(LifecycleOwner.class), signalCallbackArgumentCaptor.capture()); mCallback = signalCallbackArgumentCaptor.getValue(); - } @Test @@ -120,33 +127,125 @@ public class CastTileTest extends SysuiTestCase { assertEquals(Tile.STATE_UNAVAILABLE, mCastTile.getState().state); } - @Test - public void testStateActive_wifiEnabledAndCasting() { - CastController.CastDevice device = mock(CastController.CastDevice.class); - device.state = CastController.CastDevice.STATE_CONNECTED; - Set<CastController.CastDevice> devices = new HashSet<>(); - devices.add(device); - when(mController.getCastDevices()).thenReturn(devices); - + private void enableWifiAndProcessMessages() { NetworkController.IconState qsIcon = new NetworkController.IconState(true, 0, ""); mCallback.setWifiIndicators(true, mock(NetworkController.IconState.class), qsIcon, false,false, "", false, ""); mTestableLooper.processAllMessages(); + } + + @Test + public void testStateActive_wifiEnabledAndCasting() { + CastController.CastDevice device = new CastController.CastDevice(); + device.state = CastController.CastDevice.STATE_CONNECTED; + List<CastDevice> devices = new ArrayList<>(); + devices.add(device); + when(mController.getCastDevices()).thenReturn(devices); + enableWifiAndProcessMessages(); assertEquals(Tile.STATE_ACTIVE, mCastTile.getState().state); } @Test public void testStateInactive_wifiEnabledNotCasting() { - NetworkController.IconState qsIcon = - new NetworkController.IconState(true, 0, ""); - mCallback.setWifiIndicators(true, mock(NetworkController.IconState.class), - qsIcon, false,false, "", - false, ""); + enableWifiAndProcessMessages(); + assertEquals(Tile.STATE_INACTIVE, mCastTile.getState().state); + } + + @Test + public void testHandleClick_castDevicePresent() { + CastController.CastDevice device = new CastController.CastDevice(); + device.state = CastDevice.STATE_CONNECTED; + device.tag = mock(MediaRouter.RouteInfo.class); + List<CastDevice> devices = new ArrayList<>(); + devices.add(device); + when(mController.getCastDevices()).thenReturn(devices); + + enableWifiAndProcessMessages(); + mCastTile.handleClick(); mTestableLooper.processAllMessages(); - assertEquals(Tile.STATE_INACTIVE, mCastTile.getState().state); + verify(mActivityStarter, times(1)).postQSRunnableDismissingKeyguard(any()); + } + + @Test + public void testHandleClick_projectionOnly() { + CastController.CastDevice device = new CastController.CastDevice(); + device.state = CastDevice.STATE_CONNECTED; + device.tag = mock(MediaProjectionInfo.class); + List<CastDevice> devices = new ArrayList<>(); + devices.add(device); + when(mController.getCastDevices()).thenReturn(devices); + + enableWifiAndProcessMessages(); + mCastTile.handleClick(); + mTestableLooper.processAllMessages(); + + verify(mController, times(1)).stopCasting(same(device)); + } + + @Test + public void testUpdateState_projectionOnly() { + CastController.CastDevice device = new CastController.CastDevice(); + device.state = CastDevice.STATE_CONNECTED; + device.tag = mock(MediaProjectionInfo.class); + device.name = "Test Projection Device"; + List<CastDevice> devices = new ArrayList<>(); + devices.add(device); + when(mController.getCastDevices()).thenReturn(devices); + + enableWifiAndProcessMessages(); + assertEquals(Tile.STATE_ACTIVE, mCastTile.getState().state); + assertTrue(mCastTile.getState().secondaryLabel.toString().startsWith(device.name)); + } + + @Test + public void testUpdateState_castingAndProjection() { + CastController.CastDevice casting = new CastController.CastDevice(); + casting.state = CastDevice.STATE_CONNECTED; + casting.tag = mock(RouteInfo.class); + casting.name = "Test Casting Device"; + + CastController.CastDevice projection = new CastController.CastDevice(); + projection.state = CastDevice.STATE_CONNECTED; + projection.tag = mock(MediaProjectionInfo.class); + projection.name = "Test Projection Device"; + + List<CastDevice> devices = new ArrayList<>(); + devices.add(casting); + devices.add(projection); + when(mController.getCastDevices()).thenReturn(devices); + + enableWifiAndProcessMessages(); + + // Note here that the tile should be active, and should choose casting over projection. + assertEquals(Tile.STATE_ACTIVE, mCastTile.getState().state); + assertTrue(mCastTile.getState().secondaryLabel.toString().startsWith(casting.name)); + } + + @Test + public void testUpdateState_connectedAndConnecting() { + CastController.CastDevice connecting = new CastController.CastDevice(); + connecting.state = CastDevice.STATE_CONNECTING; + connecting.tag = mock(RouteInfo.class); + connecting.name = "Test Casting Device"; + + CastController.CastDevice connected = new CastController.CastDevice(); + connected.state = CastDevice.STATE_CONNECTED; + connected.tag = mock(RouteInfo.class); + connected.name = "Test Casting Device"; + + List<CastDevice> devices = new ArrayList<>(); + devices.add(connecting); + devices.add(connected); + when(mController.getCastDevices()).thenReturn(devices); + + enableWifiAndProcessMessages(); + + // Tile should be connected and always prefer the connected device. + assertEquals(Tile.STATE_ACTIVE, mCastTile.getState().state); + assertTrue(mCastTile.getState().secondaryLabel.toString().startsWith(connected.name)); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java index 7e089a653b9e..8cc1571b925f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java @@ -16,6 +16,7 @@ package com.android.systemui.statusbar; +import static android.app.Notification.FLAG_BUBBLE; import static android.app.NotificationManager.IMPORTANCE_DEFAULT; import static android.app.NotificationManager.IMPORTANCE_HIGH; @@ -154,9 +155,7 @@ public class NotificationTestHelper { */ public ExpandableNotificationRow createBubble() throws Exception { - Notification n = createNotification(false /* isGroupSummary */, - null /* groupKey */, makeBubbleMetadata(null)); - return generateRow(n, PKG, UID, USER_HANDLE, 0 /* extraInflationFlags */, IMPORTANCE_HIGH); + return createBubble(makeBubbleMetadata(null), PKG); } /** @@ -166,21 +165,7 @@ public class NotificationTestHelper { */ public ExpandableNotificationRow createBubble(@Nullable PendingIntent deleteIntent) throws Exception { - Notification n = createNotification(false /* isGroupSummary */, - null /* groupKey */, makeBubbleMetadata(deleteIntent)); - return generateRow(n, PKG, UID, USER_HANDLE, 0 /* extraInflationFlags */, IMPORTANCE_HIGH); - } - - /** - * Returns an {@link ExpandableNotificationRow} that should be shown as a bubble. - * - * @param bubbleMetadata the {@link BubbleMetadata} to use - */ - public ExpandableNotificationRow createBubble(BubbleMetadata bubbleMetadata) - throws Exception { - Notification n = createNotification(false /* isGroupSummary */, - null /* groupKey */, bubbleMetadata); - return generateRow(n, PKG, UID, USER_HANDLE, 0 /* extraInflationFlags */, IMPORTANCE_HIGH); + return createBubble(makeBubbleMetadata(deleteIntent), PKG); } /** @@ -192,6 +177,7 @@ public class NotificationTestHelper { throws Exception { Notification n = createNotification(false /* isGroupSummary */, null /* groupKey */, bubbleMetadata); + n.flags |= FLAG_BUBBLE; return generateRow(n, pkg, UID, USER_HANDLE, 0 /* extraInflationFlags */, IMPORTANCE_HIGH); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java new file mode 100644 index 000000000000..cca9f2834e93 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2019 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.systemui.statusbar.notification.collection; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.when; + +import android.app.Notification; +import android.os.Bundle; +import android.service.notification.StatusBarNotification; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class NotificationEntryTest extends SysuiTestCase { + @Mock + private StatusBarNotification mStatusBarNotification; + @Mock + private Notification mNotif; + + private NotificationEntry mEntry; + private Bundle mExtras; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + when(mStatusBarNotification.getKey()).thenReturn("key"); + when(mStatusBarNotification.getNotification()).thenReturn(mNotif); + + mExtras = new Bundle(); + mNotif.extras = mExtras; + + mEntry = new NotificationEntry(mStatusBarNotification); + } + + @Test + public void testGetUpdateMessage_default() { + final String msg = "Hello there!"; + doReturn(Notification.Style.class).when(mNotif).getNotificationStyle(); + mExtras.putCharSequence(Notification.EXTRA_TEXT, msg); + assertEquals(msg, mEntry.getUpdateMessage(mContext)); + } + + @Test + public void testGetUpdateMessage_bigText() { + final String msg = "A big hello there!"; + doReturn(Notification.BigTextStyle.class).when(mNotif).getNotificationStyle(); + mExtras.putCharSequence(Notification.EXTRA_TEXT, "A small hello there."); + mExtras.putCharSequence(Notification.EXTRA_BIG_TEXT, msg); + + // Should be big text, not the small text. + assertEquals(msg, mEntry.getUpdateMessage(mContext)); + } + + @Test + public void testGetUpdateMessage_media() { + doReturn(Notification.MediaStyle.class).when(mNotif).getNotificationStyle(); + + // Media notifs don't get update messages. + assertNull(mEntry.getUpdateMessage(mContext)); + } + + @Test + public void testGetUpdateMessage_inboxStyle() { + doReturn(Notification.InboxStyle.class).when(mNotif).getNotificationStyle(); + mExtras.putCharSequenceArray( + Notification.EXTRA_TEXT_LINES, + new CharSequence[]{ + "How do you feel about tests?", + "They're okay, I guess.", + "I hate when they're flaky.", + "Really? I prefer them that way."}); + + // Should be the last one only. + assertEquals("Really? I prefer them that way.", mEntry.getUpdateMessage(mContext)); + } + + @Test + public void testGetUpdateMessage_messagingStyle() { + doReturn(Notification.MessagingStyle.class).when(mNotif).getNotificationStyle(); + mExtras.putParcelableArray( + Notification.EXTRA_MESSAGES, + new Bundle[]{ + new Notification.MessagingStyle.Message( + "Hello", 0, "Josh").toBundle(), + new Notification.MessagingStyle.Message( + "Oh, hello!", 0, "Mady").toBundle()}); + + // Should be the last one only. + assertEquals("Mady: Oh, hello!", mEntry.getUpdateMessage(mContext)); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java index 4fe364a7654b..3c91b3f1bdd3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.notification.row; +import static android.app.NotificationManager.IMPORTANCE_DEFAULT; import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE; import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL; import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_POSITIVE; @@ -24,11 +25,13 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; +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 org.mockito.Mockito.when; +import android.app.NotificationChannel; import android.content.Context; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -130,6 +133,21 @@ public class NotificationBlockingHelperManagerTest extends SysuiTestCase { verify(mGutsManager).openGuts(row, 0, 0, mMenuItem); } + @Test + public void testPerhapsShowBlockingHelper_notShownForMultiChannelGroup() throws Exception { + ExpandableNotificationRow groupRow = createBlockableGroupRowSpy(10); + int i = 0; + for (ExpandableNotificationRow childRow : groupRow.getNotificationChildren()) { + childRow.getEntry().channel = + new NotificationChannel(Integer.toString(i++), "", IMPORTANCE_DEFAULT); + } + + groupRow.getEntry().userSentiment = USER_SENTIMENT_NEGATIVE; + + assertFalse(mBlockingHelperManager.perhapsShowBlockingHelper(groupRow, mMenuRow)); + + verify(mGutsManager, never()).openGuts(groupRow, 0, 0, mMenuItem); + } @Test public void testPerhapsShowBlockingHelper_shownForLargeGroup() throws Exception { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java index ff849c3720f3..8380192ffd32 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java @@ -325,7 +325,6 @@ public class NotificationGutsManagerTest extends SysuiTestCase { eq(false), eq(false), eq(true) /* isForBlockingHelper */, - eq(true) /* isUserSentimentNegative */, eq(0), eq(false) /* wasShownHighPriority */); } @@ -354,7 +353,6 @@ public class NotificationGutsManagerTest extends SysuiTestCase { eq(false), eq(false), eq(false) /* isForBlockingHelper */, - eq(true) /* isUserSentimentNegative */, eq(0), eq(false) /* wasShownHighPriority */); } @@ -385,7 +383,6 @@ public class NotificationGutsManagerTest extends SysuiTestCase { eq(false), eq(false), eq(true) /* isForBlockingHelper */, - eq(true) /* isUserSentimentNegative */, eq(IMPORTANCE_DEFAULT), eq(true) /* wasShownHighPriority */); } @@ -415,7 +412,6 @@ public class NotificationGutsManagerTest extends SysuiTestCase { eq(true), eq(false), eq(false) /* isForBlockingHelper */, - eq(true) /* isUserSentimentNegative */, eq(0), eq(false) /* wasShownHighPriority */); } @@ -444,7 +440,6 @@ public class NotificationGutsManagerTest extends SysuiTestCase { eq(false), eq(false), eq(true) /* isForBlockingHelper */, - eq(true) /* isUserSentimentNegative */, eq(0), eq(false) /* wasShownHighPriority */); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java index fb4fd314d363..d2f8e02311e6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java @@ -313,108 +313,20 @@ public class NotificationInfoTest extends SysuiTestCase { } @Test - public void testBindNotification_BlockButton() throws Exception { + public void testBindNotification_BlockLink_BlockingHelper() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT, true); - final View block = mNotificationInfo.findViewById(R.id.int_block); - final View minimize = mNotificationInfo.findViewById(R.id.block_or_minimize); - assertEquals(VISIBLE, block.getVisibility()); - assertEquals(GONE, minimize.getVisibility()); - } - - @Test - public void testBindNotification_BlockButton_BlockingHelper() throws Exception { - mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - true /* isBlockingHelper */, false, IMPORTANCE_DEFAULT, true); - final View block = mNotificationInfo.findViewById(R.id.block); + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, mock( + NotificationInfo.OnSettingsClickListener.class), null, true, false, + true /* isBlockingHelper */, IMPORTANCE_DEFAULT, true); + final View block = + mNotificationInfo.findViewById(R.id.blocking_helper_turn_off_notifications); final View interruptivenessSettings = mNotificationInfo.findViewById( - R.id.interruptiveness_settings); + R.id.inline_controls); assertEquals(VISIBLE, block.getVisibility()); assertEquals(GONE, interruptivenessSettings.getVisibility()); } @Test - public void testBindNotification_SilenceButton_CurrentlyAlerting() throws Exception { - mNotificationChannel.setImportance(IMPORTANCE_DEFAULT); - mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT, true); - final TextView silent = mNotificationInfo.findViewById(R.id.int_silent_label); - assertEquals(VISIBLE, silent.getVisibility()); - assertEquals( - mContext.getString(R.string.inline_silent_button_silent), silent.getText()); - } - - @Test - public void testBindNotification_verifyButtonTexts() throws Exception { - mNotificationChannel.setImportance(IMPORTANCE_LOW); - mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_LOW, false); - final TextView block = mNotificationInfo.findViewById(R.id.int_block_label); - final TextView alert = mNotificationInfo.findViewById(R.id.int_alert_label); - final TextView silent = mNotificationInfo.findViewById(R.id.int_silent_label); - assertEquals(VISIBLE, silent.getVisibility()); - assertEquals(VISIBLE, block.getVisibility()); - assertEquals(VISIBLE, alert.getVisibility()); - assertEquals( - mContext.getString(R.string.inline_silent_button_silent), - silent.getText()); - assertEquals( - mContext.getString(R.string.inline_silent_button_alert), alert.getText()); - assertEquals( - mContext.getString(R.string.inline_block_button), block.getText()); - } - - @Test - public void testBindNotification_verifyHintTextForSilent() throws Exception { - mNotificationChannel.setImportance(IMPORTANCE_LOW); - mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_LOW, false); - TextView hintText = mNotificationInfo.findViewById(R.id.hint_text); - assertEquals(mContext.getString(R.string.hint_text_silent), hintText.getText()); - } - - @Test - public void testBindNotification_verifyHintTextForBlock() throws Exception { - mNotificationChannel.setImportance(IMPORTANCE_LOW); - mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_LOW, false); - View blockWrapper = mNotificationInfo.findViewById(R.id.int_block_wrapper); - blockWrapper.performClick(); - TextView hintText = mNotificationInfo.findViewById(R.id.hint_text); - assertEquals(mContext.getString(R.string.hint_text_block), hintText.getText()); - } - - @Test - public void testBindNotification_verifyHintTextForAlert() throws Exception { - mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT, true); - TextView hintText = mNotificationInfo.findViewById(R.id.hint_text); - assertEquals(mContext.getString(R.string.hint_text_alert), hintText.getText()); - } - - @Test - public void testBindNotification_MinButton() throws Exception { - mSbn.getNotification().flags = Notification.FLAG_FOREGROUND_SERVICE; - mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT, true); - final View block = mNotificationInfo.findViewById(R.id.block); - final View interruptivenessSettings = mNotificationInfo.findViewById( - R.id.interruptiveness_settings); - final View minimize = mNotificationInfo.findViewById(R.id.minimize); - assertEquals(GONE, block.getVisibility()); - assertEquals(GONE, interruptivenessSettings.getVisibility()); - assertEquals(VISIBLE, minimize.getVisibility()); - } - - @Test public void testBindNotification_SetsOnClickListenerForSettings() throws Exception { final CountDownLatch latch = new CountDownLatch(1); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, @@ -484,7 +396,7 @@ public class NotificationInfoTest extends SysuiTestCase { TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, false, true, - true, true, + true, IMPORTANCE_DEFAULT, true); verify(mMetricsLogger).write(argThat(logMaker -> logMaker.getCategory() == MetricsEvent.ACTION_NOTE_CONTROLS @@ -499,7 +411,7 @@ public class NotificationInfoTest extends SysuiTestCase { TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, false, true, - true, true, + true, IMPORTANCE_DEFAULT, true); mNotificationInfo.logBlockingHelperCounter("HowCanNotifsBeRealIfAppsArent"); verify(mMetricsLogger).count(eq("HowCanNotifsBeRealIfAppsArent"), eq(1)); @@ -526,7 +438,7 @@ public class NotificationInfoTest extends SysuiTestCase { throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, MULTIPLE_CHANNEL_COUNT, mSbn, null, null, - null, true, true, IMPORTANCE_DEFAULT, true); + null, true, false, IMPORTANCE_DEFAULT, true); final TextView channelNameView = mNotificationInfo.findViewById(R.id.channel_name); assertEquals(GONE, channelNameView.getVisibility()); @@ -537,20 +449,24 @@ public class NotificationInfoTest extends SysuiTestCase { public void testStopInvisibleIfBundleFromDifferentChannels() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, MULTIPLE_CHANNEL_COUNT, mSbn, null, null, - null, true, true, IMPORTANCE_DEFAULT, true); - final TextView blockView = mNotificationInfo.findViewById(R.id.block); - assertEquals(GONE, blockView.getVisibility()); + null, true, false, IMPORTANCE_DEFAULT, true); + assertEquals(GONE, mNotificationInfo.findViewById( + R.id.interruptiveness_settings).getVisibility()); + assertEquals(VISIBLE, mNotificationInfo.findViewById( + R.id.non_configurable_multichannel_text).getVisibility()); } @Test - public void testbindNotification_UnblockableTextVisibleWhenAppUnblockable() throws Exception { + public void testBindNotification_whenAppUnblockable() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true, IMPORTANCE_DEFAULT, true); - final TextView view = mNotificationInfo.findViewById(R.id.block_prompt); + final TextView view = mNotificationInfo.findViewById(R.id.non_configurable_text); assertEquals(View.VISIBLE, view.getVisibility()); assertEquals(mContext.getString(R.string.notification_unblockable_desc), view.getText()); + assertEquals(GONE, + mNotificationInfo.findViewById(R.id.interruptiveness_settings).getVisibility()); } @Test @@ -568,23 +484,9 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT, false); - - mNotificationInfo.findViewById(R.id.int_block).performClick(); - mTestableLooper.processAllMessages(); - verify(mMockINotificationManager, never()).updateNotificationChannelForPackage( - anyString(), eq(TEST_UID), any()); - } - - @Test - public void testDoesNotUpdateNotificationChannelAfterImportanceChangedMin() - throws Exception { - mNotificationChannel.setImportance(IMPORTANCE_LOW); - mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT, false); + IMPORTANCE_LOW, false); - mNotificationInfo.findViewById(R.id.minimize).performClick(); + mNotificationInfo.findViewById(R.id.alert_row).performClick(); mTestableLooper.processAllMessages(); verify(mMockINotificationManager, never()).updateNotificationChannelForPackage( anyString(), eq(TEST_UID), any()); @@ -598,21 +500,7 @@ public class NotificationInfoTest extends SysuiTestCase { TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, IMPORTANCE_DEFAULT, true); - mNotificationInfo.findViewById(R.id.int_silent).performClick(); - mTestableLooper.processAllMessages(); - verify(mMockINotificationManager, never()).updateNotificationChannelForPackage( - anyString(), eq(TEST_UID), any()); - } - - @Test - public void testDoesNotUpdateNotificationChannelAfterImportanceChangedUnSilenced() - throws Exception { - mNotificationChannel.setImportance(IMPORTANCE_LOW); - mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT, false); - - mNotificationInfo.findViewById(R.id.int_alert).performClick(); + mNotificationInfo.findViewById(R.id.silent_row).performClick(); mTestableLooper.processAllMessages(); verify(mMockINotificationManager, never()).updateNotificationChannelForPackage( anyString(), eq(TEST_UID), any()); @@ -650,76 +538,6 @@ public class NotificationInfoTest extends SysuiTestCase { } @Test - public void testHandleCloseControls_setsNotificationsDisabledForMultipleChannelNotifications() - throws Exception { - mNotificationChannel.setImportance(IMPORTANCE_LOW); - mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel /* notificationChannel */, - 10 /* numUniqueChannelsInRow */, mSbn, null /* checkSaveListener */, - null /* onSettingsClick */, null /* onAppSettingsClick */, - true, false /* isNonblockable */, IMPORTANCE_DEFAULT, false - ); - - mNotificationInfo.findViewById(R.id.int_block_wrapper).performClick(); - mNotificationInfo.findViewById(R.id.done_button).performClick(); - mNotificationInfo.handleCloseControls(true, false); - - mTestableLooper.processAllMessages(); - verify(mMockINotificationManager, times(1)) - .setNotificationsEnabledWithImportanceLockForPackage( - anyString(), eq(TEST_UID), eq(false)); - } - - - @Test - public void testHandleCloseControls_keepsNotificationsEnabledForMultipleChannelNotifications() - throws Exception { - mNotificationChannel.setImportance(IMPORTANCE_LOW); - mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel /* notificationChannel */, - 10 /* numUniqueChannelsInRow */, mSbn, null /* checkSaveListener */, - null /* onSettingsClick */, null /* onAppSettingsClick */, - true, false /* isNonblockable */, IMPORTANCE_DEFAULT, false - ); - - mNotificationInfo.findViewById(R.id.int_block_wrapper).performClick(); - mNotificationInfo.findViewById(R.id.done_button).performClick(); - mNotificationInfo.handleCloseControls(true, false); - - mTestableLooper.processAllMessages(); - verify(mMockINotificationManager, times(1)) - .setNotificationsEnabledWithImportanceLockForPackage( - anyString(), eq(TEST_UID), eq(false)); - } - - @Test - public void testCloseControls_blockingHelperSavesImportanceForMultipleChannelNotifications() - throws Exception { - mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel /* notificationChannel */, - 10 /* numUniqueChannelsInRow */, mSbn, null /* checkSaveListener */, - null /* onSettingsClick */, null /* onAppSettingsClick */, - true /* provisioned */, - false /* isNonblockable */, true /* isForBlockingHelper */, - true /* isUserSentimentNegative */, IMPORTANCE_DEFAULT, true); - - NotificationGuts guts = spy(new NotificationGuts(mContext, null)); - when(guts.getWindowToken()).thenReturn(mock(IBinder.class)); - doNothing().when(guts).animateClose(anyInt(), anyInt(), anyBoolean()); - doNothing().when(guts).setExposed(anyBoolean(), anyBoolean()); - guts.setGutsContent(mNotificationInfo); - mNotificationInfo.setGutsParent(guts); - - mNotificationInfo.findViewById(R.id.done).performClick(); - - verify(mBlockingHelperManager).dismissCurrentBlockingHelper(); - mTestableLooper.processAllMessages(); - verify(mMockINotificationManager, times(1)) - .setNotificationsEnabledWithImportanceLockForPackage( - anyString(), eq(TEST_UID), eq(true)); - } - - @Test public void testCloseControls_nonNullCheckSaveListenerDoesntDelayKeepShowing_BlockingHelper() throws Exception { NotificationInfo.CheckSaveListener listener = @@ -729,7 +547,7 @@ public class NotificationInfoTest extends SysuiTestCase { 10 /* numUniqueChannelsInRow */, mSbn, listener /* checkSaveListener */, null /* onSettingsClick */, null /* onAppSettingsClick */, true /* provisioned */, false /* isNonblockable */, true /* isForBlockingHelper */, - true /* isUserSentimentNegative */, IMPORTANCE_DEFAULT, true); + IMPORTANCE_DEFAULT, true); NotificationGuts guts = spy(new NotificationGuts(mContext, null)); when(guts.getWindowToken()).thenReturn(mock(IBinder.class)); @@ -738,7 +556,7 @@ public class NotificationInfoTest extends SysuiTestCase { guts.setGutsContent(mNotificationInfo); mNotificationInfo.setGutsParent(guts); - mNotificationInfo.findViewById(R.id.done).performClick(); + mNotificationInfo.findViewById(R.id.keep_showing).performClick(); verify(mBlockingHelperManager).dismissCurrentBlockingHelper(); mTestableLooper.processAllMessages(); @@ -757,8 +575,7 @@ public class NotificationInfoTest extends SysuiTestCase { 10 /* numUniqueChannelsInRow */, mSbn, listener /* checkSaveListener */, null /* onSettingsClick */, null /* onAppSettingsClick */, false /* isNonblockable */, true /* isForBlockingHelper */, - true, true /* isUserSentimentNegative */, /* isNoisy */ - IMPORTANCE_DEFAULT, true); + true, IMPORTANCE_DEFAULT, true); mNotificationInfo.handleCloseControls(true /* save */, false /* force */); @@ -777,9 +594,9 @@ public class NotificationInfoTest extends SysuiTestCase { null /* onSettingsClick */, null /* onAppSettingsClick */, true /* provisioned */, false /* isNonblockable */, true /* isForBlockingHelper */, - true /* isUserSentimentNegative */, IMPORTANCE_DEFAULT, true); + IMPORTANCE_DEFAULT, true); - mNotificationInfo.findViewById(R.id.block).performClick(); + mNotificationInfo.findViewById(R.id.deliver_silently).performClick(); mTestableLooper.processAllMessages(); verify(listener).checkSave(any(Runnable.class), eq(mSbn)); } @@ -799,7 +616,6 @@ public class NotificationInfoTest extends SysuiTestCase { false /* isNonblockable */, true /* isForBlockingHelper */, true, - false /* isUserSentimentNegative */, IMPORTANCE_DEFAULT, true); NotificationGuts guts = mock(NotificationGuts.class); doCallRealMethod().when(guts).closeControls(anyInt(), anyInt(), anyBoolean(), anyBoolean()); @@ -811,53 +627,9 @@ public class NotificationInfoTest extends SysuiTestCase { } @Test - public void testNonBlockableAppDoesNotBecomeBlocked() throws Exception { - mNotificationChannel.setImportance(IMPORTANCE_LOW); - mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true, - true, false, IMPORTANCE_DEFAULT, false); - mNotificationInfo.findViewById(R.id.block).performClick(); - waitForUndoButton(); - - mTestableLooper.processAllMessages(); - verify(mMockINotificationManager, never()).updateNotificationChannelForPackage( - anyString(), eq(TEST_UID), any()); - } - - @Test - public void testBlockChangedCallsUpdateNotificationChannel_notBlockingHelper() + public void testSilentlyChangedCallsUpdateNotificationChannel_blockingHelper() throws Exception { mNotificationChannel.setImportance(IMPORTANCE_LOW); - mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, - null, null, null, - true, false, - IMPORTANCE_DEFAULT, false); - - mNotificationInfo.findViewById(R.id.int_block_wrapper).performClick(); - mNotificationInfo.findViewById(R.id.done_button).performClick(); - mNotificationInfo.handleCloseControls(true, false); - - ArgumentCaptor<LogMaker> logMakerCaptor = ArgumentCaptor.forClass(LogMaker.class); - verify(mMetricsLogger, times(2)).write(logMakerCaptor.capture()); - assertEquals(MetricsProto.MetricsEvent.TYPE_ACTION, - logMakerCaptor.getValue().getType()); - assertEquals(IMPORTANCE_NONE - IMPORTANCE_LOW, - logMakerCaptor.getValue().getSubtype()); - - mTestableLooper.processAllMessages(); - ArgumentCaptor<NotificationChannel> updated = - ArgumentCaptor.forClass(NotificationChannel.class); - verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage( - anyString(), eq(TEST_UID), updated.capture()); - assertTrue((updated.getValue().getUserLockedFields() - & USER_LOCKED_IMPORTANCE) != 0); - assertEquals(IMPORTANCE_NONE, updated.getValue().getImportance()); - } - - @Test - public void testBlockChangedCallsUpdateNotificationChannel_blockingHelper() throws Exception { - mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification( mMockPackageManager, mMockINotificationManager, @@ -871,21 +643,13 @@ public class NotificationInfoTest extends SysuiTestCase { true /*provisioned */, false /* isNonblockable */, true /* isForBlockingHelper */, - true /* isUserSentimentNegative */, IMPORTANCE_DEFAULT, false); - mNotificationInfo.findViewById(R.id.block).performClick(); + mNotificationInfo.findViewById(R.id.deliver_silently).performClick(); waitForUndoButton(); mNotificationInfo.handleCloseControls(true, false); - ArgumentCaptor<LogMaker> logMakerCaptor = ArgumentCaptor.forClass(LogMaker.class); - verify(mMetricsLogger, times(3)).write(logMakerCaptor.capture()); - assertEquals(MetricsProto.MetricsEvent.TYPE_ACTION, - logMakerCaptor.getValue().getType()); - assertEquals(IMPORTANCE_NONE - IMPORTANCE_LOW, - logMakerCaptor.getValue().getSubtype()); - mTestableLooper.processAllMessages(); ArgumentCaptor<NotificationChannel> updated = ArgumentCaptor.forClass(NotificationChannel.class); @@ -893,69 +657,17 @@ public class NotificationInfoTest extends SysuiTestCase { anyString(), eq(TEST_UID), updated.capture()); assertTrue((updated.getValue().getUserLockedFields() & USER_LOCKED_IMPORTANCE) != 0); - assertEquals(IMPORTANCE_NONE, updated.getValue().getImportance()); + assertEquals(IMPORTANCE_LOW, updated.getValue().getImportance()); } - @Test - public void testNonBlockableAppDoesNotBecomeMin() throws Exception { + public void testKeepUpdatesNotificationChannel_blockingHelper() throws Exception { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true, - true, false, IMPORTANCE_DEFAULT, false); - mNotificationInfo.findViewById(R.id.minimize).performClick(); - waitForUndoButton(); - - mTestableLooper.processAllMessages(); - verify(mMockINotificationManager, never()).updateNotificationChannelForPackage( - anyString(), eq(TEST_UID), any()); - } - - @Test - public void testMinChangedCallsUpdateNotificationChannel() throws Exception { - mNotificationChannel.setImportance(IMPORTANCE_LOW); - mSbn.getNotification().flags = Notification.FLAG_FOREGROUND_SERVICE; - mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - true, false, IMPORTANCE_DEFAULT, false); - - mNotificationInfo.findViewById(R.id.minimize).performClick(); - waitForUndoButton(); - mNotificationInfo.handleCloseControls(true, false); - - mTestableLooper.processAllMessages(); - ArgumentCaptor<NotificationChannel> updated = - ArgumentCaptor.forClass(NotificationChannel.class); - verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage( - anyString(), eq(TEST_UID), updated.capture()); - assertTrue((updated.getValue().getUserLockedFields() - & USER_LOCKED_IMPORTANCE) != 0); - assertEquals(IMPORTANCE_MIN, updated.getValue().getImportance()); - } - - @Test - public void testSilentlyChangedCallsUpdateNotificationChannel_blockingHelper() - throws Exception { - mNotificationChannel.setImportance(IMPORTANCE_LOW); - mNotificationInfo.bindNotification( - mMockPackageManager, - mMockINotificationManager, - TEST_PACKAGE_NAME, - mNotificationChannel, - 1 /* numChannels */, - mSbn, - null /* checkSaveListener */, - null /* onSettingsClick */, - null /* onAppSettingsClick */, - true /*provisioned */, - false /* isNonblockable */, - true /* isForBlockingHelper */, - true /* isUserSentimentNegative */, - IMPORTANCE_DEFAULT, - false); + IMPORTANCE_LOW, false); - mNotificationInfo.findViewById(R.id.deliver_silently).performClick(); - waitForUndoButton(); + mNotificationInfo.findViewById(R.id.keep_showing).performClick(); mNotificationInfo.handleCloseControls(true, false); mTestableLooper.processAllMessages(); @@ -963,16 +675,15 @@ public class NotificationInfoTest extends SysuiTestCase { ArgumentCaptor.forClass(NotificationChannel.class); verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage( anyString(), eq(TEST_UID), updated.capture()); - assertTrue((updated.getValue().getUserLockedFields() - & USER_LOCKED_IMPORTANCE) != 0); - assertEquals(IMPORTANCE_LOW, updated.getValue().getImportance()); + assertTrue(0 != (mNotificationChannel.getUserLockedFields() & USER_LOCKED_IMPORTANCE)); + assertEquals(IMPORTANCE_LOW, mNotificationChannel.getImportance()); } @Test - public void testKeepUpdatesNotificationChannel() throws Exception { - mNotificationChannel.setImportance(IMPORTANCE_LOW); + public void testNoActionsUpdatesNotificationChannel_blockingHelper() throws Exception { + mNotificationChannel.setImportance(IMPORTANCE_DEFAULT); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true, IMPORTANCE_DEFAULT, false); mNotificationInfo.handleCloseControls(true, false); @@ -983,7 +694,7 @@ public class NotificationInfoTest extends SysuiTestCase { verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage( anyString(), eq(TEST_UID), updated.capture()); assertTrue(0 != (mNotificationChannel.getUserLockedFields() & USER_LOCKED_IMPORTANCE)); - assertEquals(IMPORTANCE_LOW, mNotificationChannel.getImportance()); + assertEquals(IMPORTANCE_DEFAULT, mNotificationChannel.getImportance()); } @Test @@ -993,8 +704,8 @@ public class NotificationInfoTest extends SysuiTestCase { TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, IMPORTANCE_DEFAULT, true); - mNotificationInfo.findViewById(R.id.int_silent_wrapper).performClick(); - mNotificationInfo.findViewById(R.id.done_button).performClick(); + mNotificationInfo.findViewById(R.id.silent_row).performClick(); + mNotificationInfo.findViewById(R.id.done).performClick(); mNotificationInfo.handleCloseControls(true, false); mTestableLooper.processAllMessages(); @@ -1014,8 +725,8 @@ public class NotificationInfoTest extends SysuiTestCase { TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, IMPORTANCE_DEFAULT, false); - mNotificationInfo.findViewById(R.id.int_alert_wrapper).performClick(); - mNotificationInfo.findViewById(R.id.done_button).performClick(); + mNotificationInfo.findViewById(R.id.alert_row).performClick(); + mNotificationInfo.findViewById(R.id.done).performClick(); mNotificationInfo.handleCloseControls(true, false); mTestableLooper.processAllMessages(); @@ -1036,8 +747,8 @@ public class NotificationInfoTest extends SysuiTestCase { TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, IMPORTANCE_DEFAULT, true); - mNotificationInfo.findViewById(R.id.int_silent_wrapper).performClick(); - mNotificationInfo.findViewById(R.id.done_button).performClick(); + mNotificationInfo.findViewById(R.id.silent_row).performClick(); + mNotificationInfo.findViewById(R.id.done).performClick(); mNotificationInfo.handleCloseControls(true, false); mTestableLooper.processAllMessages(); @@ -1058,8 +769,8 @@ public class NotificationInfoTest extends SysuiTestCase { TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, IMPORTANCE_LOW, false); - mNotificationInfo.findViewById(R.id.int_alert_wrapper).performClick(); - mNotificationInfo.findViewById(R.id.done_button).performClick(); + mNotificationInfo.findViewById(R.id.alert_row).performClick(); + mNotificationInfo.findViewById(R.id.done).performClick(); mNotificationInfo.handleCloseControls(true, false); mTestableLooper.processAllMessages(); @@ -1073,30 +784,14 @@ public class NotificationInfoTest extends SysuiTestCase { } @Test - public void testCloseControlsDoesNotUpdateMinIfSaveIsFalse() throws Exception { - mNotificationChannel.setImportance(IMPORTANCE_LOW); - mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true, - true, false, IMPORTANCE_DEFAULT, false); - - mNotificationInfo.findViewById(R.id.minimize).performClick(); - waitForUndoButton(); - mNotificationInfo.handleCloseControls(false, false); - - mTestableLooper.processAllMessages(); - verify(mMockINotificationManager, never()).updateNotificationChannelForPackage( - eq(TEST_PACKAGE_NAME), eq(TEST_UID), eq(mNotificationChannel)); - } - - @Test public void testCloseControlsDoesNotUpdateIfSaveIsFalse() throws Exception { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false, - IMPORTANCE_DEFAULT, false); + IMPORTANCE_LOW, false); - mNotificationInfo.findViewById(R.id.int_block_wrapper).performClick(); - mNotificationInfo.findViewById(R.id.done_button).performClick(); + mNotificationInfo.findViewById(R.id.alert_row).performClick(); + mNotificationInfo.findViewById(R.id.done).performClick(); mNotificationInfo.handleCloseControls(false, false); mTestableLooper.processAllMessages(); @@ -1105,33 +800,17 @@ public class NotificationInfoTest extends SysuiTestCase { } @Test - public void testBlockDoesNothingIfCheckSaveListenerIsNoOp() throws Exception { - mNotificationChannel.setImportance(IMPORTANCE_LOW); - mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, - (Runnable saveImportance, StatusBarNotification sbn) -> { - }, null, null, true, true, IMPORTANCE_DEFAULT, false); - - mNotificationInfo.findViewById(R.id.int_block_wrapper).performClick(); - mTestableLooper.processAllMessages(); - mNotificationInfo.handleCloseControls(true, false); - - verify(mMockINotificationManager, never()).updateNotificationChannelForPackage( - eq(TEST_PACKAGE_NAME), eq(TEST_UID), eq(mNotificationChannel)); - } - - @Test public void testCloseControlsUpdatesWhenCheckSaveListenerUsesCallback() throws Exception { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, (Runnable saveImportance, StatusBarNotification sbn) -> { saveImportance.run(); - }, null, null, true, false, IMPORTANCE_DEFAULT, false + }, null, null, true, false, IMPORTANCE_LOW, false ); - mNotificationInfo.findViewById(R.id.int_block_wrapper).performClick(); - mNotificationInfo.findViewById(R.id.done_button).performClick(); + mNotificationInfo.findViewById(R.id.alert_row).performClick(); + mNotificationInfo.findViewById(R.id.done).performClick(); mTestableLooper.processAllMessages(); verify(mMockINotificationManager, never()).updateNotificationChannelForPackage( eq(TEST_PACKAGE_NAME), eq(TEST_UID), eq(mNotificationChannel)); @@ -1147,18 +826,4 @@ public class NotificationInfoTest extends SysuiTestCase { public void testWillBeRemovedReturnsFalseBeforeBind() throws Exception { assertFalse(mNotificationInfo.willBeRemoved()); } - - @Test - public void testUndoText_min() throws Exception { - mSbn.getNotification().flags = Notification.FLAG_FOREGROUND_SERVICE; - mNotificationChannel.setImportance(IMPORTANCE_LOW); - mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true, - true, false, IMPORTANCE_DEFAULT, false); - - mNotificationInfo.findViewById(R.id.minimize).performClick(); - waitForUndoButton(); - TextView confirmationText = mNotificationInfo.findViewById(R.id.confirmation_text); - assertTrue(confirmationText.getText().toString().contains("minimized")); - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java index 1248cbbd335d..67ad37b2d29a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java @@ -45,7 +45,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.Collections; -import java.util.Set; +import java.util.List; @RunWith(AndroidTestingRunner.class) @RunWithLooper @@ -118,10 +118,10 @@ public class AutoTileManagerTest extends SysuiTestCase { verify(mQsTileHost, never()).addTile("night"); } - private static Set<CastDevice> buildFakeCastDevice(boolean isCasting) { + private static List<CastDevice> buildFakeCastDevice(boolean isCasting) { CastDevice cd = new CastDevice(); cd.state = isCasting ? CastDevice.STATE_CONNECTED : CastDevice.STATE_DISCONNECTED; - return Collections.singleton(cd); + return Collections.singletonList(cd); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/QuickStepControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/QuickStepControllerTest.java deleted file mode 100644 index 782983032923..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/QuickStepControllerTest.java +++ /dev/null @@ -1,696 +0,0 @@ -/* - * Copyright (C) 2018 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.systemui.statusbar.phone; - -import static android.view.WindowManagerPolicyConstants.NAV_BAR_BOTTOM; -import static android.view.WindowManagerPolicyConstants.NAV_BAR_LEFT; -import static android.view.WindowManagerPolicyConstants.NAV_BAR_RIGHT; - -import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_DEAD_ZONE; -import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_HOME; -import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_NONE; -import static com.android.systemui.statusbar.phone.NavigationBarView.WINDOW_TARGET_BOTTOM; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.anyFloat; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -import android.content.Context; -import android.content.res.Resources; -import android.testing.AndroidTestingRunner; -import android.testing.TestableLooper.RunWithLooper; -import android.view.MotionEvent; -import android.view.View; - -import androidx.test.filters.SmallTest; - -import com.android.systemui.R; -import com.android.systemui.SysuiTestCase; -import com.android.systemui.recents.OverviewProxyService; -import com.android.systemui.shared.recents.IOverviewProxy; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.MockitoAnnotations; - -/** atest QuickStepControllerTest */ -@RunWith(AndroidTestingRunner.class) -@RunWithLooper -@SmallTest -public class QuickStepControllerTest extends SysuiTestCase { - private static final int NAVBAR_WIDTH = 1000; - private static final int NAVBAR_HEIGHT = 300; - - private QuickStepController mController; - private NavigationBarView mNavigationBarView; - private StatusBar mStatusBar; - private OverviewProxyService mProxyService; - private IOverviewProxy mProxy; - private Resources mResources; - - @Before - public void setup() { - MockitoAnnotations.initMocks(this); - final ButtonDispatcher backButton = mock(ButtonDispatcher.class); - mResources = mock(Resources.class); - - mProxyService = mock(OverviewProxyService.class); - mProxy = mock(IOverviewProxy.Stub.class); - doReturn(mProxy).when(mProxyService).getProxy(); - doReturn(true).when(mProxyService).shouldShowSwipeUpUI(); - mDependency.injectTestDependency(OverviewProxyService.class, mProxyService); - - mStatusBar = mock(StatusBar.class); - doReturn(false).when(mStatusBar).isKeyguardShowing(); - mContext.putComponent(StatusBar.class, mStatusBar); - - mNavigationBarView = mock(NavigationBarView.class); - doReturn(false).when(mNavigationBarView).inScreenPinning(); - doReturn(true).when(mNavigationBarView).isNotificationsFullyCollapsed(); - doReturn(true).when(mNavigationBarView).isQuickScrubEnabled(); - doReturn(HIT_TARGET_NONE).when(mNavigationBarView).getDownHitTarget(); - doReturn(WINDOW_TARGET_BOTTOM).when(mNavigationBarView).getWindowTarget(); - doReturn(backButton).when(mNavigationBarView).getBackButton(); - doReturn(mResources).when(mNavigationBarView).getResources(); - doReturn(mContext).when(mNavigationBarView).getContext(); - - mController = new QuickStepController(mContext); - mController.setComponents(mNavigationBarView); - mController.setBarState(false /* isRTL */, NAV_BAR_BOTTOM); - } - - @Test - public void testNoActionsNoGestures() throws Exception { - MotionEvent ev = event(MotionEvent.ACTION_DOWN, 1, 1); - assertFalse(mController.onInterceptTouchEvent(ev)); - verify(mNavigationBarView, never()).requestUnbufferedDispatch(ev); - assertNull(mController.getCurrentAction()); - } - - @Test - public void testNoGesturesWhenSwipeUpDisabled() throws Exception { - doReturn(false).when(mProxyService).shouldShowSwipeUpUI(); - mController.setGestureActions(mockAction(true), null /* swipeDownAction */, - null /* swipeLeftAction */, null /* swipeRightAction */, null /* leftEdgeSwipe */, - null /* rightEdgeSwipe */); - - MotionEvent ev = event(MotionEvent.ACTION_DOWN, 1, 1); - assertFalse(mController.onInterceptTouchEvent(ev)); - verify(mNavigationBarView, never()).requestUnbufferedDispatch(ev); - assertNull(mController.getCurrentAction()); - } - - @Test - public void testHasActionDetectGesturesTouchdown() throws Exception { - MotionEvent ev = event(MotionEvent.ACTION_DOWN, 1, 1); - - // Add enabled gesture action - NavigationGestureAction action = mockAction(true); - mController.setGestureActions(action, null /* swipeDownAction */, - null /* swipeLeftAction */, null /* swipeRightAction */, null /* leftEdgeSwipe */, - null /* rightEdgeSwipe */); - - assertFalse(mController.onInterceptTouchEvent(ev)); - verify(mNavigationBarView, times(1)).requestUnbufferedDispatch(ev); - verify(action, times(1)).reset(); - verify(mProxy, times(1)).onPreMotionEvent(mNavigationBarView.getDownHitTarget()); - verify(mProxy, times(1)).onMotionEvent(ev); - assertNull(mController.getCurrentAction()); - } - - @Test - public void testProxyDisconnectedNoGestures() throws Exception { - MotionEvent ev = event(MotionEvent.ACTION_DOWN, 1, 1); - - // Add enabled gesture action - mController.setGestureActions(mockAction(true), null /* swipeDownAction */, - null /* swipeLeftAction */, null /* swipeRightAction */, null /* leftEdgeSwipe */, - null /* rightEdgeSwipe */); - - // Set the gesture on deadzone - doReturn(null).when(mProxyService).getProxy(); - - assertFalse(mController.onInterceptTouchEvent(ev)); - verify(mNavigationBarView, never()).requestUnbufferedDispatch(ev); - assertNull(mController.getCurrentAction()); - } - - @Test - public void testNoActionsNoGesturesOverDeadzone() throws Exception { - MotionEvent ev = event(MotionEvent.ACTION_DOWN, 1, 1); - - // Touched over deadzone - doReturn(HIT_TARGET_DEAD_ZONE).when(mNavigationBarView).getDownHitTarget(); - - assertTrue(mController.onInterceptTouchEvent(ev)); - verify(mNavigationBarView, never()).requestUnbufferedDispatch(ev); - assertNull(mController.getCurrentAction()); - } - - @Test - public void testOnTouchIgnoredDownEventAfterOnIntercept() { - mController.setGestureActions(mockAction(true), null /* swipeDownAction */, - null /* swipeLeftAction */, null /* swipeRightAction */, null /* leftEdgeSwipe */, - null /* rightEdgeSwipe */); - - MotionEvent ev = event(MotionEvent.ACTION_DOWN, 1, 1); - assertFalse(touch(ev)); - verify(mNavigationBarView, times(1)).requestUnbufferedDispatch(ev); - - // OnTouch event for down is ignored, so requestUnbufferedDispatch ran once from before - assertFalse(mNavigationBarView.onTouchEvent(ev)); - verify(mNavigationBarView, times(1)).requestUnbufferedDispatch(ev); - } - - @Test - public void testGesturesCallCorrectAction() throws Exception { - doReturn(NAVBAR_WIDTH).when(mNavigationBarView).getWidth(); - doReturn(NAVBAR_HEIGHT).when(mNavigationBarView).getHeight(); - - NavigationGestureAction swipeUp = mockAction(true); - NavigationGestureAction swipeDown = mockAction(true); - NavigationGestureAction swipeLeft = mockAction(true); - NavigationGestureAction swipeRight = mockAction(true); - mController.setGestureActions(swipeUp, swipeDown, swipeLeft, swipeRight, - null /* leftEdgeSwipe */, null /* rightEdgeSwipe */); - - // Swipe Up - assertGestureTriggersAction(swipeUp, 1, 100, 5, 1); - // Swipe Down - assertGestureTriggersAction(swipeDown, 1, 1, 5, 100); - // Swipe Left - assertGestureTriggersAction(swipeLeft, NAVBAR_WIDTH / 2, 1, 5, 1); - // Swipe Right - assertGestureTriggersAction(swipeRight, NAVBAR_WIDTH / 2, 1, NAVBAR_WIDTH, 5); - } - - @Test - public void testGesturesCallCorrectActionLandscape() throws Exception { - doReturn(NAVBAR_HEIGHT).when(mNavigationBarView).getWidth(); - doReturn(NAVBAR_WIDTH).when(mNavigationBarView).getHeight(); - - NavigationGestureAction swipeUp = mockAction(true); - NavigationGestureAction swipeDown = mockAction(true); - NavigationGestureAction swipeLeft = mockAction(true); - NavigationGestureAction swipeRight = mockAction(true); - mController.setGestureActions(swipeUp, swipeDown, swipeLeft, swipeRight, - null /* leftEdgeSwipe */, null /* rightEdgeSwipe */); - - // In landscape - mController.setBarState(false /* isRTL */, NAV_BAR_RIGHT); - - // Swipe Up - assertGestureTriggersAction(swipeRight, 1, 100, 5, 1); - // Swipe Down - assertGestureTriggersAction(swipeLeft, 1, NAVBAR_WIDTH / 2, 5, NAVBAR_WIDTH); - // Swipe Left - assertGestureTriggersAction(swipeUp, 100, 1, 5, 1); - // Swipe Right - assertGestureTriggersAction(swipeDown, 1, 1, 100, 5); - } - - @Test - public void testGesturesCallCorrectActionSeascape() throws Exception { - doReturn(NAVBAR_HEIGHT).when(mNavigationBarView).getWidth(); - doReturn(NAVBAR_WIDTH).when(mNavigationBarView).getHeight(); - - mController.setBarState(false /* isRTL */, NAV_BAR_LEFT); - NavigationGestureAction swipeUp = mockAction(true); - NavigationGestureAction swipeDown = mockAction(true); - NavigationGestureAction swipeLeft = mockAction(true); - NavigationGestureAction swipeRight = mockAction(true); - mController.setGestureActions(swipeUp, swipeDown, swipeLeft, swipeRight, - null /* leftEdgeSwipe */, null /* rightEdgeSwipe */); - - // Swipe Up - assertGestureTriggersAction(swipeLeft, 1, NAVBAR_WIDTH / 2, 5, 1); - // Swipe Down - assertGestureTriggersAction(swipeRight, 1, NAVBAR_WIDTH / 2, 5, NAVBAR_WIDTH); - // Swipe Left - assertGestureTriggersAction(swipeDown, 100, 1, 5, 1); - // Swipe Right - assertGestureTriggersAction(swipeUp, 1, 1, 100, 5); - } - - @Test - public void testGesturesCallCorrectActionRTL() throws Exception { - doReturn(NAVBAR_WIDTH).when(mNavigationBarView).getWidth(); - doReturn(NAVBAR_HEIGHT).when(mNavigationBarView).getHeight(); - mController.setBarState(true /* isRTL */, NAV_BAR_BOTTOM); - - // The swipe gestures below are for LTR, so RTL in portrait will be swapped - NavigationGestureAction swipeUp = mockAction(true); - NavigationGestureAction swipeDown = mockAction(true); - NavigationGestureAction swipeLeft = mockAction(true); - NavigationGestureAction swipeRight = mockAction(true); - mController.setGestureActions(swipeUp, swipeDown, swipeLeft, swipeRight, - null /* leftEdgeSwipe */, null /* rightEdgeSwipe */); - - // Swipe Up in RTL - assertGestureTriggersAction(swipeUp, 1, 100, 5, 1); - // Swipe Down in RTL - assertGestureTriggersAction(swipeDown, 1, 1, 5, 100); - // Swipe Left in RTL - assertGestureTriggersAction(swipeRight, NAVBAR_WIDTH / 2, 1, 5, 1); - // Swipe Right in RTL - assertGestureTriggersAction(swipeLeft, NAVBAR_WIDTH / 2, 1, NAVBAR_WIDTH, 0); - } - - @Test - public void testGesturesCallCorrectActionLandscapeRTL() throws Exception { - doReturn(NAVBAR_HEIGHT).when(mNavigationBarView).getWidth(); - doReturn(NAVBAR_WIDTH).when(mNavigationBarView).getHeight(); - mController.setBarState(true /* isRTL */, NAV_BAR_RIGHT); - - // The swipe gestures below are for LTR, so RTL in landscape will be swapped - NavigationGestureAction swipeUp = mockAction(true); - NavigationGestureAction swipeDown = mockAction(true); - NavigationGestureAction swipeLeft = mockAction(true); - NavigationGestureAction swipeRight = mockAction(true); - mController.setGestureActions(swipeUp, swipeDown, swipeLeft, swipeRight, - null /* leftEdgeSwipe */, null /* rightEdgeSwipe */); - - // Swipe Up - assertGestureTriggersAction(swipeLeft, 1, NAVBAR_WIDTH / 2, 5, 1); - // Swipe Down - assertGestureTriggersAction(swipeRight, 1, NAVBAR_WIDTH / 2, 5, NAVBAR_WIDTH); - // Swipe Left - assertGestureTriggersAction(swipeUp, 100, 1, 5, 1); - // Swipe Right - assertGestureTriggersAction(swipeDown, 1, 1, 100, 5); - } - - @Test - public void testGesturesCallCorrectActionSeascapeRTL() throws Exception { - doReturn(NAVBAR_HEIGHT).when(mNavigationBarView).getWidth(); - doReturn(NAVBAR_WIDTH).when(mNavigationBarView).getHeight(); - mController.setBarState(true /* isRTL */, NAV_BAR_LEFT); - - // The swipe gestures below are for LTR, so RTL in seascape will be swapped - NavigationGestureAction swipeUp = mockAction(true); - NavigationGestureAction swipeDown = mockAction(true); - NavigationGestureAction swipeLeft = mockAction(true); - NavigationGestureAction swipeRight = mockAction(true); - mController.setGestureActions(swipeUp, swipeDown, swipeLeft, swipeRight, - null /* leftEdgeSwipe */, null /* rightEdgeSwipe */); - - // Swipe Up - assertGestureTriggersAction(swipeRight, 1, NAVBAR_WIDTH / 2, 5, 1); - // Swipe Down - assertGestureTriggersAction(swipeLeft, 1, NAVBAR_WIDTH / 2, 5, NAVBAR_WIDTH); - // Swipe Left - assertGestureTriggersAction(swipeDown, 100, 1, 5, 1); - // Swipe Right - assertGestureTriggersAction(swipeUp, 1, 1, 100, 5); - } - - @Test - public void testActionPreventByPinnedState() throws Exception { - // Screen is pinned - doReturn(true).when(mNavigationBarView).inScreenPinning(); - - // Add enabled gesture action - NavigationGestureAction action = mockAction(true); - mController.setGestureActions(action, null /* swipeDownAction */, - null /* swipeLeftAction */, null /* swipeRightAction */, null /* leftEdgeSwipe */, - null /* rightEdgeSwipe */); - - // Touch down to begin swipe - MotionEvent downEvent = event(MotionEvent.ACTION_DOWN, 1, 100); - assertFalse(touch(downEvent)); - verify(mProxy, never()).onPreMotionEvent(mNavigationBarView.getDownHitTarget()); - verify(mProxy, never()).onMotionEvent(downEvent); - - // Move to start gesture, but pinned so it should not trigger action - MotionEvent moveEvent = event(MotionEvent.ACTION_MOVE, 1, 1); - assertFalse(touch(moveEvent)); - assertNull(mController.getCurrentAction()); - verify(mNavigationBarView, times(1)).showPinningEscapeToast(); - verify(action, never()).onGestureStart(moveEvent); - } - - @Test - public void testActionPreventedNotificationsShown() throws Exception { - NavigationGestureAction action = mockAction(true); - doReturn(false).when(action).canRunWhenNotificationsShowing(); - mController.setGestureActions(action, null /* swipeDownAction */, - null /* swipeLeftAction */, null /* swipeRightAction */, null /* leftEdgeSwipe */, - null /* rightEdgeSwipe */); - - // Show the notifications - doReturn(false).when(mNavigationBarView).isNotificationsFullyCollapsed(); - - // Swipe up - assertFalse(touch(MotionEvent.ACTION_DOWN, 1, 100)); - assertFalse(touch(MotionEvent.ACTION_MOVE, 1, 1)); - assertNull(mController.getCurrentAction()); - assertFalse(touch(MotionEvent.ACTION_UP, 1, 1)); - - // Hide the notifications - doReturn(true).when(mNavigationBarView).isNotificationsFullyCollapsed(); - - // Swipe up - assertFalse(touch(MotionEvent.ACTION_DOWN, 1, 100)); - assertTrue(touch(MotionEvent.ACTION_MOVE, 1, 1)); - assertEquals(action, mController.getCurrentAction()); - assertFalse(touch(MotionEvent.ACTION_UP, 1, 1)); - } - - @Test - public void testActionCannotPerform() throws Exception { - NavigationGestureAction action = mockAction(true); - mController.setGestureActions(action, null /* swipeDownAction */, - null /* swipeLeftAction */, null /* swipeRightAction */, null /* leftEdgeSwipe */, - null /* rightEdgeSwipe */); - - // Cannot perform action - doReturn(false).when(action).canPerformAction(); - - // Swipe up - assertFalse(touch(MotionEvent.ACTION_DOWN, 1, 100)); - assertFalse(touch(MotionEvent.ACTION_MOVE, 1, 1)); - assertNull(mController.getCurrentAction()); - assertFalse(touch(MotionEvent.ACTION_UP, 1, 1)); - - // Cannot perform action - doReturn(true).when(action).canPerformAction(); - - // Swipe up - assertFalse(touch(MotionEvent.ACTION_DOWN, 1, 100)); - assertTrue(touch(MotionEvent.ACTION_MOVE, 1, 1)); - assertEquals(action, mController.getCurrentAction()); - assertFalse(touch(MotionEvent.ACTION_UP, 1, 1)); - } - - @Test - public void testQuickScrub() throws Exception { - doReturn(NAVBAR_WIDTH).when(mNavigationBarView).getWidth(); - doReturn(NAVBAR_HEIGHT).when(mNavigationBarView).getHeight(); - QuickScrubAction action = spy(new QuickScrubAction(mNavigationBarView, mProxyService)); - mController.setGestureActions(null /* swipeUpAction */, null /* swipeDownAction */, - null /* swipeLeftAction */, action, null /* leftEdgeSwipe */, - null /* rightEdgeSwipe */); - int x = NAVBAR_WIDTH / 2; - int y = 20; - - // Set the layout and other padding to make sure the scrub fraction is calculated correctly - action.onLayout(true, 0, 0, NAVBAR_WIDTH, NAVBAR_HEIGHT); - doReturn(0).when(mNavigationBarView).getPaddingLeft(); - doReturn(0).when(mNavigationBarView).getPaddingRight(); - doReturn(0).when(mNavigationBarView).getPaddingStart(); - doReturn(0).when(mResources) - .getDimensionPixelSize(R.dimen.nav_quick_scrub_track_edge_padding); - - // Quickscrub disabled, so the action should be disabled - doReturn(false).when(mNavigationBarView).isQuickScrubEnabled(); - assertFalse(action.isEnabled()); - doReturn(true).when(mNavigationBarView).isQuickScrubEnabled(); - - // Touch down - MotionEvent downEvent = event(MotionEvent.ACTION_DOWN, x, y); - assertFalse(touch(downEvent)); - assertNull(mController.getCurrentAction()); - verify(mProxy, times(1)).onPreMotionEvent(mNavigationBarView.getDownHitTarget()); - verify(mProxy, times(1)).onMotionEvent(downEvent); - - // Move to start trigger action from gesture - MotionEvent moveEvent1 = event(MotionEvent.ACTION_MOVE, x + 100, y); - assertTrue(touch(moveEvent1)); - assertEquals(action, mController.getCurrentAction()); - verify(action, times(1)).onGestureStart(moveEvent1); - verify(mProxy, times(1)).onQuickScrubStart(); - verify(mProxyService, times(1)).notifyQuickScrubStarted(); - verify(mNavigationBarView, times(1)).updateSlippery(); - verify(mProxy, never()).onMotionEvent(moveEvent1); - - // Move again for scrub - float fraction = 3f / 4; - x = (int) (NAVBAR_WIDTH * fraction); - MotionEvent moveEvent2 = event(MotionEvent.ACTION_MOVE, x, y); - assertTrue(touch(moveEvent2)); - assertEquals(action, mController.getCurrentAction()); - verify(action, times(1)).onGestureMove(x, y); - verify(mProxy, times(1)).onQuickScrubProgress(fraction); - verify(mProxy, never()).onMotionEvent(moveEvent2); - - // Action up - MotionEvent upEvent = event(MotionEvent.ACTION_UP, 1, y); - assertFalse(touch(upEvent)); - assertNull(mController.getCurrentAction()); - verify(action, times(1)).onGestureEnd(); - verify(mProxy, times(1)).onQuickScrubEnd(); - verify(mProxy, never()).onMotionEvent(upEvent); - } - - @Test - public void testQuickStep() throws Exception { - QuickStepAction action = new QuickStepAction(mNavigationBarView, mProxyService); - mController.setGestureActions(action, null /* swipeDownAction */, - null /* swipeLeftAction */, null /* swipeRightAction */, null /* leftEdgeSwipe */, - null /* rightEdgeSwipe */); - - // Notifications are up, should prevent quickstep - doReturn(false).when(mNavigationBarView).isNotificationsFullyCollapsed(); - - // Swipe up - assertFalse(touch(MotionEvent.ACTION_DOWN, 1, 100)); - assertNull(mController.getCurrentAction()); - assertFalse(touch(MotionEvent.ACTION_MOVE, 1, 1)); - assertNull(mController.getCurrentAction()); - assertFalse(touch(MotionEvent.ACTION_UP, 1, 1)); - doReturn(true).when(mNavigationBarView).isNotificationsFullyCollapsed(); - - // Quickstep disabled, so the action should be disabled - doReturn(false).when(mNavigationBarView).isQuickStepSwipeUpEnabled(); - assertFalse(action.isEnabled()); - doReturn(true).when(mNavigationBarView).isQuickStepSwipeUpEnabled(); - - // Swipe up should call proxy events - MotionEvent downEvent = event(MotionEvent.ACTION_DOWN, 1, 100); - assertFalse(touch(downEvent)); - assertNull(mController.getCurrentAction()); - verify(mProxy, times(1)).onPreMotionEvent(mNavigationBarView.getDownHitTarget()); - verify(mProxy, times(1)).onMotionEvent(downEvent); - - MotionEvent moveEvent = event(MotionEvent.ACTION_MOVE, 1, 1); - assertTrue(touch(moveEvent)); - assertEquals(action, mController.getCurrentAction()); - verify(mProxy, times(1)).onQuickStep(moveEvent); - verify(mProxyService, times(1)).notifyQuickStepStarted(); - } - - @Test - public void testLongPressPreventDetection() throws Exception { - NavigationGestureAction action = mockAction(true); - mController.setGestureActions(action, null /* swipeDownAction */, - null /* swipeLeftAction */, null /* swipeRightAction */, null /* leftEdgeSwipe */, - null /* rightEdgeSwipe */); - - // Start the drag up - assertFalse(touch(MotionEvent.ACTION_DOWN, 100, 1)); - assertNull(mController.getCurrentAction()); - - // Long press something on the navigation bar such as Home button - mNavigationBarView.onNavigationButtonLongPress(mock(View.class)); - - // Swipe right will not start any gestures - MotionEvent motionMoveEvent = event(MotionEvent.ACTION_MOVE, 1, 1); - assertFalse(touch(motionMoveEvent)); - assertNull(mController.getCurrentAction()); - verify(action, never()).startGesture(motionMoveEvent); - - // Touch up - assertFalse(touch(MotionEvent.ACTION_UP, 1, 1)); - verify(action, never()).endGesture(); - } - - @Test - public void testHitTargetDragged() throws Exception { - ButtonDispatcher button = mock(ButtonDispatcher.class); - FakeLocationView buttonView = spy(new FakeLocationView(mContext, NAVBAR_WIDTH / 2, - NAVBAR_HEIGHT / 2)); - doReturn(buttonView).when(button).getCurrentView(); - - NavigationGestureAction action = mockAction(true); - mController.setGestureActions(action, action, action, action, action, action); - - // Setup getting the hit target - doReturn(HIT_TARGET_HOME).when(action).requiresTouchDownHitTarget(); - doReturn(true).when(action).allowHitTargetToMoveOverDrag(); - doReturn(HIT_TARGET_HOME).when(mNavigationBarView).getDownHitTarget(); - doReturn(button).when(mNavigationBarView).getHomeButton(); - doReturn(NAVBAR_WIDTH).when(mNavigationBarView).getWidth(); - doReturn(NAVBAR_HEIGHT).when(mNavigationBarView).getHeight(); - - // Portrait - assertGestureDragsHitTargetAllDirections(buttonView, false /* isRTL */, NAV_BAR_BOTTOM); - - // Portrait RTL - assertGestureDragsHitTargetAllDirections(buttonView, true /* isRTL */, NAV_BAR_BOTTOM); - - // Landscape - assertGestureDragsHitTargetAllDirections(buttonView, false /* isRTL */, NAV_BAR_RIGHT); - - // Landscape RTL - assertGestureDragsHitTargetAllDirections(buttonView, true /* isRTL */, NAV_BAR_RIGHT); - - // Seascape - assertGestureDragsHitTargetAllDirections(buttonView, false /* isRTL */, NAV_BAR_LEFT); - - // Seascape RTL - assertGestureDragsHitTargetAllDirections(buttonView, true /* isRTL */, NAV_BAR_LEFT); - } - - private void assertGestureDragsHitTargetAllDirections(View buttonView, boolean isRTL, - int navPos) { - mController.setBarState(isRTL, navPos); - - // Swipe up - assertGestureDragsHitTarget(buttonView, 10 /* x1 */, 200 /* y1 */, 0 /* x2 */, 0 /* y2 */, - 0 /* dx */, -1 /* dy */); - // Swipe left - assertGestureDragsHitTarget(buttonView, 200 /* x1 */, 10 /* y1 */, 0 /* x2 */, 0 /* y2 */, - -1 /* dx */, 0 /* dy */); - // Swipe right - assertGestureDragsHitTarget(buttonView, 0 /* x1 */, 0 /* y1 */, 200 /* x2 */, 10 /* y2 */, - 1 /* dx */, 0 /* dy */); - // Swipe down - assertGestureDragsHitTarget(buttonView, 0 /* x1 */, 0 /* y1 */, 10 /* x2 */, 200 /* y2 */, - 0 /* dx */, 1 /* dy */); - } - - /** - * Asserts the gesture actually moves the hit target - * @param buttonView button to check if moved, use Mockito.spy on a real object - * @param x1 start x - * @param x2 start y - * @param y1 end x - * @param y2 end y - * @param dx diff in x, if not 0, its sign determines direction, value does not matter - * @param dy diff in y, if not 0, its sign determines direction, value does not matter - */ - private void assertGestureDragsHitTarget(View buttonView, int x1, int y1, int x2, int y2, - int dx, int dy) { - ArgumentCaptor<Float> captor = ArgumentCaptor.forClass(Float.class); - assertFalse(touch(MotionEvent.ACTION_DOWN, x1, y1)); - assertTrue(touch(MotionEvent.ACTION_MOVE, x2, y2)); - - // Verify positions of the button drag - if (dx == 0) { - verify(buttonView, never()).setTranslationX(anyFloat()); - } else { - verify(buttonView).setTranslationX(captor.capture()); - if (dx < 0) { - assertTrue("Button should have moved left", (float) captor.getValue() < 0); - } else { - assertTrue("Button should have moved right", (float) captor.getValue() > 0); - } - } - if (dy == 0) { - verify(buttonView, never()).setTranslationY(anyFloat()); - } else { - verify(buttonView).setTranslationY(captor.capture()); - if (dy < 0) { - assertTrue("Button should have moved up", (float) captor.getValue() < 0); - } else { - assertTrue("Button should have moved down", (float) captor.getValue() > 0); - } - } - - // Touch up - assertFalse(touch(MotionEvent.ACTION_UP, x2, y2)); - verify(buttonView, times(1)).animate(); - - // Reset button state - reset(buttonView); - } - - private MotionEvent event(int action, float x, float y) { - final MotionEvent event = mock(MotionEvent.class); - doReturn(x).when(event).getX(); - doReturn(y).when(event).getY(); - doReturn(action & MotionEvent.ACTION_MASK).when(event).getActionMasked(); - doReturn(action).when(event).getAction(); - return event; - } - - private boolean touch(int action, float x, float y) { - return touch(event(action, x, y)); - } - - private boolean touch(MotionEvent event) { - return mController.onInterceptTouchEvent(event); - } - - private NavigationGestureAction mockAction(boolean enabled) { - final NavigationGestureAction action = mock(NavigationGestureAction.class); - doReturn(enabled).when(action).isEnabled(); - doReturn(HIT_TARGET_NONE).when(action).requiresTouchDownHitTarget(); - doReturn(true).when(action).canPerformAction(); - return action; - } - - private void assertGestureTriggersAction(NavigationGestureAction action, int x1, int y1, - int x2, int y2) { - // Start the drag - assertFalse(touch(MotionEvent.ACTION_DOWN, x1, y1)); - assertNull(mController.getCurrentAction()); - - // Swipe - MotionEvent motionMoveEvent = event(MotionEvent.ACTION_MOVE, x2, y2); - assertTrue(touch(motionMoveEvent)); - assertEquals(action, mController.getCurrentAction()); - verify(action, times(1)).startGesture(motionMoveEvent); - - // Move again - assertTrue(touch(MotionEvent.ACTION_MOVE, x2, y2)); - verify(action, times(1)).onGestureMove(x2, y2); - - // Touch up - assertFalse(touch(MotionEvent.ACTION_UP, x2, y2)); - assertNull(mController.getCurrentAction()); - verify(action, times(1)).endGesture(); - } - - static class FakeLocationView extends View { - private final int mX; - private final int mY; - - public FakeLocationView(Context context, int x, int y) { - super(context); - mX = x; - mY = y; - } - - @Override - public void getLocationInWindow(int[] outLocation) { - outLocation[0] = mX; - outLocation[1] = mY; - } - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java index 41e82cbeac36..51fb47bd049c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java @@ -152,7 +152,6 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { bubbleSbn.getNotification().contentIntent = mContentIntent; bubbleSbn.getNotification().flags |= Notification.FLAG_AUTO_CANCEL; // Do what BubbleController's NotificationEntryListener#onPendingEntryAdded does: - mBubbleNotificationRow.getEntry().setIsBubble(true); mBubbleNotificationRow.getEntry().setShowInShadeWhenBubble(true); mActiveNotifications = new ArrayList<>(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java index cd9069aa1233..3edfb56fbaac 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java @@ -95,7 +95,7 @@ public class SmartReplyConstantsTest extends SysuiTestCase { triggerConstantsOnChange(); assertEquals(false, mConstants.requiresTargetingP()); - overrideSetting(SystemUiDeviceConfigFlags.SSIN_REQUIRES_TARGETING_P, ""); + overrideSetting(SystemUiDeviceConfigFlags.SSIN_REQUIRES_TARGETING_P, null); triggerConstantsOnChange(); assertEquals(true, mConstants.requiresTargetingP()); } @@ -223,21 +223,21 @@ public class SmartReplyConstantsTest extends SysuiTestCase { private void resetAllDeviceConfigFlags() { DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, - SystemUiDeviceConfigFlags.SSIN_ENABLED, "", false /* makeDefault */); + SystemUiDeviceConfigFlags.SSIN_ENABLED, null, false /* makeDefault */); DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, - SystemUiDeviceConfigFlags.SSIN_REQUIRES_TARGETING_P, "", false /* makeDefault */); + SystemUiDeviceConfigFlags.SSIN_REQUIRES_TARGETING_P, null, false /* makeDefault */); DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, - SystemUiDeviceConfigFlags.SSIN_MAX_SQUEEZE_REMEASURE_ATTEMPTS, "", + SystemUiDeviceConfigFlags.SSIN_MAX_SQUEEZE_REMEASURE_ATTEMPTS, null, false /* makeDefault */); DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, - SystemUiDeviceConfigFlags.SSIN_EDIT_CHOICES_BEFORE_SENDING, "", + SystemUiDeviceConfigFlags.SSIN_EDIT_CHOICES_BEFORE_SENDING, null, false /* makeDefault */); DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, - SystemUiDeviceConfigFlags.SSIN_SHOW_IN_HEADS_UP, "", false /* makeDefault */); + SystemUiDeviceConfigFlags.SSIN_SHOW_IN_HEADS_UP, null, false /* makeDefault */); DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, - SystemUiDeviceConfigFlags.SSIN_MIN_NUM_SYSTEM_GENERATED_REPLIES, "", + SystemUiDeviceConfigFlags.SSIN_MIN_NUM_SYSTEM_GENERATED_REPLIES, null, false /* makeDefault */); DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, - SystemUiDeviceConfigFlags.SSIN_MAX_NUM_ACTIONS, "", false /* makeDefault */); + SystemUiDeviceConfigFlags.SSIN_MAX_NUM_ACTIONS, null, false /* makeDefault */); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeCastController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeCastController.java index 51149abe792d..f6b24da9b821 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeCastController.java +++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeCastController.java @@ -19,7 +19,8 @@ import android.testing.LeakCheck; import com.android.systemui.statusbar.policy.CastController; import com.android.systemui.statusbar.policy.CastController.Callback; -import java.util.Set; +import java.util.ArrayList; +import java.util.List; public class FakeCastController extends BaseLeakChecker<Callback> implements CastController { public FakeCastController(LeakCheck test) { @@ -37,8 +38,8 @@ public class FakeCastController extends BaseLeakChecker<Callback> implements Cas } @Override - public Set<CastDevice> getCastDevices() { - return null; + public List<CastDevice> getCastDevices() { + return new ArrayList<>(); } @Override diff --git a/packages/overlays/NavigationBarModeGesturalOverlay/res/values/config.xml b/packages/overlays/NavigationBarModeGesturalOverlay/res/values/config.xml index 23d2e7babdcc..704ff2ee2aa3 100644 --- a/packages/overlays/NavigationBarModeGesturalOverlay/res/values/config.xml +++ b/packages/overlays/NavigationBarModeGesturalOverlay/res/values/config.xml @@ -31,6 +31,6 @@ <bool name="config_navBarTapThrough">true</bool> <!-- Controls the size of the back gesture inset. --> - <dimen name="config_backGestureInset">48dp</dimen> + <dimen name="config_backGestureInset">20dp</dimen> </resources> diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java index 7106664d0699..79afe8e4de72 100644 --- a/services/backup/java/com/android/server/backup/BackupManagerService.java +++ b/services/backup/java/com/android/server/backup/BackupManagerService.java @@ -31,11 +31,14 @@ import android.app.backup.ISelectBackupTransportCallback; import android.app.job.JobParameters; import android.app.job.JobScheduler; import android.app.job.JobService; +import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.PackageManager; import android.os.Binder; +import android.os.FileUtils; import android.os.HandlerThread; import android.os.IBinder; import android.os.ParcelFileDescriptor; @@ -49,6 +52,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.server.SystemConfig; import com.android.server.SystemService; +import java.io.File; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.Collections; @@ -86,6 +90,18 @@ public class BackupManagerService { private Set<ComponentName> mTransportWhitelist; + private final BroadcastReceiver mUserRemovedReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (Intent.ACTION_USER_REMOVED.equals(intent.getAction())) { + int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL); + if (userId > 0) { // for only non system users + onRemovedNonSystemUser(userId); + } + } + } + }; + /** Instantiate a new instance of {@link BackupManagerService}. */ public BackupManagerService( Context context, Trampoline trampoline, HandlerThread backupThread) { @@ -99,6 +115,23 @@ public class BackupManagerService { if (mTransportWhitelist == null) { mTransportWhitelist = Collections.emptySet(); } + + mContext.registerReceiver(mUserRemovedReceiver, + new IntentFilter(Intent.ACTION_USER_REMOVED)); + } + + /** + * Remove backup state for non system {@code userId} when the user is removed from the device. + * For non system users, backup state is stored in both the user's own dir and the system dir. + * When the user is removed, the user's own dir gets removed by the OS. This method ensures that + * the part of the user backup state which is in the system dir also gets removed. + */ + private void onRemovedNonSystemUser(int userId) { + Slog.i(TAG, "Removing state for non system user " + userId); + File dir = UserBackupManagerFiles.getStateDirInSystemDir(userId); + if (!FileUtils.deleteContentsAndDir(dir)) { + Slog.w(TAG, "Failed to delete state dir for removed user: " + userId); + } } /** diff --git a/services/backup/java/com/android/server/backup/Trampoline.java b/services/backup/java/com/android/server/backup/Trampoline.java index bb0aba0c2e88..a9b292b37903 100644 --- a/services/backup/java/com/android/server/backup/Trampoline.java +++ b/services/backup/java/com/android/server/backup/Trampoline.java @@ -47,7 +47,6 @@ import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.DumpUtils; -import com.android.server.backup.utils.FileUtils; import com.android.server.backup.utils.RandomAccessFileUtils; import java.io.File; @@ -95,7 +94,7 @@ public class Trampoline extends IBackupManager.Stub { * Name of file for non-system users that remembers whether backup was explicitly activated or * deactivated with a call to setBackupServiceActive. */ - private static final String REMEMBER_ACTIVATED_FILENAME_PREFIX = "backup-remember-activated"; + private static final String REMEMBER_ACTIVATED_FILENAME = "backup-remember-activated"; // Product-level suppression of backup/restore. private static final String BACKUP_DISABLE_PROPERTY = "ro.backup.disable"; @@ -143,8 +142,7 @@ public class Trampoline extends IBackupManager.Stub { /** Stored in the system user's directory and the file is indexed by the user it refers to. */ protected File getRememberActivatedFileForNonSystemUser(int userId) { - return FileUtils.createNewFile(UserBackupManagerFiles.getStateFileInSystemDir( - REMEMBER_ACTIVATED_FILENAME_PREFIX, userId)); + return UserBackupManagerFiles.getStateFileInSystemDir(REMEMBER_ACTIVATED_FILENAME, userId); } /** Stored in the system user's directory and the file is indexed by the user it refers to. */ @@ -336,8 +334,13 @@ public class Trampoline extends IBackupManager.Stub { // action since we need to remember that a permissioned call was made irrespective of // whether the call changes the state or not. if (userId != UserHandle.USER_SYSTEM) { - RandomAccessFileUtils.writeBoolean(getRememberActivatedFileForNonSystemUser(userId), - makeActive); + try { + File rememberFile = getRememberActivatedFileForNonSystemUser(userId); + createFile(rememberFile); + RandomAccessFileUtils.writeBoolean(rememberFile, makeActive); + } catch (IOException e) { + Slog.e(TAG, "Unable to persist backup service activity", e); + } } if (mGlobalDisable) { diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerFiles.java b/services/backup/java/com/android/server/backup/UserBackupManagerFiles.java index 4638ac63de4a..8e605426d0c9 100644 --- a/services/backup/java/com/android/server/backup/UserBackupManagerFiles.java +++ b/services/backup/java/com/android/server/backup/UserBackupManagerFiles.java @@ -49,8 +49,13 @@ final class UserBackupManagerFiles { return new File(Environment.getDownloadCacheDirectory(), BACKUP_STAGING_DIR); } - /** Stored in the system user's directory and the file is indexed by the user it refers to. */ - static File getStateFileInSystemDir(String prefix, int userId) { - return new File(getBaseStateDir(UserHandle.USER_SYSTEM), prefix + "-" + userId); + /** A user specific dir within the system user's directory. */ + static File getStateDirInSystemDir(int userId) { + return new File(getBaseStateDir(UserHandle.USER_SYSTEM), "" + userId); + } + + /** Stored in a user specific dir within the system user's directory. */ + static File getStateFileInSystemDir(String filename, int userId) { + return new File(getStateDirInSystemDir(userId), filename); } } diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index a7404bc63e2a..a3e7d3685100 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -164,6 +164,20 @@ public class CompanionDeviceManagerService extends SystemService implements Bind } @Override + public void onUnlockUser(int userHandle) { + Set<Association> associations = readAllAssociations(userHandle); + Set<String> companionAppPackages = new HashSet<>(); + for (Association association : associations) { + companionAppPackages.add(association.companionAppPackage); + } + ActivityTaskManagerInternal atmInternal = LocalServices.getService( + ActivityTaskManagerInternal.class); + if (atmInternal != null) { + atmInternal.setCompanionAppPackages(userHandle, companionAppPackages); + } + } + + @Override public void binderDied() { Handler.getMain().post(this::cleanup); } diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 22a0f18e8389..640525411239 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -6382,6 +6382,11 @@ public class ConnectivityService extends IConnectivityManager.Stub Slog.wtf(TAG, networkAgent.name() + " connected with null LinkProperties"); } + // NetworkCapabilities need to be set before sending the private DNS config to + // NetworkMonitor, otherwise NetworkMonitor cannot determine if validation is required. + synchronized (networkAgent) { + networkAgent.setNetworkCapabilities(networkAgent.networkCapabilities); + } handlePerNetworkPrivateDnsConfig(networkAgent, mDnsManager.getPrivateDnsConfig()); updateLinkProperties(networkAgent, new LinkProperties(networkAgent.linkProperties), null); diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java index d1ae284512d2..3dc8af1c06c5 100644 --- a/services/core/java/com/android/server/NetworkManagementService.java +++ b/services/core/java/com/android/server/NetworkManagementService.java @@ -89,10 +89,10 @@ import android.util.StatsLog; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IBatteryStats; -import com.android.internal.net.NetworkStatsFactory; import com.android.internal.util.DumpUtils; import com.android.internal.util.HexDump; import com.android.internal.util.Preconditions; +import com.android.server.net.NetworkStatsFactory; import com.google.android.collect.Maps; diff --git a/services/core/java/com/android/server/ServiceWatcher.java b/services/core/java/com/android/server/ServiceWatcher.java index a5a515f93e75..e3dc3b7a9848 100644 --- a/services/core/java/com/android/server/ServiceWatcher.java +++ b/services/core/java/com/android/server/ServiceWatcher.java @@ -382,7 +382,13 @@ public class ServiceWatcher implements ServiceConnection { /** * Runs the given function synchronously if currently connected, and returns the default value * if not currently connected or if any exception is thrown. + * + * @deprecated Using this function is an indication that your AIDL API is broken. Calls from + * system server to outside MUST be one-way, and so cannot return any result, and this + * method should not be needed or used. Use a separate callback interface to allow outside + * components to return results back to the system server. */ + @Deprecated public final <T> T runOnBinderBlocking(BlockingBinderRunner<T> runner, T defaultValue) { try { return runOnHandlerBlocking(() -> { diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 4598d3ef7f4b..307919223a2b 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -929,13 +929,20 @@ class StorageManagerService extends IStorageManager.Stub private void initIfBootedAndConnected() { Slog.d(TAG, "Thinking about init, mBootCompleted=" + mBootCompleted + ", mDaemonConnected=" + mDaemonConnected); - if (mBootCompleted && mDaemonConnected - && !StorageManager.isFileEncryptedNativeOnly()) { - // When booting a device without native support, make sure that our - // user directories are locked or unlocked based on the current - // emulation status. - final boolean initLocked = StorageManager.isFileEncryptedEmulatedOnly(); - Slog.d(TAG, "Setting up emulation state, initlocked=" + initLocked); + if (mBootCompleted && mDaemonConnected) { + // Tell vold to lock or unlock the user directories based on the + // current file-based encryption status. + final boolean initLocked; + if (StorageManager.isFileEncryptedNativeOrEmulated()) { + // For native FBE this is a no-op after reboot, but this is + // still needed in case of framework restarts. + Slog.d(TAG, "FBE is enabled; ensuring all user directories are locked."); + initLocked = true; + } else { + // This is in case FBE emulation was turned off. + Slog.d(TAG, "FBE is disabled; ensuring the FBE emulation state is cleared."); + initLocked = false; + } final List<UserInfo> users = mContext.getSystemService(UserManager.class).getUsers(); for (UserInfo user : users) { try { @@ -3840,7 +3847,9 @@ class StorageManagerService extends IStorageManager.Stub uid, packageName, READ_EXTERNAL_STORAGE, OP_READ_EXTERNAL_STORAGE); final boolean hasWrite = StorageManager.checkPermissionAndAppOp(mContext, false, 0, uid, packageName, WRITE_EXTERNAL_STORAGE, OP_WRITE_EXTERNAL_STORAGE); - final boolean hasStorage = hasRead || hasWrite; + // STOPSHIP: remove this temporary hack once we have dynamic runtime + // permissions fully enabled again + final boolean hasStorage = hasRead || hasWrite || true; // We're only willing to give out broad access if they also hold // runtime permission; this is a firm CDD requirement diff --git a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java index e42666c6a637..7ab70fad70d4 100644 --- a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java +++ b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java @@ -49,6 +49,7 @@ class ActivityManagerDebugConfig { static final boolean DEBUG_BROADCAST_BACKGROUND = DEBUG_BROADCAST || false; static final boolean DEBUG_BROADCAST_LIGHT = DEBUG_BROADCAST || false; static final boolean DEBUG_BROADCAST_DEFERRAL = DEBUG_BROADCAST || false; + static final boolean DEBUG_COMPACTION = DEBUG_ALL || false; static final boolean DEBUG_LRU = DEBUG_ALL || false; static final boolean DEBUG_MU = DEBUG_ALL || false; static final boolean DEBUG_NETWORK = DEBUG_ALL || false; diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index a5932ccc1a5c..1757c9816355 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -213,6 +213,7 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageManagerInternal; import android.content.pm.PackageManagerInternal.CheckPermissionDelegate; +import android.content.pm.PackageParser; import android.content.pm.ParceledListSlice; import android.content.pm.PathPermission; import android.content.pm.PermissionInfo; @@ -18474,7 +18475,9 @@ public class ActivityManagerService extends IActivityManager.Stub void updateApplicationInfoLocked(@NonNull List<String> packagesToUpdate, int userId) { final boolean updateFrameworkRes = packagesToUpdate.contains("android"); - + if (updateFrameworkRes) { + PackageParser.readConfigUseRoundIcon(null); + } mProcessList.updateApplicationInfoLocked(packagesToUpdate, userId, updateFrameworkRes); if (updateFrameworkRes) { diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index 8a462dabd14c..d1379b669563 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -16,6 +16,7 @@ package com.android.server.am; +import static android.app.ActivityManagerInternal.ALLOW_NON_FULL; import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.app.ActivityTaskManager.RESIZE_MODE_SYSTEM; import static android.app.ActivityTaskManager.RESIZE_MODE_USER; @@ -427,8 +428,12 @@ final class ActivityManagerShellCommand extends ShellCommand { if (intent.getComponent() != null) { packageName = intent.getComponent().getPackageName(); } else { + // queryIntentActivities does not convert user id, so we convert it here first + int userIdForQuery = mInternal.mUserController.handleIncomingUser( + Binder.getCallingPid(), Binder.getCallingUid(), mUserId, false, + ALLOW_NON_FULL, "ActivityManagerShellCommand", null); List<ResolveInfo> activities = mPm.queryIntentActivities(intent, mimeType, 0, - mUserId).getList(); + userIdForQuery).getList(); if (activities == null || activities.size() <= 0) { getErrPrintWriter().println("Error: Intent does not match any activities: " + intent); diff --git a/services/core/java/com/android/server/am/AppCompactor.java b/services/core/java/com/android/server/am/AppCompactor.java index f58fb95b1ec9..043dc8d00081 100644 --- a/services/core/java/com/android/server/am/AppCompactor.java +++ b/services/core/java/com/android/server/am/AppCompactor.java @@ -18,6 +18,9 @@ package com.android.server.am; import static android.os.Process.THREAD_PRIORITY_FOREGROUND; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_COMPACTION; +import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; + import android.app.ActivityManager; import android.app.ActivityThread; import android.os.Debug; @@ -30,6 +33,7 @@ import android.provider.DeviceConfig; import android.provider.DeviceConfig.OnPropertyChangedListener; import android.text.TextUtils; import android.util.EventLog; +import android.util.Slog; import android.util.StatsLog; import com.android.internal.annotations.GuardedBy; @@ -39,7 +43,12 @@ import com.android.server.ServiceThread; import java.io.FileOutputStream; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.Map; import java.util.Random; +import java.util.Set; public final class AppCompactor { @@ -55,6 +64,12 @@ public final class AppCompactor { @VisibleForTesting static final String KEY_COMPACT_THROTTLE_6 = "compact_throttle_6"; @VisibleForTesting static final String KEY_COMPACT_STATSD_SAMPLE_RATE = "compact_statsd_sample_rate"; + @VisibleForTesting static final String KEY_COMPACT_FULL_RSS_THROTTLE_KB = + "compact_full_rss_throttle_kb"; + @VisibleForTesting static final String KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB = + "compact_full_delta_rss_throttle_kb"; + @VisibleForTesting static final String KEY_COMPACT_PROC_STATE_THROTTLE = + "compact_proc_state_throttle"; // Phenotype sends int configurations and we map them to the strings we'll use on device, // preventing a weird string value entering the kernel. @@ -79,6 +94,11 @@ public final class AppCompactor { @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_6 = 10 * 60 * 1000; // The sampling rate to push app compaction events into statsd for upload. @VisibleForTesting static final float DEFAULT_STATSD_SAMPLE_RATE = 0.1f; + @VisibleForTesting static final long DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB = 12_000L; + @VisibleForTesting static final long DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB = 8_000L; + // Format of this string should be a comma separated list of integers. + @VisibleForTesting static final String DEFAULT_COMPACT_PROC_STATE_THROTTLE = + String.valueOf(ActivityManager.PROCESS_STATE_RECEIVER); @VisibleForTesting interface PropertyChangedCallbackForTest { @@ -123,6 +143,12 @@ public final class AppCompactor { updateCompactionThrottles(); } else if (KEY_COMPACT_STATSD_SAMPLE_RATE.equals(name)) { updateStatsdSampleRate(); + } else if (KEY_COMPACT_FULL_RSS_THROTTLE_KB.equals(name)) { + updateFullRssThrottle(); + } else if (KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB.equals(name)) { + updateFullDeltaRssThrottle(); + } else if (KEY_COMPACT_PROC_STATE_THROTTLE.equals(name)) { + updateProcStateThrottle(); } } if (mTestCallback != null) { @@ -154,18 +180,42 @@ public final class AppCompactor { @VisibleForTesting volatile long mCompactThrottlePersistent = DEFAULT_COMPACT_THROTTLE_6; @GuardedBy("mPhenotypeFlagLock") private volatile boolean mUseCompaction = DEFAULT_USE_COMPACTION; - private final Random mRandom = new Random(); @GuardedBy("mPhenotypeFlagLock") @VisibleForTesting volatile float mStatsdSampleRate = DEFAULT_STATSD_SAMPLE_RATE; + @GuardedBy("mPhenotypeFlagLock") + @VisibleForTesting volatile long mFullAnonRssThrottleKb = + DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB; + @GuardedBy("mPhenoypeFlagLock") + @VisibleForTesting volatile long mFullDeltaRssThrottleKb = + DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB; + @GuardedBy("mPhenoypeFlagLock") + @VisibleForTesting final Set<Integer> mProcStateThrottle; // Handler on which compaction runs. private Handler mCompactionHandler; + // Maps process ID to last compaction statistics for processes that we've fully compacted. Used + // when evaluating throttles that we only consider for "full" compaction, so we don't store + // data for "some" compactions. + private Map<Integer, LastCompactionStats> mLastCompactionStats = + new LinkedHashMap<Integer, LastCompactionStats>() { + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + return size() > 100; + } + }; + + private int mSomeCompactionCount; + private int mFullCompactionCount; + private int mPersistentCompactionCount; + private int mBfgsCompactionCount; + public AppCompactor(ActivityManagerService am) { mAm = am; mCompactionThread = new ServiceThread("CompactionThread", THREAD_PRIORITY_FOREGROUND, true); + mProcStateThrottle = new HashSet<>(); } @VisibleForTesting @@ -186,10 +236,12 @@ public final class AppCompactor { updateCompactionActions(); updateCompactionThrottles(); updateStatsdSampleRate(); + updateFullRssThrottle(); + updateFullDeltaRssThrottle(); + updateProcStateThrottle(); } Process.setThreadGroupAndCpuset(mCompactionThread.getThreadId(), Process.THREAD_GROUP_SYSTEM); - } /** @@ -212,7 +264,33 @@ public final class AppCompactor { pw.println(" " + KEY_COMPACT_THROTTLE_2 + "=" + mCompactThrottleSomeFull); pw.println(" " + KEY_COMPACT_THROTTLE_3 + "=" + mCompactThrottleFullSome); pw.println(" " + KEY_COMPACT_THROTTLE_4 + "=" + mCompactThrottleFullFull); + pw.println(" " + KEY_COMPACT_THROTTLE_5 + "=" + mCompactThrottleBFGS); + pw.println(" " + KEY_COMPACT_THROTTLE_6 + "=" + mCompactThrottlePersistent); pw.println(" " + KEY_COMPACT_STATSD_SAMPLE_RATE + "=" + mStatsdSampleRate); + pw.println(" " + KEY_COMPACT_FULL_RSS_THROTTLE_KB + "=" + + mFullAnonRssThrottleKb); + pw.println(" " + KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB + "=" + + mFullDeltaRssThrottleKb); + pw.println(" " + KEY_COMPACT_PROC_STATE_THROTTLE + "=" + + Arrays.toString(mProcStateThrottle.toArray(new Integer[0]))); + + pw.println(" " + mSomeCompactionCount + " some, " + mFullCompactionCount + + " full, " + mPersistentCompactionCount + " persistent, " + + mBfgsCompactionCount + " BFGS compactions."); + + if (mLastCompactionStats != null) { + pw.println(" Tracking last compaction stats for " + mLastCompactionStats.size() + + " processes."); + if (DEBUG_COMPACTION) { + for (Map.Entry<Integer, LastCompactionStats> entry + : mLastCompactionStats.entrySet()) { + int pid = entry.getKey(); + LastCompactionStats stats = entry.getValue(); + pw.println(" " + pid + ": " + + Arrays.toString(stats.getRssAfterCompaction())); + } + } + } } } @@ -277,7 +355,7 @@ public final class AppCompactor { /** * Reads the flag value from DeviceConfig to determine whether app compaction - * should be enabled, and starts/stops the compaction thread as needed. + * should be enabled, and starts the compaction thread if needed. */ @GuardedBy("mPhenotypeFlagLock") private void updateUseCompaction() { @@ -360,6 +438,58 @@ public final class AppCompactor { mStatsdSampleRate = Math.min(1.0f, Math.max(0.0f, mStatsdSampleRate)); } + @GuardedBy("mPhenotypeFlagLock") + private void updateFullRssThrottle() { + mFullAnonRssThrottleKb = DeviceConfig.getLong(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + KEY_COMPACT_FULL_RSS_THROTTLE_KB, DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB); + + // Don't allow negative values. 0 means don't apply the throttle. + if (mFullAnonRssThrottleKb < 0) { + mFullAnonRssThrottleKb = DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB; + } + } + + @GuardedBy("mPhenotypeFlagLock") + private void updateFullDeltaRssThrottle() { + mFullDeltaRssThrottleKb = DeviceConfig.getLong(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB, DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB); + + if (mFullDeltaRssThrottleKb < 0) { + mFullDeltaRssThrottleKb = DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB; + } + } + + @GuardedBy("mPhenotypeFlagLock") + private void updateProcStateThrottle() { + String procStateThrottleString = DeviceConfig.getString( + DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, KEY_COMPACT_PROC_STATE_THROTTLE, + DEFAULT_COMPACT_PROC_STATE_THROTTLE); + if (!parseProcStateThrottle(procStateThrottleString)) { + Slog.w(TAG_AM, "Unable to parse app compact proc state throttle \"" + + procStateThrottleString + "\" falling back to default."); + if (!parseProcStateThrottle(DEFAULT_COMPACT_PROC_STATE_THROTTLE)) { + Slog.wtf(TAG_AM, + "Unable to parse default app compact proc state throttle " + + DEFAULT_COMPACT_PROC_STATE_THROTTLE); + } + } + } + + private boolean parseProcStateThrottle(String procStateThrottleString) { + String[] procStates = TextUtils.split(procStateThrottleString, ","); + mProcStateThrottle.clear(); + for (String procState : procStates) { + try { + mProcStateThrottle.add(Integer.parseInt(procState)); + } catch (NumberFormatException e) { + Slog.e(TAG_AM, "Failed to parse default app compaction proc state: " + + procState); + return false; + } + } + return true; + } + @VisibleForTesting static String compactActionIntToString(int action) { switch(action) { @@ -376,6 +506,22 @@ public final class AppCompactor { } } + @VisibleForTesting static String procStateListToString(Integer... processStates) { + return Arrays.toString(processStates); + } + + private static final class LastCompactionStats { + private final long[] mRssAfterCompaction; + + LastCompactionStats(long[] rss) { + mRssAfterCompaction = rss; + } + + long[] getRssAfterCompaction() { + return mRssAfterCompaction; + } + } + private final class MemCompactionHandler extends Handler { private MemCompactionHandler() { super(mCompactionThread.getLooper()); @@ -392,24 +538,34 @@ public final class AppCompactor { final String name; int pendingAction, lastCompactAction; long lastCompactTime; + LastCompactionStats lastCompactionStats; + int lastOomAdj = msg.arg1; + int procState = msg.arg2; synchronized (mAm) { proc = mPendingCompactionProcesses.remove(0); pendingAction = proc.reqCompactAction; + pid = proc.pid; + name = proc.processName; // don't compact if the process has returned to perceptible // and this is only a cached/home/prev compaction if ((pendingAction == COMPACT_PROCESS_SOME || pendingAction == COMPACT_PROCESS_FULL) && (proc.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ)) { + if (DEBUG_COMPACTION) { + Slog.d(TAG_AM, + "Skipping compaction as process " + name + " is " + + "now perceptible."); + } return; } - pid = proc.pid; - name = proc.processName; - lastCompactAction = proc.lastCompactAction; lastCompactTime = proc.lastCompactTime; + // remove rather than get so that insertion order will be updated when we + // put the post-compaction stats back into the map. + lastCompactionStats = mLastCompactionStats.remove(pid); } if (pid == 0) { @@ -431,6 +587,12 @@ public final class AppCompactor { || (lastCompactAction == COMPACT_PROCESS_FULL && (start - lastCompactTime < mCompactThrottleSomeFull))) { + if (DEBUG_COMPACTION) { + Slog.d(TAG_AM, "Skipping some compaction for " + name + + ": too soon. throttle=" + mCompactThrottleSomeSome + + "/" + mCompactThrottleSomeFull + " last=" + + (start - lastCompactTime) + "ms ago"); + } return; } } else if (pendingAction == COMPACT_PROCESS_FULL) { @@ -439,18 +601,35 @@ public final class AppCompactor { || (lastCompactAction == COMPACT_PROCESS_FULL && (start - lastCompactTime < mCompactThrottleFullFull))) { + if (DEBUG_COMPACTION) { + Slog.d(TAG_AM, "Skipping full compaction for " + name + + ": too soon. throttle=" + mCompactThrottleFullSome + + "/" + mCompactThrottleFullFull + " last=" + + (start - lastCompactTime) + "ms ago"); + } return; } } else if (pendingAction == COMPACT_PROCESS_PERSISTENT) { if (start - lastCompactTime < mCompactThrottlePersistent) { + if (DEBUG_COMPACTION) { + Slog.d(TAG_AM, "Skipping persistent compaction for " + name + + ": too soon. throttle=" + mCompactThrottlePersistent + + " last=" + (start - lastCompactTime) + "ms ago"); + } return; } } else if (pendingAction == COMPACT_PROCESS_BFGS) { if (start - lastCompactTime < mCompactThrottleBFGS) { + if (DEBUG_COMPACTION) { + Slog.d(TAG_AM, "Skipping bfgs compaction for " + name + + ": too soon. throttle=" + mCompactThrottleBFGS + + " last=" + (start - lastCompactTime) + "ms ago"); + } return; } } } + switch (pendingAction) { case COMPACT_PROCESS_SOME: action = mCompactActionSome; @@ -470,12 +649,77 @@ public final class AppCompactor { return; } + if (mProcStateThrottle.contains(procState)) { + if (DEBUG_COMPACTION) { + Slog.d(TAG_AM, "Skipping full compaction for process " + name + + "; proc state is " + procState); + } + return; + } + + long[] rssBefore = Process.getRss(pid); + long anonRssBefore = rssBefore[2]; + + if (rssBefore[0] == 0 && rssBefore[1] == 0 && rssBefore[2] == 0 + && rssBefore[3] == 0) { + if (DEBUG_COMPACTION) { + Slog.d(TAG_AM, "Skipping compaction for" + "process " + pid + + " with no memory usage. Dead?"); + } + return; + } + + if (action.equals(COMPACT_ACTION_FULL) || action.equals(COMPACT_ACTION_ANON)) { + if (mFullAnonRssThrottleKb > 0L + && anonRssBefore < mFullAnonRssThrottleKb) { + if (DEBUG_COMPACTION) { + Slog.d(TAG_AM, "Skipping full compaction for process " + + name + "; anon RSS is too small: " + anonRssBefore + + "KB."); + } + return; + } + + if (lastCompactionStats != null && mFullDeltaRssThrottleKb > 0L) { + long[] lastRss = lastCompactionStats.getRssAfterCompaction(); + long absDelta = Math.abs(rssBefore[1] - lastRss[1]) + + Math.abs(rssBefore[2] - lastRss[2]) + + Math.abs(rssBefore[3] - lastRss[3]); + if (absDelta <= mFullDeltaRssThrottleKb) { + if (DEBUG_COMPACTION) { + Slog.d(TAG_AM, "Skipping full compaction for process " + + name + "; abs delta is too small: " + absDelta + + "KB."); + } + return; + } + } + } + + // Now we've passed through all the throttles and are going to compact, update + // bookkeeping. + switch (pendingAction) { + case COMPACT_PROCESS_SOME: + mSomeCompactionCount++; + break; + case COMPACT_PROCESS_FULL: + mFullCompactionCount++; + break; + case COMPACT_PROCESS_PERSISTENT: + mPersistentCompactionCount++; + break; + case COMPACT_PROCESS_BFGS: + mBfgsCompactionCount++; + break; + default: + break; + } + try { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Compact " + ((pendingAction == COMPACT_PROCESS_SOME) ? "some" : "full") + ": " + name); long zramFreeKbBefore = Debug.getZramFreeKb(); - long[] rssBefore = Process.getRss(pid); FileOutputStream fos = new FileOutputStream("/proc/" + pid + "/reclaim"); fos.write(action.getBytes()); fos.close(); @@ -485,9 +729,11 @@ public final class AppCompactor { long zramFreeKbAfter = Debug.getZramFreeKb(); EventLog.writeEvent(EventLogTags.AM_COMPACT, pid, name, action, rssBefore[0], rssBefore[1], rssBefore[2], rssBefore[3], - rssAfter[0], rssAfter[1], rssAfter[2], rssAfter[3], time, - lastCompactAction, lastCompactTime, msg.arg1, msg.arg2, - zramFreeKbBefore, zramFreeKbAfter); + rssAfter[0] - rssBefore[0], rssAfter[1] - rssBefore[1], + rssAfter[2] - rssBefore[2], rssAfter[3] - rssBefore[3], time, + lastCompactAction, lastCompactTime, lastOomAdj, procState, + zramFreeKbBefore, zramFreeKbAfter - zramFreeKbBefore); + // Note that as above not taking mPhenoTypeFlagLock here to avoid locking // on every single compaction for a flag that will seldom change and the // impact of reading the wrong value here is low. @@ -495,17 +741,23 @@ public final class AppCompactor { StatsLog.write(StatsLog.APP_COMPACTED, pid, name, pendingAction, rssBefore[0], rssBefore[1], rssBefore[2], rssBefore[3], rssAfter[0], rssAfter[1], rssAfter[2], rssAfter[3], time, - lastCompactAction, lastCompactTime, msg.arg1, - ActivityManager.processStateAmToProto(msg.arg2), + lastCompactAction, lastCompactTime, lastOomAdj, + ActivityManager.processStateAmToProto(procState), zramFreeKbBefore, zramFreeKbAfter); } + synchronized (mAm) { proc.lastCompactTime = end; proc.lastCompactAction = pendingAction; } - Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + + if (action.equals(COMPACT_ACTION_FULL) + || action.equals(COMPACT_ACTION_ANON)) { + mLastCompactionStats.put(pid, new LastCompactionStats(rssAfter)); + } } catch (Exception e) { // nothing to do, presumably the process died + } finally { Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); } break; diff --git a/services/core/java/com/android/server/am/EventLogTags.logtags b/services/core/java/com/android/server/am/EventLogTags.logtags index 4b12e43692a6..e65a4e50198b 100644 --- a/services/core/java/com/android/server/am/EventLogTags.logtags +++ b/services/core/java/com/android/server/am/EventLogTags.logtags @@ -138,4 +138,4 @@ option java_package com.android.server.am 30061 am_remove_task (Task ID|1|5), (Stack ID|1|5) # The task is being compacted -30063 am_compact (Pid|1|5),(Process Name|3),(Action|3),(BeforeRssTotal|2|2),(BeforeRssFile|2|2),(BeforeRssAnon|2|2),(BeforeRssSwap|2|2),(AfterRssTotal|2|2),(AfterRssFile|2|2),(AfterRssAnon|2|2),(AfterRssSwap|2|2),(Time|2|3),(LastAction|1|2),(LastActionTimestamp|2|3),(setAdj|1|2),(procState|1|2),(BeforeZRAMFree|2|2),(AfterZRAMFree|2|2) +30063 am_compact (Pid|1|5),(Process Name|3),(Action|3),(BeforeRssTotal|2|2),(BeforeRssFile|2|2),(BeforeRssAnon|2|2),(BeforeRssSwap|2|2),(DeltaRssTotal|2|2),(DeltaRssFile|2|2),(DeltaRssAnon|2|2),(DeltaRssSwap|2|2),(Time|2|3),(LastAction|1|2),(LastActionTimestamp|2|3),(setAdj|1|2),(procState|1|2),(BeforeZRAMFree|2|2),(DeltaZRAMFree|2|2) diff --git a/services/core/java/com/android/server/attention/AttentionManagerService.java b/services/core/java/com/android/server/attention/AttentionManagerService.java index 1681c5bc61d3..bc78d1ad751f 100644 --- a/services/core/java/com/android/server/attention/AttentionManagerService.java +++ b/services/core/java/com/android/server/attention/AttentionManagerService.java @@ -19,6 +19,7 @@ package com.android.server.attention; import static android.provider.DeviceConfig.NAMESPACE_ATTENTION_MANAGER_SERVICE; import static android.provider.Settings.System.ADAPTIVE_SLEEP; import static android.service.attention.AttentionService.ATTENTION_FAILURE_CANCELLED; +import static android.service.attention.AttentionService.ATTENTION_FAILURE_UNKNOWN; import android.Manifest; import android.annotation.NonNull; @@ -249,6 +250,7 @@ public class AttentionManagerService extends SystemService { if (userState.mPendingAttentionCheck != null && userState.mPendingAttentionCheck.mCallbackInternal.equals( callbackInternal)) { + userState.mPendingAttentionCheck.cancel(ATTENTION_FAILURE_UNKNOWN); userState.mPendingAttentionCheck = null; } return; @@ -624,7 +626,7 @@ public class AttentionManagerService extends SystemService { if (userState == null) { return; } - cancel(userState, AttentionService.ATTENTION_FAILURE_UNKNOWN); + cancel(userState, ATTENTION_FAILURE_UNKNOWN); mContext.unbindService(userState.mConnection); userState.mConnection.cleanupService(); diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java index 5f624ba9be9d..7750bfefae05 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -207,7 +207,7 @@ public final class AudioDeviceInventory { mDeviceBroker.setDeviceVolume( streamState, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP); } - makeA2dpDeviceAvailable(address, btDevice.getName(), + makeA2dpDeviceAvailable(address, BtHelper.getName(btDevice), "onSetA2dpSinkConnectionState", a2dpCodec); } } @@ -257,7 +257,7 @@ public final class AudioDeviceInventory { if (isConnected && state != BluetoothProfile.STATE_CONNECTED) { makeHearingAidDeviceUnavailable(address); } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) { - makeHearingAidDeviceAvailable(address, btDevice.getName(), + makeHearingAidDeviceAvailable(address, BtHelper.getName(btDevice), "onSetHearingAidConnectionState"); } } @@ -318,7 +318,7 @@ public final class AudioDeviceInventory { } } if (AudioSystem.handleDeviceConfigChange(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address, - btDevice.getName(), a2dpCodec) != AudioSystem.AUDIO_STATUS_OK) { + BtHelper.getName(btDevice), a2dpCodec) != AudioSystem.AUDIO_STATUS_OK) { int musicDevice = mDeviceBroker.getDeviceForStream(AudioSystem.STREAM_MUSIC); // force A2DP device disconnection in case of error so that AudioService state is // consistent with audio policy manager state @@ -603,10 +603,9 @@ public final class AudioDeviceInventory { } // A2DP device exists, handle active device change final String existingDevicekey = mConnectedDevices.keyAt(i); - final String deviceName = device.getName(); mConnectedDevices.remove(existingDevicekey); mConnectedDevices.put(deviceKey, new DeviceInfo( - AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, deviceName, + AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, BtHelper.getName(device), address, a2dpCodec)); mDeviceBroker.postA2dpActiveDeviceChange( new BtHelper.BluetoothA2dpDeviceInfo( diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java index 522a55d8ea1e..2d9156b8782e 100644 --- a/services/core/java/com/android/server/audio/BtHelper.java +++ b/services/core/java/com/android/server/audio/BtHelper.java @@ -152,6 +152,14 @@ public class BtHelper { } } + /*package*/ @NonNull static String getName(@NonNull BluetoothDevice device) { + final String deviceName = device.getName(); + if (deviceName == null) { + return ""; + } + return deviceName; + } + //---------------------------------------------------------------------- // Interface for AudioDeviceBroker @@ -515,7 +523,7 @@ public class BtHelper { if (!BluetoothAdapter.checkBluetoothAddress(address)) { address = ""; } - String btDeviceName = btDevice.getName(); + String btDeviceName = getName(btDevice); boolean result = false; if (isActive) { result |= mDeviceBroker.handleDeviceConnection( diff --git a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java index c60dd6ca130a..d8e7b7db7b75 100644 --- a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java +++ b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java @@ -657,6 +657,7 @@ public abstract class BiometricServiceBase extends SystemService Slog.e(getTag(), "HAL died"); mMetricsLogger.count(getMetrics().tagHalDied(), 1); mHALDeathCount++; + mCurrentUserId = UserHandle.USER_NULL; handleError(getHalDeviceId(), BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE, 0 /*vendorCode */); diff --git a/services/core/java/com/android/server/biometrics/face/FaceService.java b/services/core/java/com/android/server/biometrics/face/FaceService.java index fe762c06458a..c573bbb2e1f0 100644 --- a/services/core/java/com/android/server/biometrics/face/FaceService.java +++ b/services/core/java/com/android/server/biometrics/face/FaceService.java @@ -46,9 +46,9 @@ import android.os.RemoteException; import android.os.SELinux; import android.os.UserHandle; import android.os.UserManager; -import android.service.restricted_image.RestrictedImagesDumpProto; import android.service.restricted_image.RestrictedImageProto; import android.service.restricted_image.RestrictedImageSetProto; +import android.service.restricted_image.RestrictedImagesDumpProto; import android.util.Slog; import android.util.proto.ProtoOutputStream; @@ -383,6 +383,12 @@ public class FaceService extends BiometricServiceBase { @Override // Binder call public void resetLockout(byte[] token) { checkPermission(MANAGE_BIOMETRIC); + + if (!FaceService.this.hasEnrolledBiometrics(mCurrentUserId)) { + Slog.w(TAG, "Ignoring lockout reset, no templates enrolled"); + return; + } + try { mDaemonWrapper.resetLockout(token); } catch (RemoteException e) { @@ -391,61 +397,62 @@ public class FaceService extends BiometricServiceBase { } @Override - public boolean setFeature(int feature, boolean enabled, final byte[] token) { + public void setFeature(int feature, boolean enabled, final byte[] token, + IFaceServiceReceiver receiver) { checkPermission(MANAGE_BIOMETRIC); - if (!FaceService.this.hasEnrolledBiometrics(mCurrentUserId)) { - Slog.e(TAG, "No enrolled biometrics while setting feature: " + feature); - return false; - } + mHandler.post(() -> { + if (!FaceService.this.hasEnrolledBiometrics(mCurrentUserId)) { + Slog.e(TAG, "No enrolled biometrics while setting feature: " + feature); + return; + } - final ArrayList<Byte> byteToken = new ArrayList<>(); - for (int i = 0; i < token.length; i++) { - byteToken.add(token[i]); - } + final ArrayList<Byte> byteToken = new ArrayList<>(); + for (int i = 0; i < token.length; i++) { + byteToken.add(token[i]); + } - // TODO: Support multiple faces - final int faceId = getFirstTemplateForUser(mCurrentUserId); + // TODO: Support multiple faces + final int faceId = getFirstTemplateForUser(mCurrentUserId); - if (mDaemon != null) { - try { - return mDaemon.setFeature(feature, enabled, byteToken, faceId) == Status.OK; - } catch (RemoteException e) { - Slog.e(getTag(), "Unable to set feature: " + feature + " to enabled:" + enabled, - e); + if (mDaemon != null) { + try { + final int result = mDaemon.setFeature(feature, enabled, byteToken, faceId); + receiver.onFeatureSet(result == Status.OK, feature); + } catch (RemoteException e) { + Slog.e(getTag(), "Unable to set feature: " + feature + + " to enabled:" + enabled, e); + } } - } - return false; + }); + } @Override - public boolean getFeature(int feature) { + public void getFeature(int feature, IFaceServiceReceiver receiver) { checkPermission(MANAGE_BIOMETRIC); - // This should ideally return tri-state, but the user isn't shown settings unless - // they are enrolled so it's fine for now. - if (!FaceService.this.hasEnrolledBiometrics(mCurrentUserId)) { - Slog.e(TAG, "No enrolled biometrics while getting feature: " + feature); - return false; - } + mHandler.post(() -> { + // This should ideally return tri-state, but the user isn't shown settings unless + // they are enrolled so it's fine for now. + if (!FaceService.this.hasEnrolledBiometrics(mCurrentUserId)) { + Slog.e(TAG, "No enrolled biometrics while getting feature: " + feature); + return; + } - // TODO: Support multiple faces - final int faceId = getFirstTemplateForUser(mCurrentUserId); + // TODO: Support multiple faces + final int faceId = getFirstTemplateForUser(mCurrentUserId); - if (mDaemon != null) { - try { - OptionalBool result = mDaemon.getFeature(feature, faceId); - if (result.status == Status.OK) { - return result.value; - } else { - // Same tri-state comment applies here. - return false; + if (mDaemon != null) { + try { + OptionalBool result = mDaemon.getFeature(feature, faceId); + receiver.onFeatureGet(result.status == Status.OK, feature, result.value); + } catch (RemoteException e) { + Slog.e(getTag(), "Unable to getRequireAttention", e); } - } catch (RemoteException e) { - Slog.e(getTag(), "Unable to getRequireAttention", e); } - } - return false; + }); + } @Override diff --git a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java index 164468e99967..3d9a47be56ea 100644 --- a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java @@ -419,6 +419,12 @@ public class FingerprintService extends BiometricServiceBase { @Override // Binder call public void resetTimeout(byte [] token) { checkPermission(RESET_FINGERPRINT_LOCKOUT); + + if (!FingerprintService.this.hasEnrolledBiometrics(mCurrentUserId)) { + Slog.w(TAG, "Ignoring lockout reset, no templates enrolled"); + return; + } + // TODO: confirm security token when we move timeout management into the HAL layer. mHandler.post(mResetFailedAttemptsForCurrentUserRunnable); } diff --git a/services/core/java/com/android/server/broadcastradio/hal2/AnnouncementAggregator.java b/services/core/java/com/android/server/broadcastradio/hal2/AnnouncementAggregator.java index 0bbaf25aaa12..53076975849b 100644 --- a/services/core/java/com/android/server/broadcastradio/hal2/AnnouncementAggregator.java +++ b/services/core/java/com/android/server/broadcastradio/hal2/AnnouncementAggregator.java @@ -90,7 +90,11 @@ public class AnnouncementAggregator extends ICloseHandle.Stub { for (ModuleWatcher watcher : mModuleWatchers) { combined.addAll(watcher.currentList); } - TunerCallback.dispatch(() -> mListener.onListUpdated(combined)); + try { + mListener.onListUpdated(combined); + } catch (RemoteException ex) { + Slog.e(TAG, "mListener.onListUpdated() failed: ", ex); + } } } diff --git a/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java b/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java index 2df89821611c..5e79c5943d7b 100644 --- a/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java +++ b/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java @@ -54,13 +54,6 @@ public class BroadcastRadioService { @GuardedBy("mLock") private final Map<Integer, RadioModule> mModules = new HashMap<>(); - // Map from module ID to TunerSession created by openSession(). - // - // Because this service currently implements a 1 AIDL to 1 HAL policy, mTunerSessions is used to - // enforce the "aggresive open" policy mandated for IBroadcastRadio.openSession(). In the - // future, this solution will be replaced with a multiple-AIDL to 1 HAL implementation. - private final Map<Integer, TunerSession> mTunerSessions = new HashMap<>(); - private IServiceNotification.Stub mServiceListener = new IServiceNotification.Stub() { @Override public void onRegistration(String fqName, String serviceName, boolean preexisting) { @@ -81,8 +74,10 @@ public class BroadcastRadioService { } Slog.v(TAG, "loaded broadcast radio module " + moduleId + ": " + serviceName + " (HAL 2.0)"); - closeTunerSessionLocked(moduleId); - mModules.put(moduleId, module); + RadioModule prevModule = mModules.put(moduleId, module); + if (prevModule != null) { + prevModule.closeSessions(RadioTuner.ERROR_HARDWARE_FAILURE); + } if (newService) { mServiceNameToModuleIdMap.put(serviceName, moduleId); @@ -105,8 +100,10 @@ public class BroadcastRadioService { Slog.v(TAG, "serviceDied(" + cookie + ")"); synchronized (mLock) { int moduleId = (int) cookie; - mModules.remove(moduleId); - closeTunerSessionLocked(moduleId); + RadioModule prevModule = mModules.remove(moduleId); + if (prevModule != null) { + prevModule.closeSessions(RadioTuner.ERROR_HARDWARE_FAILURE); + } for (Map.Entry<String, Integer> entry : mServiceNameToModuleIdMap.entrySet()) { if (entry.getValue() == moduleId) { @@ -166,13 +163,9 @@ public class BroadcastRadioService { if (module == null) { throw new IllegalArgumentException("Invalid module ID"); } - closeTunerSessionLocked(moduleId); } TunerSession tunerSession = module.openSession(callback); - synchronized (mLock) { - mTunerSessions.put(moduleId, tunerSession); - } if (legacyConfig != null) { tunerSession.setConfiguration(legacyConfig); } @@ -198,12 +191,4 @@ public class BroadcastRadioService { } return aggregator; } - - private void closeTunerSessionLocked(int moduleId) { - TunerSession tunerSession = mTunerSessions.remove(moduleId); - if (tunerSession != null) { - Slog.d(TAG, "Closing previous TunerSession"); - tunerSession.close(RadioTuner.ERROR_HARDWARE_FAILURE); - } - } } diff --git a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java index 832f8e1c9205..acb0207ff11f 100644 --- a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java +++ b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java @@ -26,16 +26,26 @@ import android.hardware.broadcastradio.V2_0.DabTableEntry; import android.hardware.broadcastradio.V2_0.IAnnouncementListener; import android.hardware.broadcastradio.V2_0.IBroadcastRadio; import android.hardware.broadcastradio.V2_0.ICloseHandle; +import android.hardware.broadcastradio.V2_0.ITunerCallback; import android.hardware.broadcastradio.V2_0.ITunerSession; +import android.hardware.broadcastradio.V2_0.ProgramInfo; +import android.hardware.broadcastradio.V2_0.ProgramListChunk; +import android.hardware.broadcastradio.V2_0.ProgramSelector; import android.hardware.broadcastradio.V2_0.Result; +import android.hardware.broadcastradio.V2_0.VendorKeyValue; import android.hardware.radio.RadioManager; +import android.os.DeadObjectException; import android.os.RemoteException; import android.util.MutableInt; import android.util.Slog; +import com.android.internal.annotations.GuardedBy; + import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Objects; +import java.util.Set; import java.util.stream.Collectors; class RadioModule { @@ -44,8 +54,63 @@ class RadioModule { @NonNull private final IBroadcastRadio mService; @NonNull public final RadioManager.ModuleProperties mProperties; + private final Object mLock = new Object(); + + @GuardedBy("mLock") + private ITunerSession mHalTunerSession; + + // Tracks antenna state reported by HAL (if any). + @GuardedBy("mLock") + private Boolean mAntennaConnected = null; + + @GuardedBy("mLock") + private RadioManager.ProgramInfo mProgramInfo = null; + + // Callback registered with the HAL to relay callbacks to AIDL clients. + private final ITunerCallback mHalTunerCallback = new ITunerCallback.Stub() { + @Override + public void onTuneFailed(int result, ProgramSelector programSelector) { + fanoutAidlCallback(cb -> cb.onTuneFailed(result, Convert.programSelectorFromHal( + programSelector))); + } + + @Override + public void onCurrentProgramInfoChanged(ProgramInfo halProgramInfo) { + RadioManager.ProgramInfo programInfo = Convert.programInfoFromHal(halProgramInfo); + synchronized (mLock) { + mProgramInfo = programInfo; + fanoutAidlCallbackLocked(cb -> cb.onCurrentProgramInfoChanged(programInfo)); + } + } + + @Override + public void onProgramListUpdated(ProgramListChunk programListChunk) { + // TODO: Cache per-AIDL client filters, send union of filters to HAL, use filters to fan + // back out to clients. + fanoutAidlCallback(cb -> cb.onProgramListUpdated(Convert.programListChunkFromHal( + programListChunk))); + } + + @Override + public void onAntennaStateChange(boolean connected) { + synchronized (mLock) { + mAntennaConnected = connected; + fanoutAidlCallbackLocked(cb -> cb.onAntennaState(connected)); + } + } + + @Override + public void onParametersUpdated(ArrayList<VendorKeyValue> parameters) { + fanoutAidlCallback(cb -> cb.onParametersUpdated(Convert.vendorInfoFromHal(parameters))); + } + }; + + // Collection of active AIDL tuner sessions created through openSession(). + @GuardedBy("mLock") + private final Set<TunerSession> mAidlTunerSessions = new HashSet<>(); + private RadioModule(@NonNull IBroadcastRadio service, - @NonNull RadioManager.ModuleProperties properties) { + @NonNull RadioManager.ModuleProperties properties) throws RemoteException { mProperties = Objects.requireNonNull(properties); mService = Objects.requireNonNull(service); } @@ -81,21 +146,85 @@ class RadioModule { public @NonNull TunerSession openSession(@NonNull android.hardware.radio.ITunerCallback userCb) throws RemoteException { - TunerCallback cb = new TunerCallback(Objects.requireNonNull(userCb)); - Mutable<ITunerSession> hwSession = new Mutable<>(); - MutableInt halResult = new MutableInt(Result.UNKNOWN_ERROR); + synchronized (mLock) { + if (mHalTunerSession == null) { + Mutable<ITunerSession> hwSession = new Mutable<>(); + mService.openSession(mHalTunerCallback, (result, session) -> { + Convert.throwOnError("openSession", result); + hwSession.value = session; + }); + mHalTunerSession = Objects.requireNonNull(hwSession.value); + } + TunerSession tunerSession = new TunerSession(this, mHalTunerSession, userCb); + mAidlTunerSessions.add(tunerSession); - synchronized (mService) { - mService.openSession(cb, (result, session) -> { - hwSession.value = session; - halResult.value = result; - }); + // Propagate state to new client. Note: These callbacks are invoked while holding mLock + // to prevent race conditions with new callbacks from the HAL. + if (mAntennaConnected != null) { + userCb.onAntennaState(mAntennaConnected); + } + if (mProgramInfo != null) { + userCb.onCurrentProgramInfoChanged(mProgramInfo); + } + + return tunerSession; + } + } + + public void closeSessions(Integer error) { + // Copy the contents of mAidlTunerSessions into a local array because TunerSession.close() + // must be called without mAidlTunerSessions locked because it can call + // onTunerSessionClosed(). + TunerSession[] tunerSessions; + synchronized (mLock) { + tunerSessions = new TunerSession[mAidlTunerSessions.size()]; + mAidlTunerSessions.toArray(tunerSessions); + mAidlTunerSessions.clear(); + } + for (TunerSession tunerSession : tunerSessions) { + tunerSession.close(error); + } + } + + void onTunerSessionClosed(TunerSession tunerSession) { + synchronized (mLock) { + mAidlTunerSessions.remove(tunerSession); + if (mAidlTunerSessions.isEmpty() && mHalTunerSession != null) { + Slog.v(TAG, "closing HAL tuner session"); + try { + mHalTunerSession.close(); + } catch (RemoteException ex) { + Slog.e(TAG, "mHalTunerSession.close() failed: ", ex); + } + mHalTunerSession = null; + } } + } - Convert.throwOnError("openSession", halResult.value); - Objects.requireNonNull(hwSession.value); + interface AidlCallbackRunnable { + void run(android.hardware.radio.ITunerCallback callback) throws RemoteException; + } - return new TunerSession(this, hwSession.value, cb); + // Invokes runnable with each TunerSession currently open. + void fanoutAidlCallback(AidlCallbackRunnable runnable) { + synchronized (mLock) { + fanoutAidlCallbackLocked(runnable); + } + } + + private void fanoutAidlCallbackLocked(AidlCallbackRunnable runnable) { + for (TunerSession tunerSession : mAidlTunerSessions) { + try { + runnable.run(tunerSession.mCallback); + } catch (DeadObjectException ex) { + // The other side died without calling close(), so just purge it from our + // records. + Slog.e(TAG, "Removing dead TunerSession"); + mAidlTunerSessions.remove(tunerSession); + } catch (RemoteException ex) { + Slog.e(TAG, "Failed to invoke ITunerCallback: ", ex); + } + } } public android.hardware.radio.ICloseHandle addAnnouncementListener(@NonNull int[] enabledTypes, diff --git a/services/core/java/com/android/server/broadcastradio/hal2/TunerCallback.java b/services/core/java/com/android/server/broadcastradio/hal2/TunerCallback.java deleted file mode 100644 index 3c4b49c91cf2..000000000000 --- a/services/core/java/com/android/server/broadcastradio/hal2/TunerCallback.java +++ /dev/null @@ -1,76 +0,0 @@ -/** - * 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.broadcastradio.hal2; - -import android.annotation.NonNull; -import android.hardware.broadcastradio.V2_0.ITunerCallback; -import android.hardware.broadcastradio.V2_0.ProgramInfo; -import android.hardware.broadcastradio.V2_0.ProgramListChunk; -import android.hardware.broadcastradio.V2_0.ProgramSelector; -import android.hardware.broadcastradio.V2_0.VendorKeyValue; -import android.os.RemoteException; -import android.util.Slog; - -import java.util.ArrayList; -import java.util.Objects; - -class TunerCallback extends ITunerCallback.Stub { - private static final String TAG = "BcRadio2Srv.cb"; - - final android.hardware.radio.ITunerCallback mClientCb; - - interface RunnableThrowingRemoteException { - void run() throws RemoteException; - } - - TunerCallback(@NonNull android.hardware.radio.ITunerCallback clientCallback) { - mClientCb = Objects.requireNonNull(clientCallback); - } - - static void dispatch(RunnableThrowingRemoteException func) { - try { - func.run(); - } catch (RemoteException ex) { - Slog.e(TAG, "callback call failed", ex); - } - } - - @Override - public void onTuneFailed(int result, ProgramSelector selector) { - dispatch(() -> mClientCb.onTuneFailed(result, Convert.programSelectorFromHal(selector))); - } - - @Override - public void onCurrentProgramInfoChanged(ProgramInfo info) { - dispatch(() -> mClientCb.onCurrentProgramInfoChanged(Convert.programInfoFromHal(info))); - } - - @Override - public void onProgramListUpdated(ProgramListChunk chunk) { - dispatch(() -> mClientCb.onProgramListUpdated(Convert.programListChunkFromHal(chunk))); - } - - @Override - public void onAntennaStateChange(boolean connected) { - dispatch(() -> mClientCb.onAntennaState(connected)); - } - - @Override - public void onParametersUpdated(ArrayList<VendorKeyValue> parameters) { - dispatch(() -> mClientCb.onParametersUpdated(Convert.vendorInfoFromHal(parameters))); - } -} diff --git a/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java b/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java index 05ca144ed3b9..008fea5831ad 100644 --- a/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java +++ b/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java @@ -43,7 +43,7 @@ class TunerSession extends ITuner.Stub { private final RadioModule mModule; private final ITunerSession mHwSession; - private final TunerCallback mCallback; + final android.hardware.radio.ITunerCallback mCallback; private boolean mIsClosed = false; private boolean mIsMuted = false; @@ -51,7 +51,7 @@ class TunerSession extends ITuner.Stub { private RadioManager.BandConfig mDummyConfig = null; TunerSession(@NonNull RadioModule module, @NonNull ITunerSession hwSession, - @NonNull TunerCallback callback) { + @NonNull android.hardware.radio.ITunerCallback callback) { mModule = Objects.requireNonNull(module); mHwSession = Objects.requireNonNull(hwSession); mCallback = Objects.requireNonNull(callback); @@ -73,9 +73,14 @@ class TunerSession extends ITuner.Stub { synchronized (mLock) { if (mIsClosed) return; if (error != null) { - TunerCallback.dispatch(() -> mCallback.mClientCb.onError(error)); + try { + mCallback.onError(error); + } catch (RemoteException ex) { + Slog.w(TAG, "mCallback.onError() failed: ", ex); + } } mIsClosed = true; + mModule.onTunerSessionClosed(this); } } @@ -96,7 +101,7 @@ class TunerSession extends ITuner.Stub { checkNotClosedLocked(); mDummyConfig = Objects.requireNonNull(config); Slog.i(TAG, "Ignoring setConfiguration - not applicable for broadcastradio HAL 2.x"); - TunerCallback.dispatch(() -> mCallback.mClientCb.onConfigurationChanged(config)); + mModule.fanoutAidlCallback(cb -> cb.onConfigurationChanged(config)); } } @@ -174,7 +179,7 @@ class TunerSession extends ITuner.Stub { @Override public boolean startBackgroundScan() { Slog.i(TAG, "Explicit background scan trigger is not supported with HAL 2.x"); - TunerCallback.dispatch(() -> mCallback.mClientCb.onBackgroundScanComplete()); + mModule.fanoutAidlCallback(cb -> cb.onBackgroundScanComplete()); return true; } diff --git a/services/core/java/com/android/server/connectivity/PermissionMonitor.java b/services/core/java/com/android/server/connectivity/PermissionMonitor.java index 31b96ca9e5bf..da1360d59539 100644 --- a/services/core/java/com/android/server/connectivity/PermissionMonitor.java +++ b/services/core/java/com/android/server/connectivity/PermissionMonitor.java @@ -100,6 +100,9 @@ public class PermissionMonitor { app.requestedPermissionsFlags); } } + } else { + // The last package of this uid is removed from device. Clean the package up. + permission = INetd.PERMISSION_UNINSTALLED; } return permission; } @@ -470,6 +473,7 @@ public class PermissionMonitor { ArrayList<Integer> allPermissionAppIds = new ArrayList<>(); ArrayList<Integer> internetPermissionAppIds = new ArrayList<>(); ArrayList<Integer> updateStatsPermissionAppIds = new ArrayList<>(); + ArrayList<Integer> noPermissionAppIds = new ArrayList<>(); ArrayList<Integer> uninstalledAppIds = new ArrayList<>(); for (int i = 0; i < netdPermissionsAppIds.size(); i++) { int permissions = netdPermissionsAppIds.valueAt(i); @@ -484,8 +488,10 @@ public class PermissionMonitor { updateStatsPermissionAppIds.add(netdPermissionsAppIds.keyAt(i)); break; case INetd.NO_PERMISSIONS: - uninstalledAppIds.add(netdPermissionsAppIds.keyAt(i)); + noPermissionAppIds.add(netdPermissionsAppIds.keyAt(i)); break; + case INetd.PERMISSION_UNINSTALLED: + uninstalledAppIds.add(netdPermissionsAppIds.keyAt(i)); default: Log.e(TAG, "unknown permission type: " + permissions + "for uid: " + netdPermissionsAppIds.keyAt(i)); @@ -506,8 +512,12 @@ public class PermissionMonitor { mNetd.trafficSetNetPermForUids(INetd.PERMISSION_UPDATE_DEVICE_STATS, ArrayUtils.convertToIntArray(updateStatsPermissionAppIds)); } - if (uninstalledAppIds.size() != 0) { + if (noPermissionAppIds.size() != 0) { mNetd.trafficSetNetPermForUids(INetd.NO_PERMISSIONS, + ArrayUtils.convertToIntArray(noPermissionAppIds)); + } + if (uninstalledAppIds.size() != 0) { + mNetd.trafficSetNetPermForUids(INetd.PERMISSION_UNINSTALLED, ArrayUtils.convertToIntArray(uninstalledAppIds)); } } catch (RemoteException e) { diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index 5abc73eb255a..cec4d693ad32 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -69,11 +69,13 @@ import android.util.SparseArray; import android.view.Display; import android.view.IInputFilter; import android.view.IInputFilterHost; +import android.view.IInputMonitorHost; import android.view.IWindow; import android.view.InputApplicationHandle; import android.view.InputChannel; import android.view.InputDevice; import android.view.InputEvent; +import android.view.InputMonitor; import android.view.InputWindowHandle; import android.view.KeyEvent; import android.view.PointerIcon; @@ -202,7 +204,10 @@ public class InputManagerService extends IInputManager.Stub int deviceId, int sourceMask, int[] keyCodes, boolean[] keyExists); private static native void nativeRegisterInputChannel(long ptr, InputChannel inputChannel, int displayId); + private static native void nativeRegisterInputMonitor(long ptr, InputChannel inputChannel, + int displayId, boolean isGestureMonitor); private static native void nativeUnregisterInputChannel(long ptr, InputChannel inputChannel); + private static native void nativePilferPointers(long ptr, IBinder token); private static native void nativeSetInputFilterEnabled(long ptr, boolean enable); private static native int nativeInjectInputEvent(long ptr, InputEvent event, int injectorPid, int injectorUid, int syncMode, int timeoutMillis, @@ -489,12 +494,43 @@ public class InputManagerService extends IInputManager.Stub } InputChannel[] inputChannels = InputChannel.openInputChannelPair(inputChannelName); - nativeRegisterInputChannel(mPtr, inputChannels[0], displayId); + // Give the output channel a token just for identity purposes. + inputChannels[0].setToken(new Binder()); + nativeRegisterInputMonitor(mPtr, inputChannels[0], displayId, false /*isGestureMonitor*/); inputChannels[0].dispose(); // don't need to retain the Java object reference return inputChannels[1]; } /** + * Creates an input monitor that will receive pointer events for the purposes of system-wide + * gesture interpretation. + * + * @param inputChannelName The input channel name. + * @param displayId Target display id. + * @return The input channel. + */ + @Override // Binder call + public InputMonitor monitorGestureInput(String inputChannelName, int displayId) { + if (!checkCallingPermission(android.Manifest.permission.MONITOR_INPUT, + "monitorInputRegion()")) { + throw new SecurityException("Requires MONITOR_INPUT permission"); + } + + Objects.requireNonNull(inputChannelName, "inputChannelName must not be null."); + + if (displayId < Display.DEFAULT_DISPLAY) { + throw new IllegalArgumentException("displayId must >= 0."); + } + + + InputChannel[] inputChannels = InputChannel.openInputChannelPair(inputChannelName); + InputMonitorHost host = new InputMonitorHost(inputChannels[0]); + inputChannels[0].setToken(host.asBinder()); + nativeRegisterInputMonitor(mPtr, inputChannels[0], displayId, true /*isGestureMonitor*/); + return new InputMonitor(inputChannelName, inputChannels[1], host); + } + + /** * Registers an input channel so that it can be used as an input event target. * @param inputChannel The input channel to register. * @param inputWindowHandle The handle of the input window associated with the @@ -1810,6 +1846,7 @@ public class InputManagerService extends IInputManager.Stub // Native callback. private void onPointerDownOutsideFocus(IBinder touchedToken) { + mWindowManagerCallbacks.onPointerDownOutsideFocus(touchedToken); } // Native callback. @@ -2024,6 +2061,14 @@ public class InputManagerService extends IInputManager.Stub public int getPointerLayer(); public int getPointerDisplayId(); + + /** + * Notifies window manager that a {@link android.view.MotionEvent#ACTION_DOWN} pointer event + * occurred on a window that did not have focus. + * + * @param touchedToken The token for the window that received the input event. + */ + void onPointerDownOutsideFocus(IBinder touchedToken); } /** @@ -2096,6 +2141,28 @@ public class InputManagerService extends IInputManager.Stub } } + /** + * Interface for the system to handle request from InputMonitors. + */ + private final class InputMonitorHost extends IInputMonitorHost.Stub { + private final InputChannel mInputChannel; + + InputMonitorHost(InputChannel channel) { + mInputChannel = channel; + } + + @Override + public void pilferPointers() { + nativePilferPointers(mPtr, asBinder()); + } + + @Override + public void dispose() { + nativeUnregisterInputChannel(mPtr, mInputChannel); + mInputChannel.dispose(); + } + } + private static final class KeyboardLayoutDescriptor { public String packageName; public String receiverName; diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index 3e134b266a88..da836c271457 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -94,6 +94,7 @@ import android.service.gatekeeper.GateKeeperResponse; import android.service.gatekeeper.IGateKeeperService; import android.text.TextUtils; import android.util.ArrayMap; +import android.util.ArraySet; import android.util.EventLog; import android.util.Log; import android.util.Slog; @@ -141,6 +142,7 @@ import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; +import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -324,7 +326,7 @@ public class LockSettingsService extends ILockSettings.Stub { Arrays.fill(newPasswordChars, '\u0000'); final int quality = DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC; setLockCredentialInternal(newPassword, CREDENTIAL_TYPE_PASSWORD, managedUserPassword, - quality, managedUserId, false); + quality, managedUserId, false, /* isLockTiedToParent= */ true); // We store a private credential for the managed user that's unlocked by the primary // account holder's credential. As such, the user will never be prompted to enter this // password directly, so we always store a password. @@ -1303,13 +1305,13 @@ public class LockSettingsService extends ILockSettings.Stub { setLockCredentialInternal(null, CREDENTIAL_TYPE_NONE, profilePasswordMap.get(managedUserId), DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, managedUserId, - false); + false, /* isLockTiedToParent= */ true); } else { Slog.wtf(TAG, "clear tied profile challenges, but no password supplied."); // Supplying null here would lead to untrusted credential change setLockCredentialInternal(null, CREDENTIAL_TYPE_NONE, null, DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, managedUserId, - true); + true, /* isLockTiedToParent= */ true); } mStorage.removeChildProfileLock(managedUserId); removeKeystoreProfileKey(managedUserId); @@ -1328,6 +1330,67 @@ public class LockSettingsService extends ILockSettings.Stub { && mLockPatternUtils.isSeparateProfileChallengeEnabled(userId); } + /** + * Send credentials for user {@code userId} to {@link RecoverableKeyStoreManager} during an + * unlock operation. + */ + private void sendCredentialsOnUnlockIfRequired( + int credentialType, @NonNull byte[] credential, int userId) { + // Don't send credentials during the factory reset protection flow. + if (userId == USER_FRP) { + return; + } + + // A profile with a unified lock screen stores a randomly generated credential, so skip it. + // Its parent will send credentials for the profile, as it stores the unified lock + // credential. + if (isManagedProfileWithUnifiedLock(userId)) { + return; + } + + // Send credentials for the user and any child profiles that share its lock screen. + for (int profileId : getProfilesWithSameLockScreen(userId)) { + mRecoverableKeyStoreManager.lockScreenSecretAvailable( + credentialType, credential, profileId); + } + } + + /** + * Send credentials for user {@code userId} to {@link RecoverableKeyStoreManager} when its + * credentials are set/changed. + */ + private void sendCredentialsOnChangeIfRequired( + int credentialType, byte[] credential, int userId, boolean isLockTiedToParent) { + // A profile whose lock screen is being tied to its parent's will either have a randomly + // generated credential (creation) or null (removal). We rely on the parent to send its + // credentials for the profile in both cases as it stores the unified lock credential. + if (isLockTiedToParent) { + return; + } + + // Send credentials for the user and any child profiles that share its lock screen. + for (int profileId : getProfilesWithSameLockScreen(userId)) { + mRecoverableKeyStoreManager.lockScreenSecretChanged( + credentialType, credential, profileId); + } + } + + /** + * Returns all profiles of {@code userId}, including itself, that have the same lock screen + * challenge. + */ + private Set<Integer> getProfilesWithSameLockScreen(int userId) { + Set<Integer> profiles = new ArraySet<>(); + for (UserInfo profile : mUserManager.getProfiles(userId)) { + if (profile.id == userId + || (profile.profileGroupId == userId + && isManagedProfileWithUnifiedLock(profile.id))) { + profiles.add(profile.id); + } + } + return profiles; + } + // This method should be called by LockPatternUtil only, all internal methods in this class // should call setLockCredentialInternal. @Override @@ -1342,16 +1405,24 @@ public class LockSettingsService extends ILockSettings.Stub { checkWritePermission(userId); synchronized (mSeparateChallengeLock) { setLockCredentialInternal(credential, type, savedCredential, requestedQuality, userId, - allowUntrustedChange); + allowUntrustedChange, /* isLockTiedToParent= */ false); setSeparateProfileChallengeEnabledLocked(userId, true, null); notifyPasswordChanged(userId); } + if (mUserManager.getUserInfo(userId).isManagedProfile()) { + // Make sure the profile doesn't get locked straight after setting work challenge. + setDeviceUnlockedForUser(userId); + } notifySeparateProfileChallengeChanged(userId); } + /** + * @param isLockTiedToParent is {@code true} if {@code userId} is a profile and its new + * credentials are being tied to its parent's credentials. + */ private void setLockCredentialInternal(byte[] credential, @CredentialType int credentialType, - byte[] savedCredential, int requestedQuality, int userId, - boolean allowUntrustedChange) throws RemoteException { + byte[] savedCredential, int requestedQuality, int userId, boolean allowUntrustedChange, + boolean isLockTiedToParent) throws RemoteException { // Normalize savedCredential and credential such that empty string is always represented // as null. if (savedCredential == null || savedCredential.length == 0) { @@ -1363,7 +1434,7 @@ public class LockSettingsService extends ILockSettings.Stub { synchronized (mSpManager) { if (isSyntheticPasswordBasedCredentialLocked(userId)) { spBasedSetLockCredentialInternalLocked(credential, credentialType, savedCredential, - requestedQuality, userId, allowUntrustedChange); + requestedQuality, userId, allowUntrustedChange, isLockTiedToParent); return; } } @@ -1379,7 +1450,8 @@ public class LockSettingsService extends ILockSettings.Stub { fixateNewestUserKeyAuth(userId); synchronizeUnifiedWorkChallengeForProfiles(userId, null); notifyActivePasswordMetricsAvailable(CREDENTIAL_TYPE_NONE, null, userId); - mRecoverableKeyStoreManager.lockScreenSecretChanged(credentialType, credential, userId); + sendCredentialsOnChangeIfRequired( + credentialType, credential, userId, isLockTiedToParent); return; } if (credential == null) { @@ -1414,7 +1486,7 @@ public class LockSettingsService extends ILockSettings.Stub { initializeSyntheticPasswordLocked(currentHandle.hash, savedCredential, currentHandle.type, requestedQuality, userId); spBasedSetLockCredentialInternalLocked(credential, credentialType, savedCredential, - requestedQuality, userId, allowUntrustedChange); + requestedQuality, userId, allowUntrustedChange, isLockTiedToParent); return; } } @@ -1432,8 +1504,8 @@ public class LockSettingsService extends ILockSettings.Stub { // Refresh the auth token doVerifyCredential(credential, credentialType, true, 0, userId, null /* progressCallback */); synchronizeUnifiedWorkChallengeForProfiles(userId, null); - mRecoverableKeyStoreManager.lockScreenSecretChanged(credentialType, credential, - userId); + sendCredentialsOnChangeIfRequired( + credentialType, credential, userId, isLockTiedToParent); } else { throw new RemoteException("Failed to enroll " + (credentialType == CREDENTIAL_TYPE_PASSWORD ? "password" : "pattern")); @@ -1674,8 +1746,7 @@ public class LockSettingsService extends ILockSettings.Stub { // The user employs synthetic password based credential. if (response != null) { if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) { - mRecoverableKeyStoreManager.lockScreenSecretAvailable(credentialType, credential, - userId); + sendCredentialsOnUnlockIfRequired(credentialType, credential, userId); } return response; } @@ -1709,7 +1780,8 @@ public class LockSettingsService extends ILockSettings.Stub { mStrongAuth.reportSuccessfulStrongAuthUnlock(userId); if (shouldReEnrollBaseZero) { setLockCredentialInternal(credential, storedHash.type, credentialToVerify, - DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, userId, false); + DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, userId, false, + /* isLockTiedToParent= */ false); } } @@ -1800,12 +1872,12 @@ public class LockSettingsService extends ILockSettings.Stub { storedHash.type == CREDENTIAL_TYPE_PATTERN ? DevicePolicyManager.PASSWORD_QUALITY_SOMETHING : DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC - /* TODO(roosa): keep the same password quality */, userId, false); + /* TODO(roosa): keep the same password quality */, + userId, false, /* isLockTiedToParent= */ false); if (!hasChallenge) { notifyActivePasswordMetricsAvailable(storedHash.type, credential, userId); // Use credentials to create recoverable keystore snapshot. - mRecoverableKeyStoreManager.lockScreenSecretAvailable( - storedHash.type, credential, userId); + sendCredentialsOnUnlockIfRequired(storedHash.type, credential, userId); return VerifyCredentialResponse.OK; } // Fall through to get the auth token. Technically this should never happen, @@ -1835,9 +1907,7 @@ public class LockSettingsService extends ILockSettings.Stub { unlockUser(userId, response.getPayload(), secretFromCredential(credential)); if (isManagedProfileWithSeparatedLock(userId)) { - TrustManager trustManager = - (TrustManager) mContext.getSystemService(Context.TRUST_SERVICE); - trustManager.setDeviceLockedForUser(userId, false); + setDeviceUnlockedForUser(userId); } int reEnrollQuality = storedHash.type == CREDENTIAL_TYPE_PATTERN ? DevicePolicyManager.PASSWORD_QUALITY_SOMETHING @@ -1845,7 +1915,7 @@ public class LockSettingsService extends ILockSettings.Stub { /* TODO(roosa): keep the same password quality */; if (shouldReEnroll) { setLockCredentialInternal(credential, storedHash.type, credential, - reEnrollQuality, userId, false); + reEnrollQuality, userId, false, /* isLockTiedToParent= */ false); } else { // Now that we've cleared of all required GK migration, let's do the final // migration to synthetic password. @@ -1859,8 +1929,7 @@ public class LockSettingsService extends ILockSettings.Stub { } } // Use credentials to create recoverable keystore snapshot. - mRecoverableKeyStoreManager.lockScreenSecretAvailable(storedHash.type, credential, - userId); + sendCredentialsOnUnlockIfRequired(storedHash.type, credential, userId); } else if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_RETRY) { if (response.getTimeout() > 0) { @@ -2465,9 +2534,7 @@ public class LockSettingsService extends ILockSettings.Stub { activateEscrowTokens(authResult.authToken, userId); if (isManagedProfileWithSeparatedLock(userId)) { - TrustManager trustManager = - (TrustManager) mContext.getSystemService(Context.TRUST_SERVICE); - trustManager.setDeviceLockedForUser(userId, false); + setDeviceUnlockedForUser(userId); } mStrongAuth.reportSuccessfulStrongAuthUnlock(userId); @@ -2481,6 +2548,11 @@ public class LockSettingsService extends ILockSettings.Stub { return response; } + private void setDeviceUnlockedForUser(int userId) { + final TrustManager trustManager = mContext.getSystemService(TrustManager.class); + trustManager.setDeviceLockedForUser(userId, false); + } + /** * Change the user's lockscreen password by creating a new SP blob and update the handle, based * on an existing authentication token. Even though a new SP blob is created, the underlying @@ -2549,7 +2621,7 @@ public class LockSettingsService extends ILockSettings.Stub { @GuardedBy("mSpManager") private void spBasedSetLockCredentialInternalLocked(byte[] credential, int credentialType, byte[] savedCredential, int requestedQuality, int userId, - boolean allowUntrustedChange) throws RemoteException { + boolean allowUntrustedChange, boolean isLockTiedToParent) throws RemoteException { if (DEBUG) Slog.d(TAG, "spBasedSetLockCredentialInternalLocked: user=" + userId); if (isManagedProfileWithUnifiedLock(userId)) { // get credential from keystore when managed profile has unified lock @@ -2615,7 +2687,7 @@ public class LockSettingsService extends ILockSettings.Stub { // requestedQuality, userId) instead if we still allow untrusted reset that changes // synthetic password. That would invalidate existing escrow tokens though. } - mRecoverableKeyStoreManager.lockScreenSecretChanged(credentialType, credential, userId); + sendCredentialsOnChangeIfRequired(credentialType, credential, userId, isLockTiedToParent); } /** diff --git a/core/java/com/android/internal/net/NetworkStatsFactory.java b/services/core/java/com/android/server/net/NetworkStatsFactory.java index f8483461c2d2..af299cf66d3f 100644 --- a/core/java/com/android/internal/net/NetworkStatsFactory.java +++ b/services/core/java/com/android/server/net/NetworkStatsFactory.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.internal.net; +package com.android.server.net; import static android.net.NetworkStats.SET_ALL; import static android.net.NetworkStats.TAG_ALL; diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java index 205ddb07ec44..15599111f6e5 100644 --- a/services/core/java/com/android/server/net/NetworkStatsService.java +++ b/services/core/java/com/android/server/net/NetworkStatsService.java @@ -130,7 +130,6 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.net.NetworkStatsFactory; import com.android.internal.net.VpnInfo; import com.android.internal.util.ArrayUtils; import com.android.internal.util.DumpUtils; diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index bfab85b5211b..f2e56b589bd5 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -17,6 +17,7 @@ package com.android.server.notification; import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; +import static android.app.Notification.CATEGORY_CALL; import static android.app.Notification.FLAG_AUTOGROUP_SUMMARY; import static android.app.Notification.FLAG_BUBBLE; import static android.app.Notification.FLAG_FOREGROUND_SERVICE; @@ -107,6 +108,7 @@ import android.app.NotificationChannelGroup; import android.app.NotificationManager; import android.app.NotificationManager.Policy; import android.app.PendingIntent; +import android.app.Person; import android.app.StatusBarManager; import android.app.UriGrantsManager; import android.app.admin.DeviceAdminInfo; @@ -4762,11 +4764,36 @@ public class NotificationManagerService extends SystemService { /** * Updates the flags for this notification to reflect whether it is a bubble or not. */ - private void flagNotificationForBubbles(NotificationRecord r, String pkg, int userId) { + private void flagNotificationForBubbles(NotificationRecord r, String pkg, int userId, + NotificationRecord oldRecord) { Notification notification = r.getNotification(); - boolean canBubble = mPreferencesHelper.areBubblesAllowed(pkg, userId) + + // Does the app want to bubble & have permission to bubble? + boolean canBubble = notification.getBubbleMetadata() != null + && mPreferencesHelper.areBubblesAllowed(pkg, userId) && r.getChannel().canBubble(); - if (notification.getBubbleMetadata() != null && canBubble) { + + // Is the app in the foreground? + final boolean appIsForeground = + mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND; + + // Is the notification something we'd allow to bubble? + // A call with a foreground service + person + ArrayList<Person> peopleList = notification.extras != null + ? notification.extras.getParcelableArrayList(Notification.EXTRA_PEOPLE_LIST) + : null; + boolean isForegroundCall = CATEGORY_CALL.equals(notification.category) + && (notification.flags & FLAG_FOREGROUND_SERVICE) != 0; + // OR message style (which always has a person) + Class<? extends Notification.Style> style = notification.getNotificationStyle(); + boolean isMessageStyle = Notification.MessagingStyle.class.equals(style); + boolean notificationAppropriateToBubble = isMessageStyle + || (peopleList != null && !peopleList.isEmpty() && isForegroundCall); + // OR something that was previously a bubble & still exists + boolean bubbleUpdate = oldRecord != null + && (oldRecord.getNotification().flags & FLAG_BUBBLE) != 0; + + if (canBubble && (notificationAppropriateToBubble || appIsForeground || bubbleUpdate)) { notification.flags |= FLAG_BUBBLE; } else { notification.flags &= ~FLAG_BUBBLE; @@ -5129,7 +5156,7 @@ public class NotificationManagerService extends SystemService { final String tag = n.getTag(); // We need to fix the notification up a little for bubbles - flagNotificationForBubbles(r, pkg, callingUid); + flagNotificationForBubbles(r, pkg, callingUid, old); // Handle grouped notifications and bail out early if we // can to avoid extracting signals. diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index a3e90dcff83e..f34b2cb5cf29 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -98,7 +98,7 @@ public class PreferencesHelper implements RankingConfig { private static final int DEFAULT_VISIBILITY = NotificationManager.VISIBILITY_NO_OVERRIDE; private static final int DEFAULT_IMPORTANCE = NotificationManager.IMPORTANCE_UNSPECIFIED; @VisibleForTesting - static final boolean DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS = false; + static final boolean DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS = true; private static final boolean DEFAULT_SHOW_BADGE = true; private static final boolean DEFAULT_ALLOW_BUBBLE = true; private static final boolean DEFAULT_OEM_LOCKED_IMPORTANCE = false; @@ -132,7 +132,7 @@ public class PreferencesHelper implements RankingConfig { private SparseBooleanArray mBadgingEnabled; private SparseBooleanArray mBubblesEnabled; private boolean mAreChannelsBypassingDnd; - private boolean mHideSilentStatusBarIcons; + private boolean mHideSilentStatusBarIcons = DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS; public PreferencesHelper(Context context, PackageManager pm, RankingHandler rankingHandler, ZenModeHelper zenHelper) { diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java index 6e98d6e67844..51d5acc9b555 100644 --- a/services/core/java/com/android/server/om/OverlayManagerService.java +++ b/services/core/java/com/android/server/om/OverlayManagerService.java @@ -374,7 +374,7 @@ public final class OverlayManagerService extends SystemService { synchronized (mLock) { final PackageInfo pi = mPackageManager.getPackageInfo(packageName, userId, false); - if (pi != null) { + if (pi != null && !pi.applicationInfo.isInstantApp()) { mPackageManager.cachePackageInfo(packageName, userId, pi); if (pi.isOverlayPackage()) { mImpl.onOverlayPackageAdded(packageName, userId); @@ -397,7 +397,7 @@ public final class OverlayManagerService extends SystemService { synchronized (mLock) { final PackageInfo pi = mPackageManager.getPackageInfo(packageName, userId, false); - if (pi != null) { + if (pi != null && pi.applicationInfo.isInstantApp()) { mPackageManager.cachePackageInfo(packageName, userId, pi); if (pi.isOverlayPackage()) { mImpl.onOverlayPackageChanged(packageName, userId); @@ -438,7 +438,7 @@ public final class OverlayManagerService extends SystemService { synchronized (mLock) { final PackageInfo pi = mPackageManager.getPackageInfo(packageName, userId, false); - if (pi != null) { + if (pi != null && !pi.applicationInfo.isInstantApp()) { mPackageManager.cachePackageInfo(packageName, userId, pi); if (pi.isOverlayPackage()) { mImpl.onOverlayPackageReplaced(packageName, userId); diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index d2a160b29a8b..3306ccdf2a22 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -498,6 +498,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { info.isStagedSessionReady = mStagedSessionReady; info.isStagedSessionFailed = mStagedSessionFailed; info.setStagedSessionErrorCode(mStagedSessionErrorCode, mStagedSessionErrorMessage); + info.updatedMillis = updatedMillis; } return info; } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 825daf6b0ad7..78aa5a0b66d7 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -476,6 +476,7 @@ public class PackageManagerService extends IPackageManager.Stub static final int SCAN_AS_VENDOR = 1 << 20; static final int SCAN_AS_PRODUCT = 1 << 21; static final int SCAN_AS_PRODUCT_SERVICES = 1 << 22; + static final int SCAN_AS_ODM = 1 << 23; @IntDef(flag = true, prefix = { "SCAN_" }, value = { SCAN_NO_DEX, @@ -594,6 +595,8 @@ public class PackageManagerService extends IPackageManager.Stub private static final String PRODUCT_SERVICES_OVERLAY_DIR = "/product_services/overlay"; + private static final String ODM_OVERLAY_DIR = "/odm/overlay"; + /** Canonical intent used to identify what counts as a "web browser" app */ private static final Intent sBrowserIntent; static { @@ -2523,6 +2526,13 @@ public class PackageManagerService extends IPackageManager.Stub | SCAN_AS_SYSTEM | SCAN_AS_PRODUCT_SERVICES, 0); + scanDirTracedLI(new File(ODM_OVERLAY_DIR), + mDefParseFlags + | PackageParser.PARSE_IS_SYSTEM_DIR, + scanFlags + | SCAN_AS_SYSTEM + | SCAN_AS_ODM, + 0); mParallelPackageParserCallback.findStaticOverlayPackages(); @@ -3226,6 +3236,8 @@ public class PackageManagerService extends IPackageManager.Stub // once we have a booted system. mInstaller.setWarnIfHeld(mPackages); + PackageParser.readConfigUseRoundIcon(mContext.getResources()); + mServiceStartWithDelay = SystemClock.uptimeMillis() + (60 * 1000L); Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); @@ -4688,6 +4700,11 @@ public class PackageManagerService extends IPackageManager.Stub if (ps == null || now - ps.lastUpdateTime < maxCachePeriod) { continue; } + + if (ps.pkg.isSystem()) { + continue; + } + if (packagesToDelete == null) { packagesToDelete = new ArrayList<>(); } @@ -5137,7 +5154,7 @@ public class PackageManagerService extends IPackageManager.Stub continue; } - if (!ps.getUserState().get(userId).isAvailable(flags)) { + if (!ps.readUserState(userId).isAvailable(flags)) { continue; } @@ -6968,8 +6985,7 @@ public class PackageManagerService extends IPackageManager.Stub } final PackageSetting ps = mSettings.mPackages.get(mInstantAppInstallerActivity.packageName); if (ps == null - || ps.getUserState().get(userId) == null - || !ps.getUserState().get(userId).isEnabled(mInstantAppInstallerActivity, 0)) { + || !ps.readUserState(userId).isEnabled(mInstantAppInstallerActivity, 0)) { return result; } final ResolveInfo ephemeralInstaller = new ResolveInfo(mInstantAppInstallerInfo); @@ -10399,6 +10415,7 @@ public class PackageManagerService extends IPackageManager.Stub * <li>{@link #SCAN_AS_PRODUCT_SERVICES}</li> * <li>{@link #SCAN_AS_INSTANT_APP}</li> * <li>{@link #SCAN_AS_VIRTUAL_PRELOAD}</li> + * <li>{@link #SCAN_AS_ODM}</li> * </ul> */ private @ScanFlags int adjustScanFlags(@ScanFlags int scanFlags, @@ -10435,6 +10452,10 @@ public class PackageManagerService extends IPackageManager.Stub & ApplicationInfo.PRIVATE_FLAG_PRODUCT_SERVICES) != 0) { scanFlags |= SCAN_AS_PRODUCT_SERVICES; } + if ((systemPkgSetting.pkgPrivateFlags + & ApplicationInfo.PRIVATE_FLAG_ODM) != 0) { + scanFlags |= SCAN_AS_ODM; + } } if (pkgSetting != null) { final int userId = ((user == null) ? 0 : user.getIdentifier()); @@ -11206,6 +11227,10 @@ public class PackageManagerService extends IPackageManager.Stub pkg.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_PRODUCT_SERVICES; } + if ((scanFlags & SCAN_AS_ODM) != 0) { + pkg.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_ODM; + } + // Check if the package is signed with the same key as the platform package. if (PLATFORM_PACKAGE_NAME.equals(pkg.packageName) || (platformPkg != null && compareSignatures( @@ -11532,17 +11557,44 @@ public class PackageManagerService extends IPackageManager.Stub " is static but not pre-installed."); } - // The only case where we allow installation of a non-system overlay is when - // its signature is signed with the platform certificate. - PackageSetting platformPkgSetting = mSettings.getPackageLPr("android"); - if ((platformPkgSetting.signatures.mSigningDetails - != PackageParser.SigningDetails.UNKNOWN) - && (compareSignatures( - platformPkgSetting.signatures.mSigningDetails.signatures, - pkg.mSigningDetails.signatures) - != PackageManager.SIGNATURE_MATCH)) { - throw new PackageManagerException("Overlay " + pkg.packageName + - " must be signed with the platform certificate."); + // A non-preloaded overlay packages must have targetSdkVersion >= Q, or be + // signed with the platform certificate. Check this in increasing order of + // computational cost. + if (pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.Q) { + final PackageSetting platformPkgSetting = + mSettings.getPackageLPr("android"); + if ((platformPkgSetting.signatures.mSigningDetails + != PackageParser.SigningDetails.UNKNOWN) + && (compareSignatures( + platformPkgSetting.signatures.mSigningDetails.signatures, + pkg.mSigningDetails.signatures) + != PackageManager.SIGNATURE_MATCH)) { + throw new PackageManagerException("Overlay " + pkg.packageName + + " must target Q or later, " + + "or be signed with the platform certificate"); + } + } + + // A non-preloaded overlay package, without <overlay android:targetName>, will + // only be used if it is signed with the same certificate as its target. If the + // target is already installed, check this here to augment the last line of + // defence which is OMS. + if (pkg.mOverlayTargetName == null) { + final PackageSetting targetPkgSetting = + mSettings.getPackageLPr(pkg.mOverlayTarget); + if (targetPkgSetting != null) { + if ((targetPkgSetting.signatures.mSigningDetails + != PackageParser.SigningDetails.UNKNOWN) + && (compareSignatures( + targetPkgSetting.signatures.mSigningDetails.signatures, + pkg.mSigningDetails.signatures) + != PackageManager.SIGNATURE_MATCH)) { + throw new PackageManagerException("Overlay " + pkg.packageName + + " and target " + pkg.mOverlayTarget + " signed with" + + " different certificates, and the overlay lacks" + + " <overlay android:targetName>"); + } + } } } } @@ -12128,6 +12180,8 @@ public class PackageManagerService extends IPackageManager.Stub codeRoot = Environment.getProductDirectory(); } else if (FileUtils.contains(Environment.getProductServicesDirectory(), codePath)) { codeRoot = Environment.getProductServicesDirectory(); + } else if (FileUtils.contains(Environment.getOdmDirectory(), codePath)) { + codeRoot = Environment.getOdmDirectory(); } else { // Unrecognized code path; take its top real segment as the apk root: // e.g. /something/app/blah.apk => /something @@ -16380,7 +16434,6 @@ public class PackageManagerService extends IPackageManager.Stub sharedLibLatestVersionSetting); } } - prepareScanResultLocked(result); } } catch (PackageManagerException e) { request.installResult.setError("Scanning Failed.", e); @@ -17216,13 +17269,15 @@ public class PackageManagerService extends IPackageManager.Stub final boolean oem = isOemApp(oldPackage); final boolean vendor = isVendorApp(oldPackage); final boolean product = isProductApp(oldPackage); + final boolean odm = isOdmApp(oldPackage); final @ParseFlags int systemParseFlags = parseFlags; final @ScanFlags int systemScanFlags = scanFlags | SCAN_AS_SYSTEM | (privileged ? SCAN_AS_PRIVILEGED : 0) | (oem ? SCAN_AS_OEM : 0) | (vendor ? SCAN_AS_VENDOR : 0) - | (product ? SCAN_AS_PRODUCT : 0); + | (product ? SCAN_AS_PRODUCT : 0) + | (odm ? SCAN_AS_ODM : 0); if (DEBUG_INSTALL) { Slog.d(TAG, "replaceSystemPackageLI: new=" + pkg @@ -17553,6 +17608,10 @@ public class PackageManagerService extends IPackageManager.Stub & ApplicationInfo.PRIVATE_FLAG_PRODUCT_SERVICES) != 0; } + private static boolean isOdmApp(PackageParser.Package pkg) { + return (pkg.applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_ODM) != 0; + } + private static boolean hasDomainURLs(PackageParser.Package pkg) { return (pkg.applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS) != 0; } @@ -18327,6 +18386,15 @@ public class PackageManagerService extends IPackageManager.Stub return false; } + static boolean locationIsOdm(String path) { + try { + return path.startsWith(Environment.getOdmDirectory().getCanonicalPath() + "/"); + } catch (IOException e) { + Slog.e(TAG, "Unable to access code path " + path); + } + return false; + } + /* * Tries to delete system package. */ @@ -18440,6 +18508,9 @@ public class PackageManagerService extends IPackageManager.Stub if (locationIsProductServices(codePathString)) { scanFlags |= SCAN_AS_PRODUCT_SERVICES; } + if (locationIsOdm(codePathString)) { + scanFlags |= SCAN_AS_ODM; + } final File codePath = new File(codePathString); final PackageParser.Package pkg = @@ -20842,6 +20913,7 @@ public class PackageManagerService extends IPackageManager.Stub mContext.getContentResolver(), android.provider.Settings.Global.COMPATIBILITY_MODE, 1) == 1; PackageParser.setCompatibilityModeEnabled(compatibilityModeEnabled); + if (DEBUG_SETTINGS) { Log.d(TAG, "compatibility mode:" + compatibilityModeEnabled); } diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java index 2c2cc7ea78f9..ead09b424d61 100644 --- a/services/core/java/com/android/server/pm/PackageSetting.java +++ b/services/core/java/com/android/server/pm/PackageSetting.java @@ -152,6 +152,10 @@ public final class PackageSetting extends PackageSettingBase { return (pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_PRODUCT_SERVICES) != 0; } + public boolean isOdm() { + return (pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_ODM) != 0; + } + public boolean isSystem() { return (pkgFlags & ApplicationInfo.FLAG_SYSTEM) != 0; } diff --git a/services/core/java/com/android/server/pm/SettingBase.java b/services/core/java/com/android/server/pm/SettingBase.java index fbf54391209c..a24818f04f52 100644 --- a/services/core/java/com/android/server/pm/SettingBase.java +++ b/services/core/java/com/android/server/pm/SettingBase.java @@ -64,6 +64,7 @@ abstract class SettingBase { | ApplicationInfo.PRIVATE_FLAG_VENDOR | ApplicationInfo.PRIVATE_FLAG_PRODUCT | ApplicationInfo.PRIVATE_FLAG_PRODUCT_SERVICES - | ApplicationInfo.PRIVATE_FLAG_REQUIRED_FOR_SYSTEM_USER); + | ApplicationInfo.PRIVATE_FLAG_REQUIRED_FOR_SYSTEM_USER + | ApplicationInfo.PRIVATE_FLAG_ODM); } } diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 6a1f223917b6..4f81fd9f7a9f 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -60,6 +60,7 @@ import android.os.Message; import android.os.PatternMatcher; import android.os.PersistableBundle; import android.os.Process; +import android.os.SELinux; import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; @@ -776,7 +777,8 @@ public final class Settings { | ApplicationInfo.PRIVATE_FLAG_OEM | ApplicationInfo.PRIVATE_FLAG_VENDOR | ApplicationInfo.PRIVATE_FLAG_PRODUCT - | ApplicationInfo.PRIVATE_FLAG_PRODUCT_SERVICES); + | ApplicationInfo.PRIVATE_FLAG_PRODUCT_SERVICES + | ApplicationInfo.PRIVATE_FLAG_ODM); pkgSetting.pkgFlags |= pkgFlags & ApplicationInfo.FLAG_SYSTEM; pkgSetting.pkgPrivateFlags |= pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED; @@ -788,6 +790,8 @@ public final class Settings { pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_PRODUCT; pkgSetting.pkgPrivateFlags |= pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_PRODUCT_SERVICES; + pkgSetting.pkgPrivateFlags |= + pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_ODM; pkgSetting.primaryCpuAbiString = primaryCpuAbi; pkgSetting.secondaryCpuAbiString = secondaryCpuAbi; if (childPkgNames != null) { @@ -2650,6 +2654,24 @@ public final class Settings { } void writePackageListLPr(int creatingUserId) { + String filename = mPackageListFilename.getAbsolutePath(); + String ctx = SELinux.fileSelabelLookup(filename); + if (ctx == null) { + Slog.wtf(TAG, "Failed to get SELinux context for " + + mPackageListFilename.getAbsolutePath()); + } + + if (!SELinux.setFSCreateContext(ctx)) { + Slog.wtf(TAG, "Failed to set packages.list SELinux context"); + } + try { + writePackageListLPrInternal(creatingUserId); + } finally { + SELinux.setFSCreateContext(null); + } + } + + private void writePackageListLPrInternal(int creatingUserId) { // Only derive GIDs for active users (not dying) final List<UserInfo> users = UserManagerService.getInstance().getUsers(true); int[] userIds = new int[users.size()]; @@ -4401,6 +4423,7 @@ public final class Settings { ApplicationInfo.PRIVATE_FLAG_PRODUCT, "PRODUCT", ApplicationInfo.PRIVATE_FLAG_PRODUCT_SERVICES, "PRODUCT_SERVICES", ApplicationInfo.PRIVATE_FLAG_VIRTUAL_PRELOAD, "VIRTUAL_PRELOAD", + ApplicationInfo.PRIVATE_FLAG_ODM, "ODM", }; void dumpVersionLPr(IndentingPrintWriter pw) { diff --git a/services/core/java/com/android/server/testharness/TestHarnessModeService.java b/services/core/java/com/android/server/testharness/TestHarnessModeService.java index 9f9b797796e9..4f118872eb0a 100644 --- a/services/core/java/com/android/server/testharness/TestHarnessModeService.java +++ b/services/core/java/com/android/server/testharness/TestHarnessModeService.java @@ -71,7 +71,6 @@ public class TestHarnessModeService extends SystemService { private static final String TEST_HARNESS_MODE_PROPERTY = "persist.sys.test_harness"; private PersistentDataBlockManagerInternal mPersistentDataBlockManagerInternal; - private boolean mShouldSetUpTestHarnessMode; public TestHarnessModeService(Context context) { super(context); @@ -89,9 +88,8 @@ public class TestHarnessModeService extends SystemService { setUpTestHarnessMode(); break; case PHASE_BOOT_COMPLETED: - disableAutoSync(); - configureSettings(); - showNotification(); + completeTestHarnessModeSetup(); + showNotificationIfEnabled(); break; } super.onBootPhase(phase); @@ -104,47 +102,45 @@ public class TestHarnessModeService extends SystemService { // There's no data to apply, so leave it as-is. return; } - PersistentData persistentData; + // If there is data, we should set the device as provisioned, so that we skip the setup + // wizard. + setDeviceProvisioned(); + SystemProperties.set(TEST_HARNESS_MODE_PROPERTY, "1"); + } + + private void completeTestHarnessModeSetup() { + Slog.d(TAG, "Completing Test Harness Mode setup."); + byte[] testHarnessModeData = getPersistentDataBlock().getTestHarnessModeData(); + if (testHarnessModeData == null || testHarnessModeData.length == 0) { + // There's no data to apply, so leave it as-is. + return; + } try { - persistentData = PersistentData.fromBytes(testHarnessModeData); + setUpAdbFiles(PersistentData.fromBytes(testHarnessModeData)); + disableAutoSync(); + configureSettings(); } catch (SetUpTestHarnessModeException e) { Slog.e(TAG, "Failed to set up Test Harness Mode. Bad data.", e); - return; } finally { - // Clear out the Test Harness Mode data. It's now in memory if successful or we should - // skip setting up. + // Clear out the Test Harness Mode data so that we don't repeat the setup. If it failed + // to set up, then retrying without enabling Test Harness Mode should allow it to boot. + // If we succeeded setting up, we shouldn't be re-applying the THM steps every boot + // anyway. getPersistentDataBlock().clearTestHarnessModeData(); } - mShouldSetUpTestHarnessMode = true; - setUpAdb(persistentData); - setDeviceProvisioned(); - } - - private void setUpAdb(PersistentData persistentData) { - ContentResolver cr = getContext().getContentResolver(); - - // Disable the TTL for ADB keys before enabling ADB - Settings.Global.putLong(cr, Settings.Global.ADB_ALLOWED_CONNECTION_TIME, 0); - - SystemProperties.set(TEST_HARNESS_MODE_PROPERTY, "1"); - writeAdbKeysFile(persistentData); } private void disableAutoSync() { - if (!mShouldSetUpTestHarnessMode) { - return; - } UserInfo primaryUser = UserManager.get(getContext()).getPrimaryUser(); ContentResolver .setMasterSyncAutomaticallyAsUser(false, primaryUser.getUserHandle().getIdentifier()); } private void configureSettings() { - if (!mShouldSetUpTestHarnessMode) { - return; - } ContentResolver cr = getContext().getContentResolver(); + // Disable the TTL for ADB keys before enabling ADB + Settings.Global.putLong(cr, Settings.Global.ADB_ALLOWED_CONNECTION_TIME, 0); Settings.Global.putInt(cr, Settings.Global.ADB_ENABLED, 1); Settings.Global.putInt(cr, Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 1); Settings.Global.putInt(cr, Settings.Global.PACKAGE_VERIFIER_ENABLE, 0); @@ -155,7 +151,7 @@ public class TestHarnessModeService extends SystemService { Settings.Global.putInt(cr, Settings.Global.OTA_DISABLE_AUTOMATIC_UPDATE, 1); } - private void writeAdbKeysFile(PersistentData persistentData) { + private void setUpAdbFiles(PersistentData persistentData) { AdbManagerInternal adbManager = LocalServices.getService(AdbManagerInternal.class); writeBytesToFile(persistentData.mAdbKeys, adbManager.getAdbKeysFile().toPath()); @@ -189,7 +185,7 @@ public class TestHarnessModeService extends SystemService { UserHandle.USER_CURRENT); } - private void showNotification() { + private void showNotificationIfEnabled() { if (!SystemProperties.getBoolean(TEST_HARNESS_MODE_PROPERTY, false)) { return; } diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index cd15587918ec..3b358e897ccc 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -981,6 +981,11 @@ class ActivityStarter { if (isRealCallingUidPersistentSystemProcess && allowBackgroundActivityStart) { return false; } + // don't abort if the realCallingUid is an associated companion app + if (mService.isAssociatedCompanionApp(UserHandle.getUserId(realCallingUid), + realCallingUid)) { + return false; + } } // If we don't have callerApp at this point, no caller was provided to startActivity(). // That's the case for PendingIntent-based starts, since the creator's process might not be @@ -1026,7 +1031,7 @@ class ActivityStarter { } // don't abort if the callingPackage has companion device final int callingUserId = UserHandle.getUserId(callingUid); - if (mService.isAssociatedCompanionApp(callingUserId, callingPackage)) { + if (mService.isAssociatedCompanionApp(callingUserId, callingUid)) { return false; } // don't abort if the callingPackage is temporarily whitelisted diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 98c37e65b9ac..b64abdb48fba 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -443,8 +443,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { // VoiceInteractionManagerService ComponentName mActiveVoiceInteractionServiceComponent; - // A map userId and all its companion app packages - private final Map<Integer, Set<String>> mCompanionAppPackageMap = new ArrayMap<>(); + // A map userId and all its companion app uids + private final Map<Integer, Set<Integer>> mCompanionAppUidsMap = new ArrayMap<>(); VrController mVrController; KeyguardController mKeyguardController; @@ -5812,15 +5812,10 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } WindowProcessController getProcessController(int pid, int uid) { - final ArrayMap<String, SparseArray<WindowProcessController>> pmap = mProcessNames.getMap(); - for (int i = pmap.size()-1; i >= 0; i--) { - final SparseArray<WindowProcessController> procs = pmap.valueAt(i); - for (int j = procs.size() - 1; j >= 0; j--) { - final WindowProcessController proc = procs.valueAt(j); - if (UserHandle.isApp(uid) && proc.getPid() == pid && proc.mUid == uid) { - return proc; - } - } + final WindowProcessController proc = mPidMap.get(pid); + if (proc == null) return null; + if (UserHandle.isApp(uid) && proc.mUid == uid) { + return proc; } return null; } @@ -5912,12 +5907,12 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } } - boolean isAssociatedCompanionApp(int userId, String packageName) { - final Set<String> allPackages = mCompanionAppPackageMap.get(userId); - if (allPackages == null) { + boolean isAssociatedCompanionApp(int userId, int uid) { + final Set<Integer> allUids = mCompanionAppUidsMap.get(userId); + if (allUids == null) { return false; } - return allPackages.contains(packageName); + return allUids.contains(uid); } final class H extends Handler { @@ -7296,13 +7291,16 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { @Override public void setCompanionAppPackages(int userId, Set<String> companionAppPackages) { - // Deep copy all content to make sure we do not rely on the source - final Set<String> result = new HashSet<>(); + // Translate package names into UIDs + final Set<Integer> result = new HashSet<>(); for (String pkg : companionAppPackages) { - result.add(pkg); + final int uid = getPackageManagerInternalLocked().getPackageUid(pkg, 0, userId); + if (uid >= 0) { + result.add(uid); + } } synchronized (mGlobalLock) { - mCompanionAppPackageMap.put(userId, result); + mCompanionAppUidsMap.put(userId, result); } } } diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java index f46835eb51fc..6b500967f429 100644 --- a/services/core/java/com/android/server/wm/InputManagerCallback.java +++ b/services/core/java/com/android/server/wm/InputManagerCallback.java @@ -6,6 +6,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_INPUT; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; +import static com.android.server.wm.WindowManagerService.H.ON_POINTER_DOWN_OUTSIDE_FOCUS; import android.os.Debug; import android.os.IBinder; @@ -232,6 +233,11 @@ final class InputManagerCallback implements InputManagerService.WindowManagerCal } } + @Override + public void onPointerDownOutsideFocus(IBinder touchedToken) { + mService.mH.obtainMessage(ON_POINTER_DOWN_OUTSIDE_FOCUS, touchedToken).sendToTarget(); + } + /** Waits until the built-in input devices have been configured. */ public boolean waitForInputDevicesReady(long timeoutMillis) { synchronized (mInputDevicesReadyMonitor) { diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java index f85fdb6c3882..d3dba90fe4e6 100644 --- a/services/core/java/com/android/server/wm/InputMonitor.java +++ b/services/core/java/com/android/server/wm/InputMonitor.java @@ -63,6 +63,7 @@ final class InputMonitor { // When true, need to call updateInputWindowsLw(). private boolean mUpdateInputWindowsNeeded = true; private boolean mUpdateInputWindowsPending; + private boolean mApplyImmediately; // Currently focused input window handle. private InputWindowHandle mFocusedInputWindowHandle; @@ -152,7 +153,7 @@ final class InputMonitor { mService = service; mDisplayContent = mService.mRoot.getDisplayContent(displayId); mDisplayId = displayId; - mInputTransaction = mDisplayContent.getPendingTransaction(); + mInputTransaction = mService.mTransactionFactory.make(); mHandler = AnimationThread.getHandler(); mUpdateInputForAllWindowsConsumer = new UpdateInputForAllWindowsConsumer(); } @@ -319,6 +320,14 @@ final class InputMonitor { } } + void updateInputWindowsImmediately() { + if (mUpdateInputWindowsPending) { + mApplyImmediately = true; + mUpdateInputWindows.run(); + mApplyImmediately = false; + } + } + /* Called when the current input focus changes. * Layer assignment is assumed to be complete by the time this is called. */ @@ -433,7 +442,12 @@ final class InputMonitor { wallpaperInputConsumer.show(mInputTransaction, 0); } - mDisplayContent.scheduleAnimation(); + if (mApplyImmediately) { + mInputTransaction.apply(); + } else { + mDisplayContent.getPendingTransaction().merge(mInputTransaction); + mDisplayContent.scheduleAnimation(); + } Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java index f1560d961209..144efb49e84a 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimation.java +++ b/services/core/java/com/android/server/wm/RecentsAnimation.java @@ -17,8 +17,6 @@ package com.android.server.wm; import static android.app.ActivityManager.START_TASK_TO_FRONT; -import static android.app.AppOpsManager.OP_ASSIST_STRUCTURE; -import static android.app.AppOpsManager.OP_NONE; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; @@ -34,25 +32,16 @@ import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_T import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_RECENTS_ANIMATIONS; import android.app.ActivityOptions; -import android.app.AppOpsManager; import android.app.IAssistDataReceiver; import android.content.ComponentName; -import android.content.Context; import android.content.Intent; -import android.os.Bundle; -import android.os.IBinder; import android.os.RemoteException; import android.os.Trace; import android.util.Slog; import android.view.IRecentsAnimationRunner; -import com.android.server.LocalServices; -import com.android.server.am.AssistDataRequester; -import com.android.server.contentcapture.ContentCaptureManagerInternal; import com.android.server.wm.RecentsAnimationController.RecentsAnimationCallbacks; -import java.util.List; - /** * Manages the recents animation, including the reordering of the stacks for the transition and * cleanup. See {@link com.android.server.wm.RecentsAnimationController}. @@ -70,7 +59,6 @@ class RecentsAnimation implements RecentsAnimationCallbacks, private final int mCallingPid; private int mTargetActivityType; - private AssistDataRequester mAssistDataRequester; // The stack to restore the target stack behind when the animation is finished private ActivityStack mRestoreTargetBehindStack; @@ -135,9 +123,6 @@ class RecentsAnimation implements RecentsAnimationCallbacks, mWindowManager.deferSurfaceLayout(); try { - // Kick off the assist data request in the background before showing the target activity - requestAssistData(recentsComponent, recentsUid, assistDataReceiver); - if (hasExistingActivity) { // Move the recents activity into place for the animation if it is not top most mDefaultDisplay.moveStackBehindBottomMostVisibleStack(targetStack); @@ -216,78 +201,12 @@ class RecentsAnimation implements RecentsAnimationCallbacks, } } - /** - * Requests assist data for the top visible activities. - */ - private void requestAssistData(ComponentName recentsComponent, int recentsUid, - @Deprecated IAssistDataReceiver assistDataReceiver) { - final AppOpsManager appOpsManager = (AppOpsManager) - mService.mContext.getSystemService(Context.APP_OPS_SERVICE); - final List<IBinder> topActivities = - mService.mRootActivityContainer.getTopVisibleActivities(); - final AssistDataRequester.AssistDataRequesterCallbacks assistDataCallbacks; - if (assistDataReceiver != null) { - assistDataCallbacks = new AssistDataReceiverProxy(assistDataReceiver, - recentsComponent.getPackageName()) { - @Override - public void onAssistDataReceivedLocked(Bundle data, int activityIndex, - int activityCount) { - // Try to notify the intelligence service first - final ContentCaptureManagerInternal imService = - LocalServices.getService(ContentCaptureManagerInternal.class); - final IBinder activityToken = topActivities.get(activityIndex); - final ActivityRecord r = ActivityRecord.forTokenLocked(activityToken); - if (r != null && (imService == null - || !imService.sendActivityAssistData(r.mUserId, activityToken, data))) { - // Otherwise, use the provided assist data receiver - super.onAssistDataReceivedLocked(data, activityIndex, activityCount); - } - } - }; - } else { - final ContentCaptureManagerInternal imService = - LocalServices.getService(ContentCaptureManagerInternal.class); - if (imService == null) { - // There is no intelligence service, so there is no point requesting assist data - return; - } - - assistDataCallbacks = new AssistDataRequester.AssistDataRequesterCallbacks() { - @Override - public boolean canHandleReceivedAssistDataLocked() { - return true; - } - - @Override - public void onAssistDataReceivedLocked(Bundle data, int activityIndex, - int activityCount) { - // Try to notify the intelligence service - final IBinder activityToken = topActivities.get(activityIndex); - final ActivityRecord r = ActivityRecord.forTokenLocked(activityToken); - if (r != null) { - imService.sendActivityAssistData(r.mUserId, activityToken, data); - } - } - }; - } - mAssistDataRequester = new AssistDataRequester(mService.mContext, mWindowManager, - appOpsManager, assistDataCallbacks, this, OP_ASSIST_STRUCTURE, OP_NONE); - mAssistDataRequester.requestAutofillData(topActivities, - recentsUid, recentsComponent.getPackageName()); - } - private void finishAnimation(@RecentsAnimationController.ReorderMode int reorderMode) { synchronized (mService.mGlobalLock) { if (DEBUG) Slog.d(TAG, "onAnimationFinished(): controller=" + mWindowManager.getRecentsAnimationController() + " reorderMode=" + reorderMode); - // Cancel the associated assistant data request - if (mAssistDataRequester != null) { - mAssistDataRequester.cancel(); - mAssistDataRequester = null; - } - // Unregister for stack order changes mDefaultDisplay.unregisterStackOrderChangedListener(this); diff --git a/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java b/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java index 4ff552ec3c91..79baab6bfbb6 100644 --- a/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java +++ b/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java @@ -22,12 +22,9 @@ import static android.view.PointerIcon.TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW; import static android.view.PointerIcon.TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW; import static android.view.PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW; -import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS; - import android.graphics.Rect; import android.graphics.Region; import android.hardware.input.InputManager; -import android.os.Handler; import android.view.MotionEvent; import android.view.WindowManagerPolicyConstants.PointerEventListener; @@ -40,67 +37,15 @@ import com.android.server.wm.WindowManagerService.H; public class TaskTapPointerEventListener implements PointerEventListener { private final Region mTouchExcludeRegion = new Region(); - private final Region mTmpRegion = new Region(); private final WindowManagerService mService; private final DisplayContent mDisplayContent; - private final Handler mHandler; - private final Runnable mMoveDisplayToTop; private final Rect mTmpRect = new Rect(); private int mPointerIconType = TYPE_NOT_SPECIFIED; - private int mLastDownX; - private int mLastDownY; public TaskTapPointerEventListener(WindowManagerService service, DisplayContent displayContent) { mService = service; mDisplayContent = displayContent; - mHandler = new Handler(mService.mH.getLooper()); - mMoveDisplayToTop = () -> { - int x; - int y; - synchronized (this) { - x = mLastDownX; - y = mLastDownY; - } - synchronized (mService.mGlobalLock) { - if (!mService.mPerDisplayFocusEnabled - && mService.mRoot.getTopFocusedDisplayContent() != mDisplayContent - && inputMethodWindowContains(x, y)) { - // In a single focus system, if the input method window and the input method - // target window are on the different displays, when the user is tapping on the - // input method window, we don't move its display to top. Otherwise, the input - // method target window will lose the focus. - return; - } - final Region windowTapExcludeRegion = Region.obtain(); - mDisplayContent.amendWindowTapExcludeRegion(windowTapExcludeRegion); - if (windowTapExcludeRegion.contains(x, y)) { - windowTapExcludeRegion.recycle(); - // The user is tapping on the window tap exclude region. We don't move this - // display to top. A window tap exclude region, for example, may be set by an - // ActivityView, and the region would match the bounds of both the ActivityView - // and the virtual display in it. In this case, we would take the tap that is on - // the embedded virtual display instead of this display. - return; - } - windowTapExcludeRegion.recycle(); - WindowContainer parent = mDisplayContent.getParent(); - if (parent != null && parent.getTopChild() != mDisplayContent) { - parent.positionChildAt(WindowContainer.POSITION_TOP, mDisplayContent, - true /* includingParents */); - // For compatibility, only the topmost activity is allowed to be resumed for - // pre-Q app. Ensure the topmost activities are resumed whenever a display is - // moved to top. - // TODO(b/123761773): Investigate whether we can move this into - // RootActivityContainer#updateTopResumedActivityIfNeeded(). Currently, it is - // risky to do so because it seems possible to resume activities as part of a - // larger transaction and it's too early to resume based on current order - // when performing updateTopResumedActivityIfNeeded(). - mDisplayContent.mAcitvityDisplay.ensureActivitiesVisible(null /* starting */, - 0 /* configChanges */, !PRESERVE_WINDOWS, true /* notifyClients */); - } - } - }; } @Override @@ -115,9 +60,6 @@ public class TaskTapPointerEventListener implements PointerEventListener { mService.mTaskPositioningController.handleTapOutsideTask( mDisplayContent, x, y); } - mLastDownX = x; - mLastDownY = y; - mHandler.post(mMoveDisplayToTop); } } break; @@ -178,17 +120,4 @@ public class TaskTapPointerEventListener implements PointerEventListener { mTouchExcludeRegion.set(newRegion); } } - - private int getDisplayId() { - return mDisplayContent.getDisplayId(); - } - - private boolean inputMethodWindowContains(int x, int y) { - final WindowState inputMethodWindow = mDisplayContent.mInputMethodWindow; - if (inputMethodWindow == null || !inputMethodWindow.isVisibleLw()) { - return false; - } - inputMethodWindow.getTouchableRegion(mTmpRegion); - return mTmpRegion.contains(x, y); - } } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 20d02ee5355e..8dfb02efb526 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -73,6 +73,7 @@ import static com.android.internal.util.LatencyTracker.ACTION_ROTATE_SCREEN; import static com.android.server.LockGuard.INDEX_WINDOW; import static com.android.server.LockGuard.installLock; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; +import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_BOOT; @@ -4519,6 +4520,7 @@ public class WindowManagerService extends IWindowManager.Stub public static final int SET_RUNNING_REMOTE_ANIMATION = 59; public static final int ANIMATION_FAILSAFE = 60; public static final int RECOMPUTE_FOCUS = 61; + public static final int ON_POINTER_DOWN_OUTSIDE_FOCUS = 62; /** * Used to denote that an integer field in a message will not be used. @@ -4910,6 +4912,13 @@ public class WindowManagerService extends IWindowManager.Stub } break; } + case ON_POINTER_DOWN_OUTSIDE_FOCUS: { + synchronized (mGlobalLock) { + final IBinder touchedToken = (IBinder) msg.obj; + onPointerDownOutsideFocusLocked(touchedToken); + } + break; + } } if (DEBUG_WINDOW_TRACE) { Slog.v(TAG_WM, "handleMessage: exit"); @@ -7522,22 +7531,24 @@ public class WindowManagerService extends IWindowManager.Stub @Override public boolean injectInputAfterTransactionsApplied(InputEvent ev, int mode) { - boolean shouldWaitForAnimComplete = false; + boolean shouldWaitForAnimToComplete = false; if (ev instanceof KeyEvent) { KeyEvent keyEvent = (KeyEvent) ev; - shouldWaitForAnimComplete = keyEvent.getSource() == InputDevice.SOURCE_MOUSE + shouldWaitForAnimToComplete = keyEvent.getSource() == InputDevice.SOURCE_MOUSE || keyEvent.getAction() == KeyEvent.ACTION_DOWN; } else if (ev instanceof MotionEvent) { MotionEvent motionEvent = (MotionEvent) ev; - shouldWaitForAnimComplete = motionEvent.getSource() == InputDevice.SOURCE_MOUSE + shouldWaitForAnimToComplete = motionEvent.getSource() == InputDevice.SOURCE_MOUSE || motionEvent.getAction() == MotionEvent.ACTION_DOWN; } - if (shouldWaitForAnimComplete) { + if (shouldWaitForAnimToComplete) { waitForAnimationsToComplete(); synchronized (mGlobalLock) { mWindowPlacerLocked.performSurfacePlacementIfScheduled(); + mRoot.forAllDisplays(displayContent -> + displayContent.getInputMonitor().updateInputWindowsImmediately()); } new SurfaceControl.Transaction().syncInputWindows().apply(true); @@ -7569,4 +7580,37 @@ public class WindowManagerService extends IWindowManager.Stub mGlobalLock.notifyAll(); } } + + private void onPointerDownOutsideFocusLocked(IBinder touchedToken) { + final WindowState touchedWindow = windowForClientLocked(null, touchedToken, false); + if (touchedWindow == null) { + return; + } + + final DisplayContent displayContent = touchedWindow.getDisplayContent(); + if (displayContent == null) { + return; + } + + if (!touchedWindow.canReceiveKeys()) { + // If the window that received the input event cannot receive keys, don't move the + // display it's on to the top since that window won't be able to get focus anyway. + return; + } + + final WindowContainer parent = displayContent.getParent(); + if (parent != null && parent.getTopChild() != displayContent) { + parent.positionChildAt(WindowContainer.POSITION_TOP, displayContent, + true /* includingParents */); + // For compatibility, only the topmost activity is allowed to be resumed for pre-Q + // app. Ensure the topmost activities are resumed whenever a display is moved to top. + // TODO(b/123761773): Investigate whether we can move this into + // RootActivityContainer#updateTopResumedActivityIfNeeded(). Currently, it is risky + // to do so because it seems possible to resume activities as part of a larger + // transaction and it's too early to resume based on current order when performing + // updateTopResumedActivityIfNeeded(). + displayContent.mAcitvityDisplay.ensureActivitiesVisible(null /* starting */, + 0 /* configChanges */, !PRESERVE_WINDOWS, true /* notifyClients */); + } + } } diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp index a7423ea40bf0..e1318af32301 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -52,6 +52,7 @@ cc_library_static { "com_android_server_GraphicsStatsService.cpp", "com_android_server_am_AppCompactor.cpp", "onload.cpp", + ":lib_networkStatsFactory_native", ], include_dirs: [ @@ -151,3 +152,10 @@ cc_defaults { } } } + +filegroup { + name: "lib_networkStatsFactory_native", + srcs: [ + "com_android_server_net_NetworkStatsFactory.cpp", + ], +} diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index 3d6c8684e6d8..204a1ea977e7 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -47,6 +47,7 @@ #include <input/PointerController.h> #include <input/SpriteController.h> +#include <ui/Region.h> #include <inputflinger/InputManager.h> @@ -137,8 +138,6 @@ static struct { jmethodID getAffineTransform; } gTouchCalibrationClassInfo; - - // --- Global functions --- template<typename T> @@ -188,7 +187,6 @@ static std::string getStringElementFromJavaArray(JNIEnv* env, jobjectArray array return result; } - // --- NativeInputManager --- class NativeInputManager : public virtual RefBase, @@ -207,8 +205,12 @@ public: void setDisplayViewports(JNIEnv* env, jobjectArray viewportObjArray); - status_t registerInputChannel(JNIEnv* env, const sp<InputChannel>& inputChannel, int32_t displayId); + status_t registerInputChannel(JNIEnv* env, const sp<InputChannel>& inputChannel, + int32_t displayId); + status_t registerInputMonitor(JNIEnv* env, const sp<InputChannel>& inputChannel, + int32_t displayId, bool isGestureMonitor); status_t unregisterInputChannel(JNIEnv* env, const sp<InputChannel>& inputChannel); + status_t pilferPointers(const sp<IBinder>& token); void setInputWindows(JNIEnv* env, jobjectArray windowHandleObjArray, int32_t displayId); void setFocusedApplication(JNIEnv* env, int32_t displayId, jobject applicationHandleObj); @@ -443,12 +445,24 @@ status_t NativeInputManager::registerInputChannel(JNIEnv* /* env */, inputChannel, displayId); } +status_t NativeInputManager::registerInputMonitor(JNIEnv* /* env */, + const sp<InputChannel>& inputChannel, int32_t displayId, bool isGestureMonitor) { + ATRACE_CALL(); + return mInputManager->getDispatcher()->registerInputMonitor( + inputChannel, displayId, isGestureMonitor); +} + status_t NativeInputManager::unregisterInputChannel(JNIEnv* /* env */, const sp<InputChannel>& inputChannel) { ATRACE_CALL(); return mInputManager->getDispatcher()->unregisterInputChannel(inputChannel); } +status_t NativeInputManager::pilferPointers(const sp<IBinder>& token) { + ATRACE_CALL(); + return mInputManager->getDispatcher()->pilferPointers(token); +} + void NativeInputManager::getReaderConfiguration(InputReaderConfiguration* outConfig) { ATRACE_CALL(); JNIEnv* env = jniEnv(); @@ -1396,7 +1410,6 @@ static void nativeRegisterInputChannel(JNIEnv* env, jclass /* clazz */, throwInputChannelNotInitialized(env); return; } - bool monitor = inputChannel->getToken() == nullptr && displayId != ADISPLAY_ID_NONE; status_t status = im->registerInputChannel(env, inputChannel, displayId); @@ -1407,10 +1420,33 @@ static void nativeRegisterInputChannel(JNIEnv* env, jclass /* clazz */, return; } - // If inputWindowHandle is null and displayId >= 0, treat inputChannel as monitor. - if (!monitor) { - android_view_InputChannel_setDisposeCallback(env, inputChannelObj, - handleInputChannelDisposed, im); + android_view_InputChannel_setDisposeCallback(env, inputChannelObj, + handleInputChannelDisposed, im); +} + +static void nativeRegisterInputMonitor(JNIEnv* env, jclass /* clazz */, + jlong ptr, jobject inputChannelObj, jint displayId, jboolean isGestureMonitor) { + NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); + + sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env, + inputChannelObj); + if (inputChannel == nullptr) { + throwInputChannelNotInitialized(env); + return; + } + + if (displayId == ADISPLAY_ID_NONE) { + std::string message = "InputChannel used as a monitor must be associated with a display"; + jniThrowRuntimeException(env, message.c_str()); + return; + } + + status_t status = im->registerInputMonitor(env, inputChannel, displayId, isGestureMonitor); + + if (status) { + std::string message = StringPrintf("Failed to register input channel. status=%d", status); + jniThrowRuntimeException(env, message.c_str()); + return; } } @@ -1435,6 +1471,13 @@ static void nativeUnregisterInputChannel(JNIEnv* env, jclass /* clazz */, } } +static void nativePilferPointers(JNIEnv* env, jclass /* clazz */, jlong ptr, jobject tokenObj) { + NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); + sp<IBinder> token = ibinderForJavaObject(env, tokenObj); + im->pilferPointers(token); +} + + static void nativeSetInputFilterEnabled(JNIEnv* /* env */, jclass /* clazz */, jlong ptr, jboolean enabled) { NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); @@ -1697,8 +1740,13 @@ static const JNINativeMethod gInputManagerMethods[] = { { "nativeRegisterInputChannel", "(JLandroid/view/InputChannel;I)V", (void*) nativeRegisterInputChannel }, + { "nativeRegisterInputMonitor", + "(JLandroid/view/InputChannel;IZ)V", + (void*) nativeRegisterInputMonitor}, { "nativeUnregisterInputChannel", "(JLandroid/view/InputChannel;)V", (void*) nativeUnregisterInputChannel }, + { "nativePilferPointers", "(JLandroid/os/IBinder;)V", + (void*) nativePilferPointers }, { "nativeSetInputFilterEnabled", "(JZ)V", (void*) nativeSetInputFilterEnabled }, { "nativeInjectInputEvent", "(JLandroid/view/InputEvent;IIIII)I", @@ -1792,7 +1840,7 @@ int register_android_server_InputManager(JNIEnv* env) { GET_METHOD_ID(gServiceClassInfo.notifyInputChannelBroken, clazz, "notifyInputChannelBroken", "(Landroid/os/IBinder;)V"); - + GET_METHOD_ID(gServiceClassInfo.notifyFocusChanged, clazz, "notifyFocusChanged", "(Landroid/os/IBinder;Landroid/os/IBinder;)V"); diff --git a/core/jni/com_android_internal_net_NetworkStatsFactory.cpp b/services/core/jni/com_android_server_net_NetworkStatsFactory.cpp index 8259ffcd419d..9cd743b3466a 100644 --- a/core/jni/com_android_internal_net_NetworkStatsFactory.cpp +++ b/services/core/jni/com_android_server_net_NetworkStatsFactory.cpp @@ -21,9 +21,9 @@ #include <sys/stat.h> #include <sys/types.h> -#include <core_jni_helpers.h> #include <jni.h> +#include <nativehelper/JNIHelp.h> #include <nativehelper/ScopedUtfChars.h> #include <nativehelper/ScopedLocalRef.h> #include <nativehelper/ScopedPrimitiveArray.h> @@ -333,29 +333,27 @@ static const JNINativeMethod gMethods[] = { (void*) readNetworkStatsDev }, }; -int register_com_android_internal_net_NetworkStatsFactory(JNIEnv* env) { - int err = RegisterMethodsOrDie(env, - "com/android/internal/net/NetworkStatsFactory", gMethods, +int register_android_server_net_NetworkStatsFactory(JNIEnv* env) { + int err = jniRegisterNativeMethods(env, "com/android/server/net/NetworkStatsFactory", gMethods, NELEM(gMethods)); - - gStringClass = FindClassOrDie(env, "java/lang/String"); - gStringClass = MakeGlobalRefOrDie(env, gStringClass); - - jclass clazz = FindClassOrDie(env, "android/net/NetworkStats"); - gNetworkStatsClassInfo.size = GetFieldIDOrDie(env, clazz, "size", "I"); - gNetworkStatsClassInfo.capacity = GetFieldIDOrDie(env, clazz, "capacity", "I"); - gNetworkStatsClassInfo.iface = GetFieldIDOrDie(env, clazz, "iface", "[Ljava/lang/String;"); - gNetworkStatsClassInfo.uid = GetFieldIDOrDie(env, clazz, "uid", "[I"); - gNetworkStatsClassInfo.set = GetFieldIDOrDie(env, clazz, "set", "[I"); - gNetworkStatsClassInfo.tag = GetFieldIDOrDie(env, clazz, "tag", "[I"); - gNetworkStatsClassInfo.metered = GetFieldIDOrDie(env, clazz, "metered", "[I"); - gNetworkStatsClassInfo.roaming = GetFieldIDOrDie(env, clazz, "roaming", "[I"); - gNetworkStatsClassInfo.defaultNetwork = GetFieldIDOrDie(env, clazz, "defaultNetwork", "[I"); - gNetworkStatsClassInfo.rxBytes = GetFieldIDOrDie(env, clazz, "rxBytes", "[J"); - gNetworkStatsClassInfo.rxPackets = GetFieldIDOrDie(env, clazz, "rxPackets", "[J"); - gNetworkStatsClassInfo.txBytes = GetFieldIDOrDie(env, clazz, "txBytes", "[J"); - gNetworkStatsClassInfo.txPackets = GetFieldIDOrDie(env, clazz, "txPackets", "[J"); - gNetworkStatsClassInfo.operations = GetFieldIDOrDie(env, clazz, "operations", "[J"); + gStringClass = env->FindClass("java/lang/String"); + gStringClass = static_cast<jclass>(env->NewGlobalRef(gStringClass)); + + jclass clazz = env->FindClass("android/net/NetworkStats"); + gNetworkStatsClassInfo.size = env->GetFieldID(clazz, "size", "I"); + gNetworkStatsClassInfo.capacity = env->GetFieldID(clazz, "capacity", "I"); + gNetworkStatsClassInfo.iface = env->GetFieldID(clazz, "iface", "[Ljava/lang/String;"); + gNetworkStatsClassInfo.uid = env->GetFieldID(clazz, "uid", "[I"); + gNetworkStatsClassInfo.set = env->GetFieldID(clazz, "set", "[I"); + gNetworkStatsClassInfo.tag = env->GetFieldID(clazz, "tag", "[I"); + gNetworkStatsClassInfo.metered = env->GetFieldID(clazz, "metered", "[I"); + gNetworkStatsClassInfo.roaming = env->GetFieldID(clazz, "roaming", "[I"); + gNetworkStatsClassInfo.defaultNetwork = env->GetFieldID(clazz, "defaultNetwork", "[I"); + gNetworkStatsClassInfo.rxBytes = env->GetFieldID(clazz, "rxBytes", "[J"); + gNetworkStatsClassInfo.rxPackets = env->GetFieldID(clazz, "rxPackets", "[J"); + gNetworkStatsClassInfo.txBytes = env->GetFieldID(clazz, "txBytes", "[J"); + gNetworkStatsClassInfo.txPackets = env->GetFieldID(clazz, "txPackets", "[J"); + gNetworkStatsClassInfo.operations = env->GetFieldID(clazz, "operations", "[J"); return err; } diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp index 2cfaebf4632e..cce96ff5f438 100644 --- a/services/core/jni/onload.cpp +++ b/services/core/jni/onload.cpp @@ -52,6 +52,7 @@ int register_android_server_HardwarePropertiesManagerService(JNIEnv* env); int register_android_server_SyntheticPasswordManager(JNIEnv* env); int register_android_server_GraphicsStatsService(JNIEnv* env); int register_android_hardware_display_DisplayViewport(JNIEnv* env); +int register_android_server_net_NetworkStatsFactory(JNIEnv* env); int register_android_server_net_NetworkStatsService(JNIEnv* env); int register_android_server_security_VerityUtils(JNIEnv* env); int register_android_server_am_AppCompactor(JNIEnv* env); @@ -100,6 +101,7 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) register_android_server_SyntheticPasswordManager(env); register_android_server_GraphicsStatsService(env); register_android_hardware_display_DisplayViewport(env); + register_android_server_net_NetworkStatsFactory(env); register_android_server_net_NetworkStatsService(env); register_android_server_security_VerityUtils(env); register_android_server_am_AppCompactor(env); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index b5c845a9d012..d014c0a34755 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -14123,6 +14123,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public void installUpdateFromFile(ComponentName admin, ParcelFileDescriptor updateFileDescriptor, StartInstallingUpdateCallback callback) { + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.INSTALL_SYSTEM_UPDATE) + .setAdmin(admin) + .setBoolean(isDeviceAB()) + .write(); enforceDeviceOwner(admin); final long id = mInjector.binderClearCallingIdentity(); try { @@ -14135,11 +14140,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { mContext, updateFileDescriptor, callback, mInjector, mConstants); } updateInstaller.startInstallUpdate(); - DevicePolicyEventLogger - .createEvent(DevicePolicyEnums.INSTALL_SYSTEM_UPDATE) - .setAdmin(admin) - .setBoolean(isDeviceAB()) - .write(); } finally { mInjector.binderRestoreCallingIdentity(id); } diff --git a/services/net/Android.bp b/services/net/Android.bp index 8f48f5b3d292..f73a285c1a94 100644 --- a/services/net/Android.bp +++ b/services/net/Android.bp @@ -59,6 +59,7 @@ java_library_static { srcs: ["java/**/*.java"], static_libs: [ "dnsresolver_aidl_interface-java", + "ipmemorystore-client", "netd_aidl_interface-java", "networkstack-aidl-interfaces-java", ] diff --git a/services/net/java/android/net/IIpMemoryStore.aidl b/services/net/java/android/net/IIpMemoryStore.aidl index 6f88dec8dee9..63feae65756f 100644 --- a/services/net/java/android/net/IIpMemoryStore.aidl +++ b/services/net/java/android/net/IIpMemoryStore.aidl @@ -20,8 +20,8 @@ import android.net.ipmemorystore.Blob; import android.net.ipmemorystore.NetworkAttributesParcelable; import android.net.ipmemorystore.IOnBlobRetrievedListener; import android.net.ipmemorystore.IOnL2KeyResponseListener; -import android.net.ipmemorystore.IOnNetworkAttributesRetrieved; -import android.net.ipmemorystore.IOnSameNetworkResponseListener; +import android.net.ipmemorystore.IOnNetworkAttributesRetrievedListener; +import android.net.ipmemorystore.IOnSameL3NetworkResponseListener; import android.net.ipmemorystore.IOnStatusListener; /** {@hide} */ @@ -84,7 +84,7 @@ oneway interface IIpMemoryStore { * @param listener The listener that will be invoked to return the answer. * @return (through the listener) A SameL3NetworkResponse containing the answer and confidence. */ - void isSameNetwork(String l2Key1, String l2Key2, IOnSameNetworkResponseListener listener); + void isSameNetwork(String l2Key1, String l2Key2, IOnSameL3NetworkResponseListener listener); /** * Retrieve the network attributes for a key. @@ -95,7 +95,7 @@ oneway interface IIpMemoryStore { * @return (through the listener) The network attributes and the L2 key associated with * the query. */ - void retrieveNetworkAttributes(String l2Key, IOnNetworkAttributesRetrieved listener); + void retrieveNetworkAttributes(String l2Key, IOnNetworkAttributesRetrievedListener listener); /** * Retrieve previously stored private data. diff --git a/services/net/java/android/net/IpMemoryStoreClient.java b/services/net/java/android/net/IpMemoryStoreClient.java index 2f4fdbd8a4a7..379c017b2990 100644 --- a/services/net/java/android/net/IpMemoryStoreClient.java +++ b/services/net/java/android/net/IpMemoryStoreClient.java @@ -20,14 +20,13 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.net.ipmemorystore.Blob; -import android.net.ipmemorystore.IOnBlobRetrievedListener; -import android.net.ipmemorystore.IOnL2KeyResponseListener; -import android.net.ipmemorystore.IOnNetworkAttributesRetrieved; -import android.net.ipmemorystore.IOnSameNetworkResponseListener; -import android.net.ipmemorystore.IOnStatusListener; import android.net.ipmemorystore.NetworkAttributes; +import android.net.ipmemorystore.OnBlobRetrievedListener; +import android.net.ipmemorystore.OnL2KeyResponseListener; +import android.net.ipmemorystore.OnNetworkAttributesRetrievedListener; +import android.net.ipmemorystore.OnSameL3NetworkResponseListener; +import android.net.ipmemorystore.OnStatusListener; import android.net.ipmemorystore.Status; -import android.net.ipmemorystore.StatusParcelable; import android.os.RemoteException; import android.util.Log; @@ -50,12 +49,6 @@ public abstract class IpMemoryStoreClient { @NonNull protected abstract IIpMemoryStore getService() throws InterruptedException, ExecutionException; - protected StatusParcelable internalErrorStatus() { - final StatusParcelable error = new StatusParcelable(); - error.resultCode = Status.ERROR_UNKNOWN; - return error; - } - /** * Store network attributes for a given L2 key. * If L2Key is null, choose automatically from the attributes ; passing null is equivalent to @@ -74,12 +67,13 @@ public abstract class IpMemoryStoreClient { */ public void storeNetworkAttributes(@NonNull final String l2Key, @NonNull final NetworkAttributes attributes, - @Nullable final IOnStatusListener listener) { + @Nullable final OnStatusListener listener) { try { try { - getService().storeNetworkAttributes(l2Key, attributes.toParcelable(), listener); + getService().storeNetworkAttributes(l2Key, attributes.toParcelable(), + OnStatusListener.toAIDL(listener)); } catch (InterruptedException | ExecutionException m) { - listener.onComplete(internalErrorStatus()); + listener.onComplete(new Status(Status.ERROR_UNKNOWN)); } } catch (RemoteException e) { Log.e(TAG, "Error storing network attributes", e); @@ -99,12 +93,13 @@ public abstract class IpMemoryStoreClient { */ public void storeBlob(@NonNull final String l2Key, @NonNull final String clientId, @NonNull final String name, @NonNull final Blob data, - @Nullable final IOnStatusListener listener) { + @Nullable final OnStatusListener listener) { try { try { - getService().storeBlob(l2Key, clientId, name, data, listener); + getService().storeBlob(l2Key, clientId, name, data, + OnStatusListener.toAIDL(listener)); } catch (InterruptedException | ExecutionException m) { - listener.onComplete(internalErrorStatus()); + listener.onComplete(new Status(Status.ERROR_UNKNOWN)); } } catch (RemoteException e) { Log.e(TAG, "Error storing blob", e); @@ -126,12 +121,13 @@ public abstract class IpMemoryStoreClient { * Through the listener, returns the L2 key if one matched, or null. */ public void findL2Key(@NonNull final NetworkAttributes attributes, - @NonNull final IOnL2KeyResponseListener listener) { + @NonNull final OnL2KeyResponseListener listener) { try { try { - getService().findL2Key(attributes.toParcelable(), listener); + getService().findL2Key(attributes.toParcelable(), + OnL2KeyResponseListener.toAIDL(listener)); } catch (InterruptedException | ExecutionException m) { - listener.onL2KeyResponse(internalErrorStatus(), null); + listener.onL2KeyResponse(new Status(Status.ERROR_UNKNOWN), null); } } catch (RemoteException e) { Log.e(TAG, "Error finding L2 Key", e); @@ -148,12 +144,13 @@ public abstract class IpMemoryStoreClient { * Through the listener, a SameL3NetworkResponse containing the answer and confidence. */ public void isSameNetwork(@NonNull final String l2Key1, @NonNull final String l2Key2, - @NonNull final IOnSameNetworkResponseListener listener) { + @NonNull final OnSameL3NetworkResponseListener listener) { try { try { - getService().isSameNetwork(l2Key1, l2Key2, listener); + getService().isSameNetwork(l2Key1, l2Key2, + OnSameL3NetworkResponseListener.toAIDL(listener)); } catch (InterruptedException | ExecutionException m) { - listener.onSameNetworkResponse(internalErrorStatus(), null); + listener.onSameL3NetworkResponse(new Status(Status.ERROR_UNKNOWN), null); } } catch (RemoteException e) { Log.e(TAG, "Error checking for network sameness", e); @@ -170,12 +167,13 @@ public abstract class IpMemoryStoreClient { * the query. */ public void retrieveNetworkAttributes(@NonNull final String l2Key, - @NonNull final IOnNetworkAttributesRetrieved listener) { + @NonNull final OnNetworkAttributesRetrievedListener listener) { try { try { - getService().retrieveNetworkAttributes(l2Key, listener); + getService().retrieveNetworkAttributes(l2Key, + OnNetworkAttributesRetrievedListener.toAIDL(listener)); } catch (InterruptedException | ExecutionException m) { - listener.onNetworkAttributesRetrieved(internalErrorStatus(), null, null); + listener.onNetworkAttributesRetrieved(new Status(Status.ERROR_UNKNOWN), null, null); } } catch (RemoteException e) { Log.e(TAG, "Error retrieving network attributes", e); @@ -194,12 +192,13 @@ public abstract class IpMemoryStoreClient { * and the name of the data associated with the query. */ public void retrieveBlob(@NonNull final String l2Key, @NonNull final String clientId, - @NonNull final String name, @NonNull final IOnBlobRetrievedListener listener) { + @NonNull final String name, @NonNull final OnBlobRetrievedListener listener) { try { try { - getService().retrieveBlob(l2Key, clientId, name, listener); + getService().retrieveBlob(l2Key, clientId, name, + OnBlobRetrievedListener.toAIDL(listener)); } catch (InterruptedException | ExecutionException m) { - listener.onBlobRetrieved(internalErrorStatus(), null, null, null); + listener.onBlobRetrieved(new Status(Status.ERROR_UNKNOWN), null, null, null); } } catch (RemoteException e) { Log.e(TAG, "Error retrieving blob", e); diff --git a/services/net/java/android/net/ipmemorystore/IOnNetworkAttributesRetrieved.aidl b/services/net/java/android/net/ipmemorystore/IOnNetworkAttributesRetrievedListener.aidl index fb4ca3b97895..870e217eb5b7 100644 --- a/services/net/java/android/net/ipmemorystore/IOnNetworkAttributesRetrieved.aidl +++ b/services/net/java/android/net/ipmemorystore/IOnNetworkAttributesRetrievedListener.aidl @@ -20,7 +20,7 @@ import android.net.ipmemorystore.NetworkAttributesParcelable; import android.net.ipmemorystore.StatusParcelable; /** {@hide} */ -oneway interface IOnNetworkAttributesRetrieved { +oneway interface IOnNetworkAttributesRetrievedListener { /** * Network attributes were fetched for the specified L2 key. While the L2 key will never * be null, the attributes may be if no data is stored about this L2 key. diff --git a/services/net/java/android/net/ipmemorystore/IOnSameNetworkResponseListener.aidl b/services/net/java/android/net/ipmemorystore/IOnSameL3NetworkResponseListener.aidl index 294bd3bd4012..b8ccfb99fddd 100644 --- a/services/net/java/android/net/ipmemorystore/IOnSameNetworkResponseListener.aidl +++ b/services/net/java/android/net/ipmemorystore/IOnSameL3NetworkResponseListener.aidl @@ -20,10 +20,10 @@ import android.net.ipmemorystore.SameL3NetworkResponseParcelable; import android.net.ipmemorystore.StatusParcelable; /** {@hide} */ -oneway interface IOnSameNetworkResponseListener { +oneway interface IOnSameL3NetworkResponseListener { /** * The memory store has come up with the answer to a query that was sent. */ - void onSameNetworkResponse(in StatusParcelable status, + void onSameL3NetworkResponse(in StatusParcelable status, in SameL3NetworkResponseParcelable response); } diff --git a/services/net/java/android/net/ipmemorystore/OnBlobRetrievedListener.java b/services/net/java/android/net/ipmemorystore/OnBlobRetrievedListener.java new file mode 100644 index 000000000000..9685ff6dd3ca --- /dev/null +++ b/services/net/java/android/net/ipmemorystore/OnBlobRetrievedListener.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2019 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.ipmemorystore; + +import android.annotation.NonNull; + +/** + * A listener for the IpMemoryStore to return a blob. + * @hide + */ +public interface OnBlobRetrievedListener { + /** + * The memory store has come up with the answer to a query that was sent. + */ + void onBlobRetrieved(Status status, String l2Key, String name, Blob blob); + + /** Converts this OnBlobRetrievedListener to a parcelable object */ + @NonNull + static IOnBlobRetrievedListener toAIDL(final OnBlobRetrievedListener listener) { + return new IOnBlobRetrievedListener.Stub() { + @Override + public void onBlobRetrieved(final StatusParcelable statusParcelable, final String l2Key, + final String name, final Blob blob) { + listener.onBlobRetrieved(new Status(statusParcelable), l2Key, name, blob); + } + }; + } +} diff --git a/services/net/java/android/net/ipmemorystore/OnL2KeyResponseListener.java b/services/net/java/android/net/ipmemorystore/OnL2KeyResponseListener.java new file mode 100644 index 000000000000..80209c574203 --- /dev/null +++ b/services/net/java/android/net/ipmemorystore/OnL2KeyResponseListener.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2019 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.ipmemorystore; + +import android.annotation.NonNull; + +/** + * A listener for the IpMemoryStore to return a L2 key. + * @hide + */ +public interface OnL2KeyResponseListener { + /** + * The operation has completed with the specified status. + */ + void onL2KeyResponse(Status status, String l2Key); + + /** Converts this OnL2KeyResponseListener to a parcelable object */ + @NonNull + static IOnL2KeyResponseListener toAIDL(final OnL2KeyResponseListener listener) { + return new IOnL2KeyResponseListener.Stub() { + @Override + public void onL2KeyResponse(final StatusParcelable statusParcelable, + final String l2Key) { + listener.onL2KeyResponse(new Status(statusParcelable), l2Key); + } + }; + } +} diff --git a/services/net/java/android/net/ipmemorystore/OnNetworkAttributesRetrievedListener.java b/services/net/java/android/net/ipmemorystore/OnNetworkAttributesRetrievedListener.java new file mode 100644 index 000000000000..f0f6f4016139 --- /dev/null +++ b/services/net/java/android/net/ipmemorystore/OnNetworkAttributesRetrievedListener.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2019 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.ipmemorystore; + +import android.annotation.NonNull; + +/** + * A listener for the IpMemoryStore to return network attributes. + * @hide + */ +public interface OnNetworkAttributesRetrievedListener { + /** + * The memory store has come up with the answer to a query that was sent. + */ + void onNetworkAttributesRetrieved(Status status, String l2Key, NetworkAttributes attributes); + + /** Converts this OnNetworkAttributesRetrievedListener to a parcelable object */ + @NonNull + static IOnNetworkAttributesRetrievedListener toAIDL( + final OnNetworkAttributesRetrievedListener listener) { + return new IOnNetworkAttributesRetrievedListener.Stub() { + @Override + public void onNetworkAttributesRetrieved(final StatusParcelable statusParcelable, + final String l2Key, + final NetworkAttributesParcelable networkAttributesParcelable) { + listener.onNetworkAttributesRetrieved( + new Status(statusParcelable), l2Key, + new NetworkAttributes(networkAttributesParcelable)); + } + }; + } +} diff --git a/services/net/java/android/net/ipmemorystore/OnSameL3NetworkResponseListener.java b/services/net/java/android/net/ipmemorystore/OnSameL3NetworkResponseListener.java new file mode 100644 index 000000000000..ba1e0e6f2b9f --- /dev/null +++ b/services/net/java/android/net/ipmemorystore/OnSameL3NetworkResponseListener.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2019 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.ipmemorystore; + +import android.annotation.NonNull; + +/** + * A listener for the IpMemoryStore to return a response about network sameness. + * @hide + */ +public interface OnSameL3NetworkResponseListener { + /** + * The memory store has come up with the answer to a query that was sent. + */ + void onSameL3NetworkResponse(Status status, SameL3NetworkResponse response); + + /** Converts this OnSameL3NetworkResponseListener to a parcelable object */ + @NonNull + static IOnSameL3NetworkResponseListener toAIDL(final OnSameL3NetworkResponseListener listener) { + return new IOnSameL3NetworkResponseListener.Stub() { + @Override + public void onSameL3NetworkResponse(final StatusParcelable statusParcelable, + final SameL3NetworkResponseParcelable sameL3NetworkResponseParcelable) { + listener.onSameL3NetworkResponse( + new Status(statusParcelable), + new SameL3NetworkResponse(sameL3NetworkResponseParcelable)); + } + }; + } +} diff --git a/services/net/java/android/net/ipmemorystore/OnStatusListener.java b/services/net/java/android/net/ipmemorystore/OnStatusListener.java new file mode 100644 index 000000000000..0de16660ff5f --- /dev/null +++ b/services/net/java/android/net/ipmemorystore/OnStatusListener.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2019 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.ipmemorystore; + +import android.annotation.NonNull; + +/** + * A listener for the IpMemoryStore to return a status to a client. + * @hide + */ +public interface OnStatusListener { + /** + * The operation has completed with the specified status. + */ + void onComplete(Status status); + + /** Converts this OnStatusListener to a parcelable object */ + @NonNull + static IOnStatusListener toAIDL(final OnStatusListener listener) { + return new IOnStatusListener.Stub() { + @Override + public void onComplete(final StatusParcelable statusParcelable) { + listener.onComplete(new Status(statusParcelable)); + } + }; + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/am/AppCompactorTest.java b/services/tests/mockingservicestests/src/com/android/server/am/AppCompactorTest.java index b0c97d1b4f48..475901a75d0d 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/AppCompactorTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/AppCompactorTest.java @@ -25,6 +25,7 @@ import android.os.Handler; import android.os.HandlerThread; import android.platform.test.annotations.Presubmit; import android.provider.DeviceConfig; +import android.text.TextUtils; import com.android.server.appop.AppOpsService; import com.android.server.testables.TestableDeviceConfig; @@ -38,6 +39,8 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import java.io.File; +import java.util.HashSet; +import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -103,6 +106,22 @@ public final class AppCompactorTest { AppCompactor.DEFAULT_COMPACT_THROTTLE_4); assertThat(mCompactorUnderTest.mStatsdSampleRate).isEqualTo( AppCompactor.DEFAULT_STATSD_SAMPLE_RATE); + assertThat(mCompactorUnderTest.mFullAnonRssThrottleKb).isEqualTo( + AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB); + assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo( + AppCompactor.DEFAULT_COMPACT_THROTTLE_5); + assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo( + AppCompactor.DEFAULT_COMPACT_THROTTLE_6); + assertThat(mCompactorUnderTest.mFullAnonRssThrottleKb).isEqualTo( + AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB); + assertThat(mCompactorUnderTest.mFullDeltaRssThrottleKb).isEqualTo( + AppCompactor.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB); + + Set<Integer> expected = new HashSet<>(); + for (String s : TextUtils.split(AppCompactor.DEFAULT_COMPACT_PROC_STATE_THROTTLE, ",")) { + expected.add(Integer.parseInt(s)); + } + assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactlyElementsIn(expected); } @Test @@ -139,6 +158,14 @@ public final class AppCompactorTest { DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, AppCompactor.KEY_COMPACT_STATSD_SAMPLE_RATE, Float.toString(AppCompactor.DEFAULT_STATSD_SAMPLE_RATE + 0.1f), false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + AppCompactor.KEY_COMPACT_FULL_RSS_THROTTLE_KB, + Long.toString(AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB + 1), false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + AppCompactor.KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB, + Long.toString(AppCompactor.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB + 1), false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, "1,2,3", false); // Then calling init will read and set that flag. mCompactorUnderTest.init(); @@ -157,12 +184,19 @@ public final class AppCompactorTest { AppCompactor.DEFAULT_COMPACT_THROTTLE_3 + 1); assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo( AppCompactor.DEFAULT_COMPACT_THROTTLE_4 + 1); + assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo( + AppCompactor.DEFAULT_COMPACT_THROTTLE_5 + 1); + assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo( + AppCompactor.DEFAULT_COMPACT_THROTTLE_6 + 1); assertThat(mCompactorUnderTest.mStatsdSampleRate).isEqualTo( AppCompactor.DEFAULT_STATSD_SAMPLE_RATE + 0.1f); assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo( AppCompactor.DEFAULT_COMPACT_THROTTLE_5 + 1); assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo( AppCompactor.DEFAULT_COMPACT_THROTTLE_6 + 1); + assertThat(mCompactorUnderTest.mFullAnonRssThrottleKb).isEqualTo( + AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB + 1); + assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactly(1, 2, 3); } @Test @@ -321,6 +355,10 @@ public final class AppCompactorTest { AppCompactor.DEFAULT_COMPACT_THROTTLE_3); assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo( AppCompactor.DEFAULT_COMPACT_THROTTLE_4); + assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo( + AppCompactor.DEFAULT_COMPACT_THROTTLE_5); + assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo( + AppCompactor.DEFAULT_COMPACT_THROTTLE_6); // Repeat for each of the throttle keys. mCountDown = new CountDownLatch(1); @@ -335,6 +373,10 @@ public final class AppCompactorTest { AppCompactor.DEFAULT_COMPACT_THROTTLE_3); assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo( AppCompactor.DEFAULT_COMPACT_THROTTLE_4); + assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo( + AppCompactor.DEFAULT_COMPACT_THROTTLE_5); + assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo( + AppCompactor.DEFAULT_COMPACT_THROTTLE_6); mCountDown = new CountDownLatch(1); DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, @@ -348,6 +390,10 @@ public final class AppCompactorTest { AppCompactor.DEFAULT_COMPACT_THROTTLE_3); assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo( AppCompactor.DEFAULT_COMPACT_THROTTLE_4); + assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo( + AppCompactor.DEFAULT_COMPACT_THROTTLE_5); + assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo( + AppCompactor.DEFAULT_COMPACT_THROTTLE_6); mCountDown = new CountDownLatch(1); DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, @@ -361,13 +407,51 @@ public final class AppCompactorTest { AppCompactor.DEFAULT_COMPACT_THROTTLE_3); assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo( AppCompactor.DEFAULT_COMPACT_THROTTLE_4); + assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo( + AppCompactor.DEFAULT_COMPACT_THROTTLE_5); + assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo( + AppCompactor.DEFAULT_COMPACT_THROTTLE_6); + + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + AppCompactor.KEY_COMPACT_THROTTLE_5, "foo", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); + assertThat(mCompactorUnderTest.mCompactThrottleSomeSome).isEqualTo( + AppCompactor.DEFAULT_COMPACT_THROTTLE_1); + assertThat(mCompactorUnderTest.mCompactThrottleSomeFull).isEqualTo( + AppCompactor.DEFAULT_COMPACT_THROTTLE_2); + assertThat(mCompactorUnderTest.mCompactThrottleFullSome).isEqualTo( + AppCompactor.DEFAULT_COMPACT_THROTTLE_3); + assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo( + AppCompactor.DEFAULT_COMPACT_THROTTLE_4); + assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo( + AppCompactor.DEFAULT_COMPACT_THROTTLE_5); + assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo( + AppCompactor.DEFAULT_COMPACT_THROTTLE_6); + + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + AppCompactor.KEY_COMPACT_THROTTLE_6, "foo", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); + assertThat(mCompactorUnderTest.mCompactThrottleSomeSome).isEqualTo( + AppCompactor.DEFAULT_COMPACT_THROTTLE_1); + assertThat(mCompactorUnderTest.mCompactThrottleSomeFull).isEqualTo( + AppCompactor.DEFAULT_COMPACT_THROTTLE_2); + assertThat(mCompactorUnderTest.mCompactThrottleFullSome).isEqualTo( + AppCompactor.DEFAULT_COMPACT_THROTTLE_3); + assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo( + AppCompactor.DEFAULT_COMPACT_THROTTLE_4); + assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo( + AppCompactor.DEFAULT_COMPACT_THROTTLE_5); + assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo( + AppCompactor.DEFAULT_COMPACT_THROTTLE_6); } @Test public void statsdSampleRate_listensToDeviceConfigChanges() throws InterruptedException { mCompactorUnderTest.init(); - // When we override mStatsdSampleRate with a reasonable values ... + // When we override mStatsdSampleRate with a reasonable value ... mCountDown = new CountDownLatch(1); DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, AppCompactor.KEY_COMPACT_STATSD_SAMPLE_RATE, @@ -380,11 +464,11 @@ public final class AppCompactorTest { } @Test - public void statsdSanokeRate_listensToDeviceConfigChangesBadValues() + public void statsdSampleRate_listensToDeviceConfigChangesBadValues() throws InterruptedException { mCompactorUnderTest.init(); - // When we override mStatsdSampleRate with a reasonable values ... + // When we override mStatsdSampleRate with an unreasonable value ... mCountDown = new CountDownLatch(1); DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, AppCompactor.KEY_COMPACT_STATSD_SAMPLE_RATE, "foo", false); @@ -396,7 +480,7 @@ public final class AppCompactorTest { } @Test - public void statsdSanokeRate_listensToDeviceConfigChangesOutOfRangeValues() + public void statsdSampleRate_listensToDeviceConfigChangesOutOfRangeValues() throws InterruptedException { mCompactorUnderTest.init(); @@ -420,6 +504,147 @@ public final class AppCompactorTest { assertThat(mCompactorUnderTest.mStatsdSampleRate).isEqualTo(1.0f); } + @Test + public void fullCompactionRssThrottleKb_listensToDeviceConfigChanges() + throws InterruptedException { + mCompactorUnderTest.init(); + + // When we override mStatsdSampleRate with a reasonable value ... + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + AppCompactor.KEY_COMPACT_FULL_RSS_THROTTLE_KB, + Long.toString(AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB + 1), false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); + + // Then that override is reflected in the compactor. + assertThat(mCompactorUnderTest.mFullAnonRssThrottleKb).isEqualTo( + AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB + 1); + } + + @Test + public void fullCompactionRssThrottleKb_listensToDeviceConfigChangesBadValues() + throws InterruptedException { + mCompactorUnderTest.init(); + + // When we override mStatsdSampleRate with an unreasonable value ... + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + AppCompactor.KEY_COMPACT_FULL_RSS_THROTTLE_KB, "foo", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); + + // Then that override is reflected in the compactor. + assertThat(mCompactorUnderTest.mFullAnonRssThrottleKb).isEqualTo( + AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB); + + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + AppCompactor.KEY_COMPACT_FULL_RSS_THROTTLE_KB, "-100", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); + + // Then that override is reflected in the compactor. + assertThat(mCompactorUnderTest.mFullAnonRssThrottleKb).isEqualTo( + AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB); + } + + @Test + public void fullCompactionDeltaRssThrottleKb_listensToDeviceConfigChanges() + throws InterruptedException { + mCompactorUnderTest.init(); + + // When we override mStatsdSampleRate with a reasonable value ... + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + AppCompactor.KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB, + Long.toString(AppCompactor.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB + 1), false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); + + // Then that override is reflected in the compactor. + assertThat(mCompactorUnderTest.mFullDeltaRssThrottleKb).isEqualTo( + AppCompactor.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB + 1); + } + + @Test + public void fullCompactionDeltaRssThrottleKb_listensToDeviceConfigChangesBadValues() + throws InterruptedException { + mCompactorUnderTest.init(); + + // When we override mStatsdSampleRate with an unreasonable value ... + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + AppCompactor.KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB, "foo", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); + + // Then that override is reflected in the compactor. + assertThat(mCompactorUnderTest.mFullDeltaRssThrottleKb).isEqualTo( + AppCompactor.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB); + + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + AppCompactor.KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB, "-100", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); + + // Then that override is reflected in the compactor. + assertThat(mCompactorUnderTest.mFullDeltaRssThrottleKb).isEqualTo( + AppCompactor.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB); + } + + @Test + public void procStateThrottle_listensToDeviceConfigChanges() + throws InterruptedException { + mCompactorUnderTest.init(); + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, "1,2,3", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); + assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactly(1, 2, 3); + + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, "", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); + assertThat(mCompactorUnderTest.mProcStateThrottle).isEmpty(); + } + + @Test + public void procStateThrottle_listensToDeviceConfigChangesBadValues() + throws InterruptedException { + mCompactorUnderTest.init(); + + Set<Integer> expected = new HashSet<>(); + for (String s : TextUtils.split(AppCompactor.DEFAULT_COMPACT_PROC_STATE_THROTTLE, ",")) { + expected.add(Integer.parseInt(s)); + } + + // Not numbers + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, "foo", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); + assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactlyElementsIn(expected); + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, "1,foo", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); + assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactlyElementsIn(expected); + + // Empty splits + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, ",", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); + assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactlyElementsIn(expected); + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, ",,3", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); + assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactlyElementsIn(expected); + mCountDown = new CountDownLatch(1); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, "1,,3", false); + assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); + assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactlyElementsIn(expected); + } + private class TestInjector extends Injector { @Override public AppOpsService getAppOpsService(File file, Handler handler) { diff --git a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java index 2fbeebdb4937..09e20e04835b 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java @@ -46,6 +46,7 @@ import com.android.internal.widget.ILockSettings; import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.LockSettingsInternal; import com.android.server.LocalServices; +import com.android.server.locksettings.recoverablekeystore.RecoverableKeyStoreManager; import com.android.server.wm.WindowManagerInternal; import org.mockito.invocation.InvocationOnMock; @@ -89,6 +90,7 @@ public abstract class BaseLockSettingsServiceTests extends AndroidTestCase { WindowManagerInternal mMockWindowManager; FakeGsiService mGsiService; PasswordSlotManagerTestable mPasswordSlotManager; + RecoverableKeyStoreManager mRecoverableKeyStoreManager; protected boolean mHasSecureLockScreen; @Override @@ -105,6 +107,7 @@ public abstract class BaseLockSettingsServiceTests extends AndroidTestCase { mMockWindowManager = mock(WindowManagerInternal.class); mGsiService = new FakeGsiService(); mPasswordSlotManager = new PasswordSlotManagerTestable(); + mRecoverableKeyStoreManager = mock(RecoverableKeyStoreManager.class); LocalServices.removeServiceForTest(LockSettingsInternal.class); LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class); @@ -141,12 +144,14 @@ public abstract class BaseLockSettingsServiceTests extends AndroidTestCase { mAuthSecretService = mock(IAuthSecret.class); mService = new LockSettingsServiceTestable(mContext, mLockPatternUtils, mStorage, mGateKeeperService, mKeyStore, setUpStorageManagerMock(), mActivityManager, - mSpManager, mAuthSecretService, mGsiService); + mSpManager, mAuthSecretService, mGsiService, mRecoverableKeyStoreManager); when(mUserManager.getUserInfo(eq(PRIMARY_USER_ID))).thenReturn(PRIMARY_USER_INFO); mPrimaryUserProfiles.add(PRIMARY_USER_INFO); installChildProfile(MANAGED_PROFILE_USER_ID); installAndTurnOffChildProfile(TURNED_OFF_PROFILE_USER_ID); - when(mUserManager.getProfiles(eq(PRIMARY_USER_ID))).thenReturn(mPrimaryUserProfiles); + for (UserInfo profile : mPrimaryUserProfiles) { + when(mUserManager.getProfiles(eq(profile.id))).thenReturn(mPrimaryUserProfiles); + } when(mUserManager.getUserInfo(eq(SECONDARY_USER_ID))).thenReturn(SECONDARY_USER_INFO); final ArrayList<UserInfo> allUsers = new ArrayList<>(mPrimaryUserProfiles); @@ -173,6 +178,7 @@ public abstract class BaseLockSettingsServiceTests extends AndroidTestCase { private UserInfo installChildProfile(int profileId) { final UserInfo userInfo = new UserInfo( profileId, null, null, UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_MANAGED_PROFILE); + userInfo.profileGroupId = PRIMARY_USER_ID; mPrimaryUserProfiles.add(userInfo); when(mUserManager.getUserInfo(eq(profileId))).thenReturn(userInfo); when(mUserManager.getProfileParent(eq(profileId))).thenReturn(PRIMARY_USER_INFO); diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java index f4632db3cb6d..10fb3ba938d4 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java @@ -30,6 +30,7 @@ import android.security.KeyStore; import android.security.keystore.KeyPermanentlyInvalidatedException; import com.android.internal.widget.LockPatternUtils; +import com.android.server.locksettings.recoverablekeystore.RecoverableKeyStoreManager; import java.io.FileNotFoundException; @@ -45,11 +46,13 @@ public class LockSettingsServiceTestable extends LockSettingsService { private SyntheticPasswordManager mSpManager; private IAuthSecret mAuthSecretService; private FakeGsiService mGsiService; + private RecoverableKeyStoreManager mRecoverableKeyStoreManager; public MockInjector(Context context, LockSettingsStorage storage, KeyStore keyStore, IActivityManager activityManager, LockPatternUtils lockPatternUtils, IStorageManager storageManager, SyntheticPasswordManager spManager, - IAuthSecret authSecretService, FakeGsiService gsiService) { + IAuthSecret authSecretService, FakeGsiService gsiService, + RecoverableKeyStoreManager recoverableKeyStoreManager) { super(context); mLockSettingsStorage = storage; mKeyStore = keyStore; @@ -58,6 +61,7 @@ public class LockSettingsServiceTestable extends LockSettingsService { mStorageManager = storageManager; mSpManager = spManager; mGsiService = gsiService; + mRecoverableKeyStoreManager = recoverableKeyStoreManager; } @Override @@ -119,15 +123,21 @@ public class LockSettingsServiceTestable extends LockSettingsService { public boolean isGsiRunning() { return mGsiService.isGsiRunning(); } + + @Override + public RecoverableKeyStoreManager getRecoverableKeyStoreManager(KeyStore keyStore) { + return mRecoverableKeyStoreManager; + } } protected LockSettingsServiceTestable(Context context, LockPatternUtils lockPatternUtils, LockSettingsStorage storage, FakeGateKeeperService gatekeeper, KeyStore keystore, IStorageManager storageManager, IActivityManager mActivityManager, SyntheticPasswordManager spManager, IAuthSecret authSecretService, - FakeGsiService gsiService) { + FakeGsiService gsiService, RecoverableKeyStoreManager recoverableKeyStoreManager) { super(new MockInjector(context, storage, keystore, mActivityManager, lockPatternUtils, - storageManager, spManager, authSecretService, gsiService)); + storageManager, spManager, authSecretService, gsiService, + recoverableKeyStoreManager)); mGateKeeperService = gatekeeper; mAuthSecretService = authSecretService; } diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java index 7ebc7454d995..67d6eda1b3ee 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java @@ -25,6 +25,13 @@ import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE; import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD; import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; + import android.os.RemoteException; import android.platform.test.annotations.Presubmit; import android.service.gatekeeper.GateKeeperResponse; @@ -211,6 +218,222 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests { assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID)); } + public void testSetLockCredential_forPrimaryUser_sendsCredentials() throws Exception { + final byte[] password = "password".getBytes(); + + mService.setLockCredential( + password, + CREDENTIAL_TYPE_PASSWORD, + null, + PASSWORD_QUALITY_ALPHABETIC, + PRIMARY_USER_ID, + false); + + verify(mRecoverableKeyStoreManager) + .lockScreenSecretChanged(CREDENTIAL_TYPE_PASSWORD, password, PRIMARY_USER_ID); + } + + public void testSetLockCredential_forProfileWithSeparateChallenge_sendsCredentials() + throws Exception { + final byte[] pattern = "12345".getBytes(); + + mService.setLockCredential( + pattern, + CREDENTIAL_TYPE_PATTERN, + null, + PASSWORD_QUALITY_SOMETHING, + MANAGED_PROFILE_USER_ID, + false); + + verify(mRecoverableKeyStoreManager) + .lockScreenSecretChanged(CREDENTIAL_TYPE_PATTERN, pattern, MANAGED_PROFILE_USER_ID); + } + + public void testSetLockCredential_forProfileWithSeparateChallenge_updatesCredentials() + throws Exception { + final String oldCredential = "12345"; + final byte[] newCredential = "newPassword".getBytes(); + initializeStorageWithCredential( + MANAGED_PROFILE_USER_ID, + oldCredential, + CREDENTIAL_TYPE_PATTERN, + PASSWORD_QUALITY_SOMETHING); + + mService.setLockCredential( + newCredential, + CREDENTIAL_TYPE_PASSWORD, + oldCredential.getBytes(), + PASSWORD_QUALITY_ALPHABETIC, + MANAGED_PROFILE_USER_ID, + false); + + verify(mRecoverableKeyStoreManager) + .lockScreenSecretChanged( + CREDENTIAL_TYPE_PASSWORD, newCredential, MANAGED_PROFILE_USER_ID); + } + + public void testSetLockCredential_forProfileWithUnifiedChallenge_doesNotSendRandomCredential() + throws Exception { + mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null); + + mService.setLockCredential( + "12345".getBytes(), + CREDENTIAL_TYPE_PATTERN, + null, + PASSWORD_QUALITY_SOMETHING, + PRIMARY_USER_ID, + false); + + verify(mRecoverableKeyStoreManager, never()) + .lockScreenSecretChanged( + eq(CREDENTIAL_TYPE_PASSWORD), any(), eq(MANAGED_PROFILE_USER_ID)); + } + + public void + testSetLockCredential_forPrimaryUserWithUnifiedChallengeProfile_updatesBothCredentials() + throws Exception { + final String oldCredential = "oldPassword"; + final byte[] newCredential = "newPassword".getBytes(); + initializeStorageWithCredential( + PRIMARY_USER_ID, oldCredential, CREDENTIAL_TYPE_PASSWORD, 1234); + mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null); + + mService.setLockCredential( + newCredential, + CREDENTIAL_TYPE_PASSWORD, + oldCredential.getBytes(), + PASSWORD_QUALITY_ALPHABETIC, + PRIMARY_USER_ID, + false); + + verify(mRecoverableKeyStoreManager) + .lockScreenSecretChanged(CREDENTIAL_TYPE_PASSWORD, newCredential, PRIMARY_USER_ID); + verify(mRecoverableKeyStoreManager) + .lockScreenSecretChanged( + CREDENTIAL_TYPE_PASSWORD, newCredential, MANAGED_PROFILE_USER_ID); + } + + public void + testSetLockCredential_forPrimaryUserWithUnifiedChallengeProfile_removesBothCredentials() + throws Exception { + final String oldCredential = "oldPassword"; + initializeStorageWithCredential( + PRIMARY_USER_ID, oldCredential, CREDENTIAL_TYPE_PASSWORD, 1234); + mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null); + + mService.setLockCredential( + null, + CREDENTIAL_TYPE_NONE, + oldCredential.getBytes(), + PASSWORD_QUALITY_UNSPECIFIED, + PRIMARY_USER_ID, + false); + + verify(mRecoverableKeyStoreManager) + .lockScreenSecretChanged(CREDENTIAL_TYPE_NONE, null, PRIMARY_USER_ID); + verify(mRecoverableKeyStoreManager) + .lockScreenSecretChanged(CREDENTIAL_TYPE_NONE, null, MANAGED_PROFILE_USER_ID); + } + + public void testSetLockCredential_forUnifiedToSeparateChallengeProfile_sendsNewCredentials() + throws Exception { + final String parentPassword = "parentPassword"; + final byte[] profilePassword = "profilePassword".getBytes(); + initializeStorageWithCredential( + PRIMARY_USER_ID, parentPassword, CREDENTIAL_TYPE_PASSWORD, 1234); + mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null); + + mService.setLockCredential( + profilePassword, + CREDENTIAL_TYPE_PASSWORD, + null, + PASSWORD_QUALITY_ALPHABETIC, + MANAGED_PROFILE_USER_ID, + false); + + verify(mRecoverableKeyStoreManager) + .lockScreenSecretChanged( + CREDENTIAL_TYPE_PASSWORD, profilePassword, MANAGED_PROFILE_USER_ID); + } + + public void + testSetLockCredential_forSeparateToUnifiedChallengeProfile_doesNotSendRandomCredential() + throws Exception { + final String parentPassword = "parentPassword"; + final String profilePassword = "12345"; + initializeStorageWithCredential( + PRIMARY_USER_ID, parentPassword, CREDENTIAL_TYPE_PASSWORD, 1234); + // Create and verify separate profile credentials. + testCreateCredential( + MANAGED_PROFILE_USER_ID, + profilePassword, + CREDENTIAL_TYPE_PATTERN, + PASSWORD_QUALITY_SOMETHING); + + mService.setSeparateProfileChallengeEnabled( + MANAGED_PROFILE_USER_ID, false, profilePassword.getBytes()); + + // Called once for setting the initial separate profile credentials and not again during + // unification. + verify(mRecoverableKeyStoreManager) + .lockScreenSecretChanged(anyInt(), any(), eq(MANAGED_PROFILE_USER_ID)); + } + + public void testVerifyCredential_forPrimaryUser_sendsCredentials() throws Exception { + final String password = "password"; + initializeStorageWithCredential(PRIMARY_USER_ID, password, CREDENTIAL_TYPE_PASSWORD, 1234); + reset(mRecoverableKeyStoreManager); + + mService.verifyCredential( + password.getBytes(), CREDENTIAL_TYPE_PASSWORD, 1, PRIMARY_USER_ID); + + verify(mRecoverableKeyStoreManager) + .lockScreenSecretAvailable( + CREDENTIAL_TYPE_PASSWORD, password.getBytes(), PRIMARY_USER_ID); + } + + public void testVerifyCredential_forProfileWithSeparateChallenge_sendsCredentials() + throws Exception { + final byte[] pattern = "12345".getBytes(); + mService.setLockCredential( + pattern, + CREDENTIAL_TYPE_PATTERN, + null, + PASSWORD_QUALITY_SOMETHING, + MANAGED_PROFILE_USER_ID, + false); + reset(mRecoverableKeyStoreManager); + + mService.verifyCredential(pattern, CREDENTIAL_TYPE_PATTERN, 1, MANAGED_PROFILE_USER_ID); + + verify(mRecoverableKeyStoreManager) + .lockScreenSecretAvailable( + CREDENTIAL_TYPE_PATTERN, pattern, MANAGED_PROFILE_USER_ID); + } + + public void + testVerifyCredential_forPrimaryUserWithUnifiedChallengeProfile_sendsCredentialsForBoth() + throws Exception { + final String pattern = "12345"; + initializeStorageWithCredential(PRIMARY_USER_ID, pattern, CREDENTIAL_TYPE_PATTERN, 1234); + mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null); + reset(mRecoverableKeyStoreManager); + + mService.verifyCredential(pattern.getBytes(), CREDENTIAL_TYPE_PATTERN, 1, PRIMARY_USER_ID); + + // Parent sends its credentials for both the parent and profile. + verify(mRecoverableKeyStoreManager) + .lockScreenSecretAvailable( + CREDENTIAL_TYPE_PATTERN, pattern.getBytes(), PRIMARY_USER_ID); + verify(mRecoverableKeyStoreManager) + .lockScreenSecretAvailable( + CREDENTIAL_TYPE_PATTERN, pattern.getBytes(), MANAGED_PROFILE_USER_ID); + // Profile doesn't send its own random credentials. + verify(mRecoverableKeyStoreManager, never()) + .lockScreenSecretAvailable( + eq(CREDENTIAL_TYPE_PASSWORD), any(), eq(MANAGED_PROFILE_USER_ID)); + } + private void testCreateCredential(int userId, String credential, int type, int quality) throws RemoteException { mService.setLockCredential(credential.getBytes(), type, null, quality, diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java index 5bab65c8b642..68900175cc8f 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java @@ -58,6 +58,7 @@ import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.widget.LockPatternUtils; import com.android.server.locksettings.recoverablekeystore.storage.ApplicationKeyStorage; import com.android.server.locksettings.recoverablekeystore.storage.CleanupManager; import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb; @@ -83,7 +84,7 @@ import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Map; import java.util.Random; -import java.util.concurrent.Executors; +import java.util.concurrent.ExecutorService; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; @@ -156,6 +157,7 @@ public class RecoverableKeyStoreManagerTest { @Mock private PlatformKeyManager mPlatformKeyManager; @Mock private ApplicationKeyStorage mApplicationKeyStorage; @Mock private CleanupManager mCleanupManager; + @Mock private ExecutorService mExecutorService; @Spy private TestOnlyInsecureCertificateHelper mTestOnlyInsecureCertificateHelper; private RecoverableKeyStoreDb mRecoverableKeyStoreDb; @@ -188,7 +190,7 @@ public class RecoverableKeyStoreManagerTest { mMockContext, mRecoverableKeyStoreDb, mRecoverySessionStorage, - Executors.newSingleThreadExecutor(), + mExecutorService, mRecoverySnapshotStorage, mMockListenersStorage, mPlatformKeyManager, @@ -1246,6 +1248,24 @@ public class RecoverableKeyStoreManagerTest { } } + @Test + public void lockScreenSecretAvailable_syncsKeysForUser() throws Exception { + mRecoverableKeyStoreManager.lockScreenSecretAvailable( + LockPatternUtils.CREDENTIAL_TYPE_PATTERN, "password".getBytes(), 11); + + verify(mExecutorService).execute(any()); + } + + @Test + public void lockScreenSecretChanged_syncsKeysForUser() throws Exception { + mRecoverableKeyStoreManager.lockScreenSecretChanged( + LockPatternUtils.CREDENTIAL_TYPE_PATTERN, + "password".getBytes(), + 11); + + verify(mExecutorService).execute(any()); + } + private static byte[] encryptedApplicationKey( SecretKey recoveryKey, byte[] applicationKey) throws Exception { return KeySyncUtils.encryptKeysWithRecoveryKey(recoveryKey, ImmutableMap.of( diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 6f1bd87f14f7..ca7a71ecad75 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -18,6 +18,8 @@ package com.android.server.notification; import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE; +import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE; +import static android.app.Notification.CATEGORY_CALL; import static android.app.Notification.FLAG_BUBBLE; import static android.app.Notification.FLAG_FOREGROUND_SERVICE; import static android.app.NotificationManager.EXTRA_BLOCKED_STATE; @@ -81,6 +83,7 @@ import android.app.NotificationChannel; import android.app.NotificationChannelGroup; import android.app.NotificationManager; import android.app.PendingIntent; +import android.app.Person; import android.app.admin.DevicePolicyManagerInternal; import android.app.usage.UsageStatsManagerInternal; import android.companion.ICompanionDeviceManager; @@ -103,6 +106,7 @@ import android.os.Bundle; import android.os.IBinder; import android.os.Process; import android.os.RemoteException; +import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; import android.provider.DeviceConfig; @@ -433,6 +437,11 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { private NotificationRecord generateNotificationRecord(NotificationChannel channel, Notification.TvExtender extender) { + return generateNotificationRecord(channel, extender, false /* isBubble */); + } + + private NotificationRecord generateNotificationRecord(NotificationChannel channel, + Notification.TvExtender extender, boolean isBubble) { if (channel == null) { channel = mTestNotificationChannel; } @@ -442,6 +451,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { if (extender != null) { nb.extend(extender); } + if (isBubble) { + nb.setBubbleMetadata(getBasicBubbleMetadataBuilder().build()); + } StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, "tag", mUid, 0, nb.build(), new UserHandle(mUid), null, 0); return new NotificationRecord(mContext, sbn, channel); @@ -4293,7 +4305,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - public void testFlagBubbleNotifs_flagIfAllowed() throws RemoteException { + public void testFlagBubbleNotifs_flag_appForeground() throws RemoteException { // Bubbles are allowed! mService.setPreferencesHelper(mPreferencesHelper); when(mPreferencesHelper.areBubblesAllowed(anyString(), anyInt())).thenReturn(true); @@ -4303,29 +4315,353 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { when(mPreferencesHelper.getImportance(anyString(), anyInt())).thenReturn( mTestNotificationChannel.getImportance()); - // Notif with bubble metadata + // Notif with bubble metadata but not our other misc requirements + NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel, + null /* tvExtender */, true /* isBubble */); + + // Say we're foreground + when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn( + IMPORTANCE_FOREGROUND); + + mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", + nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId()); + waitForIdle(); + + // yes allowed, yes foreground, yes bubble + assertTrue(mService.getNotificationRecord( + nr.sbn.getKey()).getNotification().isBubbleNotification()); + } + + @Test + public void testFlagBubbleNotifs_noFlag_appNotForeground() throws RemoteException { + // Bubbles are allowed! + mService.setPreferencesHelper(mPreferencesHelper); + when(mPreferencesHelper.areBubblesAllowed(anyString(), anyInt())).thenReturn(true); + when(mPreferencesHelper.getNotificationChannel( + anyString(), anyInt(), anyString(), anyBoolean())).thenReturn( + mTestNotificationChannel); + when(mPreferencesHelper.getImportance(anyString(), anyInt())).thenReturn( + mTestNotificationChannel.getImportance()); + + // Notif with bubble metadata but not our other misc requirements + NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel, + null /* tvExtender */, true /* isBubble */); + + // Make sure we're NOT foreground + when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn( + IMPORTANCE_VISIBLE); + + mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", + nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId()); + waitForIdle(); + + // yes allowed but NOT foreground, no bubble + assertFalse(mService.getNotificationRecord( + nr.sbn.getKey()).getNotification().isBubbleNotification()); + } + + @Test + public void testFlagBubbleNotifs_flag_previousForegroundFlag() throws RemoteException { + // Bubbles are allowed! + mService.setPreferencesHelper(mPreferencesHelper); + when(mPreferencesHelper.areBubblesAllowed(anyString(), anyInt())).thenReturn(true); + when(mPreferencesHelper.getNotificationChannel( + anyString(), anyInt(), anyString(), anyBoolean())).thenReturn( + mTestNotificationChannel); + when(mPreferencesHelper.getImportance(anyString(), anyInt())).thenReturn( + mTestNotificationChannel.getImportance()); + + // Notif with bubble metadata but not our other misc requirements + NotificationRecord nr1 = generateNotificationRecord(mTestNotificationChannel, + null /* tvExtender */, true /* isBubble */); + + // Send notif when we're foreground + when(mActivityManager.getPackageImportance(nr1.sbn.getPackageName())).thenReturn( + IMPORTANCE_FOREGROUND); + mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", + nr1.sbn.getId(), nr1.sbn.getNotification(), nr1.sbn.getUserId()); + waitForIdle(); + + // yes allowed, yes foreground, yes bubble + assertTrue(mService.getNotificationRecord( + nr1.sbn.getKey()).getNotification().isBubbleNotification()); + + // Send a new update when we're not foreground + NotificationRecord nr2 = generateNotificationRecord(mTestNotificationChannel, + null /* tvExtender */, true /* isBubble */); + + when(mActivityManager.getPackageImportance(nr2.sbn.getPackageName())).thenReturn( + IMPORTANCE_VISIBLE); + mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", + nr2.sbn.getId(), nr2.sbn.getNotification(), nr2.sbn.getUserId()); + waitForIdle(); + + // yes allowed, previously foreground / flagged, yes bubble + assertTrue(mService.getNotificationRecord( + nr2.sbn.getKey()).getNotification().isBubbleNotification()); + + StatusBarNotification[] notifs2 = mBinderService.getActiveNotifications(PKG); + assertEquals(1, notifs2.length); + assertEquals(1, mService.getNotificationRecordCount()); + } + + @Test + public void testFlagBubbleNotifs_noFlag_previousForegroundFlag_afterRemoval() + throws RemoteException { + // Bubbles are allowed! + mService.setPreferencesHelper(mPreferencesHelper); + when(mPreferencesHelper.areBubblesAllowed(anyString(), anyInt())).thenReturn(true); + when(mPreferencesHelper.getNotificationChannel( + anyString(), anyInt(), anyString(), anyBoolean())).thenReturn( + mTestNotificationChannel); + when(mPreferencesHelper.getImportance(anyString(), anyInt())).thenReturn( + mTestNotificationChannel.getImportance()); + + // Notif with bubble metadata but not our other misc requirements + NotificationRecord nr1 = generateNotificationRecord(mTestNotificationChannel, + null /* tvExtender */, true /* isBubble */); + + // Send notif when we're foreground + when(mActivityManager.getPackageImportance(nr1.sbn.getPackageName())).thenReturn( + IMPORTANCE_FOREGROUND); + mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", + nr1.sbn.getId(), nr1.sbn.getNotification(), nr1.sbn.getUserId()); + waitForIdle(); + + // yes allowed, yes foreground, yes bubble + assertTrue(mService.getNotificationRecord( + nr1.sbn.getKey()).getNotification().isBubbleNotification()); + + // Remove the bubble + mBinderService.cancelNotificationWithTag(PKG, "tag", nr1.sbn.getId(), + nr1.sbn.getUserId()); + waitForIdle(); + + StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG); + assertEquals(0, notifs.length); + assertEquals(0, mService.getNotificationRecordCount()); + + // Send a new update when we're not foreground + NotificationRecord nr2 = generateNotificationRecord(mTestNotificationChannel, + null /* tvExtender */, true /* isBubble */); + + when(mActivityManager.getPackageImportance(nr2.sbn.getPackageName())).thenReturn( + IMPORTANCE_VISIBLE); + mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", + nr2.sbn.getId(), nr2.sbn.getNotification(), nr2.sbn.getUserId()); + waitForIdle(); + + // yes allowed, but was removed & no foreground, so no bubble + assertFalse(mService.getNotificationRecord( + nr2.sbn.getKey()).getNotification().isBubbleNotification()); + + StatusBarNotification[] notifs2 = mBinderService.getActiveNotifications(PKG); + assertEquals(1, notifs2.length); + assertEquals(1, mService.getNotificationRecordCount()); + } + + @Test + public void testFlagBubbleNotifs_flag_messaging() throws RemoteException { + // Bubbles are allowed! + mService.setPreferencesHelper(mPreferencesHelper); + when(mPreferencesHelper.areBubblesAllowed(anyString(), anyInt())).thenReturn(true); + when(mPreferencesHelper.getNotificationChannel( + anyString(), anyInt(), anyString(), anyBoolean())).thenReturn( + mTestNotificationChannel); + when(mPreferencesHelper.getImportance(anyString(), anyInt())).thenReturn( + mTestNotificationChannel.getImportance()); + + // Give it bubble metadata + Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder().build(); + // Give it a person + Person person = new Person.Builder() + .setName("bubblebot") + .build(); + // Make it messaging style + Notification.Builder nb = new Notification.Builder(mContext, + mTestNotificationChannel.getId()) + .setContentTitle("foo") + .setBubbleMetadata(data) + .setStyle(new Notification.MessagingStyle(person) + .setConversationTitle("Bubble Chat") + .addMessage("Hello?", + SystemClock.currentThreadTimeMillis() - 300000, person) + .addMessage("Is it me you're looking for?", + SystemClock.currentThreadTimeMillis(), person) + ) + .setSmallIcon(android.R.drawable.sym_def_app_icon); + + StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, null, mUid, 0, + nb.build(), new UserHandle(mUid), null, 0); + NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel); + + mBinderService.enqueueNotificationWithTag(PKG, PKG, null, + nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId()); + waitForIdle(); + + // yes allowed, yes messaging, yes bubble + assertTrue(mService.getNotificationRecord( + sbn.getKey()).getNotification().isBubbleNotification()); + } + + @Test + public void testFlagBubbleNotifs_flag_phonecall() throws RemoteException { + // Bubbles are allowed! + mService.setPreferencesHelper(mPreferencesHelper); + when(mPreferencesHelper.areBubblesAllowed(anyString(), anyInt())).thenReturn(true); + when(mPreferencesHelper.getNotificationChannel( + anyString(), anyInt(), anyString(), anyBoolean())).thenReturn( + mTestNotificationChannel); + when(mPreferencesHelper.getImportance(anyString(), anyInt())).thenReturn( + mTestNotificationChannel.getImportance()); + + // Give it bubble metadata Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder().build(); + // Give it a person + Person person = new Person.Builder() + .setName("bubblebot") + .build(); + // Make it a phone call Notification.Builder nb = new Notification.Builder(mContext, mTestNotificationChannel.getId()) + .setCategory(CATEGORY_CALL) + .addPerson(person) .setContentTitle("foo") .setBubbleMetadata(data) .setSmallIcon(android.R.drawable.sym_def_app_icon); StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, null, mUid, 0, nb.build(), new UserHandle(mUid), null, 0); + // Make sure it has foreground service + sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE; NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel); mBinderService.enqueueNotificationWithTag(PKG, PKG, null, nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId()); waitForIdle(); - // yes allowed, yes bubble + // yes phone call, yes person, yes foreground service, yes bubble assertTrue(mService.getNotificationRecord( sbn.getKey()).getNotification().isBubbleNotification()); } @Test - public void testFlagBubbleNotifs_noFlagIfNotAllowed() throws RemoteException { + public void testFlagBubbleNotifs_noFlag_phonecall_noForegroundService() throws RemoteException { + // Bubbles are allowed! + mService.setPreferencesHelper(mPreferencesHelper); + when(mPreferencesHelper.areBubblesAllowed(anyString(), anyInt())).thenReturn(true); + when(mPreferencesHelper.getNotificationChannel( + anyString(), anyInt(), anyString(), anyBoolean())).thenReturn( + mTestNotificationChannel); + when(mPreferencesHelper.getImportance(anyString(), anyInt())).thenReturn( + mTestNotificationChannel.getImportance()); + + // Give it bubble metadata + Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder().build(); + // Give it a person + Person person = new Person.Builder() + .setName("bubblebot") + .build(); + // Make it a phone call + Notification.Builder nb = new Notification.Builder(mContext, + mTestNotificationChannel.getId()) + .setCategory(CATEGORY_CALL) + .addPerson(person) + .setContentTitle("foo") + .setBubbleMetadata(data) + .setSmallIcon(android.R.drawable.sym_def_app_icon); + + StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, null, mUid, 0, + nb.build(), new UserHandle(mUid), null, 0); + NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel); + + mBinderService.enqueueNotificationWithTag(PKG, PKG, null, + nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId()); + waitForIdle(); + + // yes phone call, yes person, NO foreground service, no bubble + assertFalse(mService.getNotificationRecord( + sbn.getKey()).getNotification().isBubbleNotification()); + } + + @Test + public void testFlagBubbleNotifs_noFlag_phonecall_noPerson() throws RemoteException { + // Bubbles are allowed! + mService.setPreferencesHelper(mPreferencesHelper); + when(mPreferencesHelper.areBubblesAllowed(anyString(), anyInt())).thenReturn(true); + when(mPreferencesHelper.getNotificationChannel( + anyString(), anyInt(), anyString(), anyBoolean())).thenReturn( + mTestNotificationChannel); + when(mPreferencesHelper.getImportance(anyString(), anyInt())).thenReturn( + mTestNotificationChannel.getImportance()); + + // Give it bubble metadata + Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder().build(); + // Make it a phone call + Notification.Builder nb = new Notification.Builder(mContext, + mTestNotificationChannel.getId()) + .setCategory(CATEGORY_CALL) + .setContentTitle("foo") + .setBubbleMetadata(data) + .setSmallIcon(android.R.drawable.sym_def_app_icon); + + StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, null, mUid, 0, + nb.build(), new UserHandle(mUid), null, 0); + // Make sure it has foreground service + sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE; + NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel); + + mBinderService.enqueueNotificationWithTag(PKG, PKG, null, + nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId()); + waitForIdle(); + + // yes phone call, yes foreground service, BUT NO person, no bubble + assertFalse(mService.getNotificationRecord( + sbn.getKey()).getNotification().isBubbleNotification()); + } + + @Test + public void testFlagBubbleNotifs_noFlag_phonecall_noCategory() throws RemoteException { + // Bubbles are allowed! + mService.setPreferencesHelper(mPreferencesHelper); + when(mPreferencesHelper.areBubblesAllowed(anyString(), anyInt())).thenReturn(true); + when(mPreferencesHelper.getNotificationChannel( + anyString(), anyInt(), anyString(), anyBoolean())).thenReturn( + mTestNotificationChannel); + when(mPreferencesHelper.getImportance(anyString(), anyInt())).thenReturn( + mTestNotificationChannel.getImportance()); + + // Give it bubble metadata + Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder().build(); + // Give it a person + Person person = new Person.Builder() + .setName("bubblebot") + .build(); + // No category + Notification.Builder nb = new Notification.Builder(mContext, + mTestNotificationChannel.getId()) + .addPerson(person) + .setContentTitle("foo") + .setBubbleMetadata(data) + .setSmallIcon(android.R.drawable.sym_def_app_icon); + + StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, null, mUid, 0, + nb.build(), new UserHandle(mUid), null, 0); + // Make sure it has foreground service + sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE; + NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel); + + mBinderService.enqueueNotificationWithTag(PKG, PKG, null, + nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId()); + waitForIdle(); + + // yes person, yes foreground service, BUT NO call, no bubble + assertFalse(mService.getNotificationRecord( + sbn.getKey()).getNotification().isBubbleNotification()); + } + + @Test + public void testFlagBubbleNotifs_noFlag_messaging_appNotAllowed() throws RemoteException { // Bubbles are NOT allowed! mService.setPreferencesHelper(mPreferencesHelper); when(mPreferencesHelper.areBubblesAllowed(anyString(), anyInt())).thenReturn(false); @@ -4335,12 +4671,24 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { when(mPreferencesHelper.getImportance(anyString(), anyInt())).thenReturn( mTestNotificationChannel.getImportance()); - // Notif with bubble metadata + // Give it bubble metadata Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder().build(); + // Give it a person + Person person = new Person.Builder() + .setName("bubblebot") + .build(); + // Make it messaging style Notification.Builder nb = new Notification.Builder(mContext, mTestNotificationChannel.getId()) .setContentTitle("foo") .setBubbleMetadata(data) + .setStyle(new Notification.MessagingStyle(person) + .setConversationTitle("Bubble Chat") + .addMessage("Hello?", + SystemClock.currentThreadTimeMillis() - 300000, person) + .addMessage("Is it me you're looking for?", + SystemClock.currentThreadTimeMillis(), person) + ) .setSmallIcon(android.R.drawable.sym_def_app_icon); StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, null, mUid, 0, @@ -4358,7 +4706,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - public void testFlagBubbleNotifs_noFlagIfNotBubble() throws RemoteException { + public void testFlagBubbleNotifs_noFlag_notBubble() throws RemoteException { // Bubbles are allowed! mService.setPreferencesHelper(mPreferencesHelper); when(mPreferencesHelper.areBubblesAllowed(anyString(), anyInt())).thenReturn(true); @@ -4369,9 +4717,50 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mTestNotificationChannel.getImportance()); // Notif WITHOUT bubble metadata + NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel); + + // Post the notification + mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", + nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId()); + waitForIdle(); + + // no bubble metadata, no bubble + assertFalse(mService.getNotificationRecord( + nr.sbn.getKey()).getNotification().isBubbleNotification()); + } + + @Test + public void testFlagBubbleNotifs_noFlag_messaging_channelNotAllowed() throws RemoteException { + // Bubbles are allowed! + mService.setPreferencesHelper(mPreferencesHelper); + when(mPreferencesHelper.areBubblesAllowed(anyString(), anyInt())).thenReturn(true); + when(mPreferencesHelper.getNotificationChannel( + anyString(), anyInt(), anyString(), anyBoolean())).thenReturn( + mTestNotificationChannel); + when(mPreferencesHelper.getImportance(anyString(), anyInt())).thenReturn( + mTestNotificationChannel.getImportance()); + + // But not on this channel! + mTestNotificationChannel.setAllowBubbles(false); + + // Give it bubble metadata + Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder().build(); + // Give it a person + Person person = new Person.Builder() + .setName("bubblebot") + .build(); + // Make it messaging style Notification.Builder nb = new Notification.Builder(mContext, mTestNotificationChannel.getId()) .setContentTitle("foo") + .setBubbleMetadata(data) + .setStyle(new Notification.MessagingStyle(person) + .setConversationTitle("Bubble Chat") + .addMessage("Hello?", + SystemClock.currentThreadTimeMillis() - 300000, person) + .addMessage("Is it me you're looking for?", + SystemClock.currentThreadTimeMillis(), person) + ) .setSmallIcon(android.R.drawable.sym_def_app_icon); StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, null, mUid, 0, @@ -4383,16 +4772,57 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId()); waitForIdle(); - // no bubble metadata, no bubble + // channel not allowed, no bubble assertFalse(mService.getNotificationRecord( sbn.getKey()).getNotification().isBubbleNotification()); } @Test - public void testFlagBubbleNotifs_noFlagIfChannelNotBubble() throws RemoteException { + public void testFlagBubbleNotifs_noFlag_phonecall_notAllowed() throws RemoteException { // Bubbles are allowed! mService.setPreferencesHelper(mPreferencesHelper); - when(mPreferencesHelper.areBubblesAllowed(anyString(), anyInt())).thenReturn(true); + when(mPreferencesHelper.areBubblesAllowed(anyString(), anyInt())).thenReturn(false); + when(mPreferencesHelper.getNotificationChannel( + anyString(), anyInt(), anyString(), anyBoolean())).thenReturn( + mTestNotificationChannel); + when(mPreferencesHelper.getImportance(anyString(), anyInt())).thenReturn( + mTestNotificationChannel.getImportance()); + + // Give it bubble metadata + Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder().build(); + // Give it a person + Person person = new Person.Builder() + .setName("bubblebot") + .build(); + // Make it a phone call + Notification.Builder nb = new Notification.Builder(mContext, + mTestNotificationChannel.getId()) + .setCategory(CATEGORY_CALL) + .addPerson(person) + .setContentTitle("foo") + .setBubbleMetadata(data) + .setSmallIcon(android.R.drawable.sym_def_app_icon); + + StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, null, mUid, 0, + nb.build(), new UserHandle(mUid), null, 0); + // Make sure it has foreground service + sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE; + NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel); + + mBinderService.enqueueNotificationWithTag(PKG, PKG, null, + nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId()); + waitForIdle(); + + // yes phone call, yes person, yes foreground service, but not allowed, no bubble + assertFalse(mService.getNotificationRecord( + sbn.getKey()).getNotification().isBubbleNotification()); + } + + @Test + public void testFlagBubbleNotifs_noFlag_phonecall_channelNotAllowed() throws RemoteException { + // Bubbles are allowed! + mService.setPreferencesHelper(mPreferencesHelper); + when(mPreferencesHelper.areBubblesAllowed(anyString(), anyInt())).thenReturn(false); when(mPreferencesHelper.getNotificationChannel( anyString(), anyInt(), anyString(), anyBoolean())).thenReturn( mTestNotificationChannel); @@ -4402,24 +4832,32 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // But not on this channel! mTestNotificationChannel.setAllowBubbles(false); - // Notif with bubble metadata + // Give it bubble metadata Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder().build(); + // Give it a person + Person person = new Person.Builder() + .setName("bubblebot") + .build(); + // Make it a phone call Notification.Builder nb = new Notification.Builder(mContext, mTestNotificationChannel.getId()) + .setCategory(CATEGORY_CALL) + .addPerson(person) .setContentTitle("foo") .setBubbleMetadata(data) .setSmallIcon(android.R.drawable.sym_def_app_icon); StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, null, mUid, 0, nb.build(), new UserHandle(mUid), null, 0); + // Make sure it has foreground service + sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE; NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel); - // Post the notification mBinderService.enqueueNotificationWithTag(PKG, PKG, null, nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId()); waitForIdle(); - // channel not allowed, no bubble + // yes phone call, yes person, yes foreground service, but channel not allowed, no bubble assertFalse(mService.getNotificationRecord( sbn.getKey()).getNotification().isBubbleNotification()); } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java index 87f10a4e077c..6ed78b36190f 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -2124,9 +2124,16 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testXml_statusBarIcons_default() throws Exception { - ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL); + String preQXml = "<ranking version=\"1\">\n" + + "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n" + + "<channel id=\"something\" name=\"name\" importance=\"2\" " + + "show_badge=\"true\" />\n" + + "<channel id=\"miscellaneous\" name=\"Uncategorized\" usage=\"5\" " + + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" + + "</package>\n" + + "</ranking>\n"; mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper); - loadStreamXml(baos, false, UserHandle.USER_ALL); + loadByteArrayXml(preQXml.getBytes(), true, UserHandle.USER_SYSTEM); assertEquals(PreferencesHelper.DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS, mHelper.shouldHideSilentStatusIcons()); diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java index c05a3461e26e..1822cee89eaa 100644 --- a/telecomm/java/android/telecom/Call.java +++ b/telecomm/java/android/telecom/Call.java @@ -552,7 +552,6 @@ public final class Call { private final Bundle mExtras; private final Bundle mIntentExtras; private final long mCreationTimeMillis; - private final CallIdentification mCallIdentification; private final @CallDirection int mCallDirection; /** @@ -738,8 +737,6 @@ public final class Call { * The display name for the caller. * <p> * This is the name as reported by the {@link ConnectionService} associated with this call. - * The name reported by a {@link CallScreeningService} can be retrieved using - * {@link CallIdentification#getName()}. * * @return The display name for the caller. */ @@ -857,23 +854,6 @@ public final class Call { } /** - * Returns {@link CallIdentification} information provided by a - * {@link CallScreeningService} for this call. - * <p> - * {@link InCallService} implementations should display the {@link CallIdentification} for - * calls. The name of the call screening service is provided in - * {@link CallIdentification#getCallScreeningAppName()} and should be used to attribute the - * call identification information. - * - * @return The {@link CallIdentification} if it was provided by a - * {@link CallScreeningService}, or {@code null} if no {@link CallScreeningService} has - * provided {@link CallIdentification} information for the call. - */ - public @Nullable CallIdentification getCallIdentification() { - return mCallIdentification; - } - - /** * Indicates whether the call is an incoming or outgoing call. * @return The call's direction. */ @@ -902,7 +882,6 @@ public final class Call { areBundlesEqual(mExtras, d.mExtras) && areBundlesEqual(mIntentExtras, d.mIntentExtras) && Objects.equals(mCreationTimeMillis, d.mCreationTimeMillis) && - Objects.equals(mCallIdentification, d.mCallIdentification) && Objects.equals(mCallDirection, d.mCallDirection); } return false; @@ -925,7 +904,6 @@ public final class Call { mExtras, mIntentExtras, mCreationTimeMillis, - mCallIdentification, mCallDirection); } @@ -947,7 +925,6 @@ public final class Call { Bundle extras, Bundle intentExtras, long creationTimeMillis, - CallIdentification callIdentification, int callDirection) { mTelecomCallId = telecomCallId; mHandle = handle; @@ -965,7 +942,6 @@ public final class Call { mExtras = extras; mIntentExtras = intentExtras; mCreationTimeMillis = creationTimeMillis; - mCallIdentification = callIdentification; mCallDirection = callDirection; } @@ -988,7 +964,6 @@ public final class Call { parcelableCall.getExtras(), parcelableCall.getIntentExtras(), parcelableCall.getCreationTimeMillis(), - parcelableCall.getCallIdentification(), parcelableCall.getCallDirection()); } diff --git a/telecomm/java/android/telecom/CallIdentification.java b/telecomm/java/android/telecom/CallIdentification.java deleted file mode 100644 index 745affd6b17d..000000000000 --- a/telecomm/java/android/telecom/CallIdentification.java +++ /dev/null @@ -1,451 +0,0 @@ -/* - * Copyright (C) 2018 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.telecom; - -import android.annotation.IntDef; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.content.pm.ApplicationInfo; -import android.graphics.drawable.Icon; -import android.os.Parcel; -import android.os.Parcelable; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.Objects; - -/** - * Encapsulates information about an incoming or outgoing {@link Call} provided by a - * {@link CallScreeningService}. - * <p> - * Call identified information is consumed by the {@link InCallService dialer} app to provide the - * user with more information about a call. This can include information such as the name of the - * caller, address, etc. Call identification information is persisted to the - * {@link android.provider.CallLog}. - */ -public final class CallIdentification implements Parcelable { - /** - * Builder for {@link CallIdentification} instances. - * <p> - * A {@link CallScreeningService} uses this class to create new instances of - * {@link CallIdentification} for a screened call. - */ - public final static class Builder { - private CharSequence mName; - private CharSequence mDescription; - private CharSequence mDetails; - private Icon mPhoto; - private int mNuisanceConfidence = CallIdentification.CONFIDENCE_UNKNOWN; - private String mPackageName; - private CharSequence mAppName; - - /** - * Default builder constructor. - */ - public Builder() { - // Default constructor - } - - /** - * Create instance of call identification with specified package/app name. - * - * @param callIdPackageName The package name. - * @param callIdAppName The app name. - * @hide - */ - public Builder(@NonNull String callIdPackageName, @NonNull CharSequence callIdAppName) { - mPackageName = callIdPackageName; - mAppName = callIdAppName; - } - - /** - * Sets the name associated with the {@link CallIdentification} being built. - * <p> - * Could be a business name, for example. - * - * @param name The name associated with the call, or {@code null} if none is provided. - * @return Builder instance. - */ - public @NonNull Builder setName(@Nullable CharSequence name) { - mName = name; - return this; - } - - /** - * Sets the description associated with the {@link CallIdentification} being built. - * <p> - * A description of the call as identified by a {@link CallScreeningService}. The - * description is typically presented by Dialer apps after the - * {@link CallIdentification#getName() name} to provide a short piece of relevant - * information about the call. This could include a location, address, or a message - * regarding the potential nature of the call (e.g. potential telemarketer). - * - * @param description The call description, or {@code null} if none is provided. - * @return Builder instance. - */ - public @NonNull Builder setDescription(@Nullable CharSequence description) { - mDescription = description; - return this; - } - - /** - * Sets the details associated with the {@link CallIdentification} being built. - * <p> - * The details is typically presented by Dialer apps after the - * {@link CallIdentification#getName() name} and - * {@link CallIdentification#getDescription() description} to provide further clarifying - * information about the call. This could include, for example, the opening hours of a - * business, or a stats about the number of times a call has been reported as spam. - * - * @param details The call details, or {@code null} if none is provided. - * @return Builder instance. - */ - - public @NonNull Builder setDetails(@Nullable CharSequence details) { - mDetails = details; - return this; - } - - /** - * Sets the photo associated with the {@link CallIdentification} being built. - * <p> - * This could be, for example, a business logo, or a photo of the caller. - * - * @param photo The photo associated with the call, or {@code null} if none was provided. - * @return Builder instance. - */ - public @NonNull Builder setPhoto(@Nullable Icon photo) { - mPhoto = photo; - return this; - } - - /** - * Sets the nuisance confidence with the {@link CallIdentification} being built. - * <p> - * This can be used to specify how confident the {@link CallScreeningService} is that a call - * is or is not a nuisance call. - * - * @param nuisanceConfidence The nuisance confidence. - * @return The builder. - */ - public @NonNull Builder setNuisanceConfidence(@NuisanceConfidence int nuisanceConfidence) { - mNuisanceConfidence = nuisanceConfidence; - return this; - } - - /** - * Creates a new instance of {@link CallIdentification} based on the parameters set in this - * builder. - * - * @return {@link CallIdentification} instance. - */ - public @NonNull CallIdentification build() { - return new CallIdentification(mName, mDescription, mDetails, mPhoto, - mNuisanceConfidence, mPackageName, mAppName); - } - } - - /** @hide */ - @Retention(RetentionPolicy.SOURCE) - @IntDef( - prefix = { "CONFIDENCE_" }, - value = {CONFIDENCE_NUISANCE, CONFIDENCE_LIKELY_NUISANCE, CONFIDENCE_UNKNOWN, - CONFIDENCE_LIKELY_NOT_NUISANCE, CONFIDENCE_NOT_NUISANCE}) - public @interface NuisanceConfidence {} - - /** - * Call has been identified as a nuisance call. - * <p> - * Returned from {@link #getNuisanceConfidence()} to indicate that a - * {@link CallScreeningService} to indicate how confident it is that a call is or is not a - * nuisance call. - */ - public static final int CONFIDENCE_NUISANCE = 2; - - /** - * Call has been identified as a likely nuisance call. - * <p> - * Returned from {@link #getNuisanceConfidence()} to indicate that a - * {@link CallScreeningService} to indicate how confident it is that a call is or is not a - * nuisance call. - */ - public static final int CONFIDENCE_LIKELY_NUISANCE = 1; - - /** - * Call could not be classified as nuisance or non-nuisance. - * <p> - * Returned from {@link #getNuisanceConfidence()} to indicate that a - * {@link CallScreeningService} to indicate how confident it is that a call is or is not a - * nuisance call. - */ - public static final int CONFIDENCE_UNKNOWN = 0; - - /** - * Call has been identified as not likely to be a nuisance call. - * <p> - * Returned from {@link #getNuisanceConfidence()} to indicate that a - * {@link CallScreeningService} to indicate how confident it is that a call is or is not a - * nuisance call. - */ - public static final int CONFIDENCE_LIKELY_NOT_NUISANCE = -1; - - /** - * Call has been identified as not a nuisance call. - * <p> - * Returned from {@link #getNuisanceConfidence()} to indicate that a - * {@link CallScreeningService} to indicate how confident it is that a call is or is not a - * nuisance call. - */ - public static final int CONFIDENCE_NOT_NUISANCE = -2; - - /** - * Default constructor for {@link CallIdentification}. - * - * @param name The name. - * @param description The description. - * @param details The details. - * @param photo The photo. - * @param nuisanceConfidence Confidence that this is a nuisance call. - * @hide - */ - private CallIdentification(@Nullable String name, @Nullable String description, - @Nullable String details, @Nullable Icon photo, - @NuisanceConfidence int nuisanceConfidence) { - this(name, description, details, photo, nuisanceConfidence, null, null); - } - - /** - * Default constructor for {@link CallIdentification}. - * - * @param name The name. - * @param description The description. - * @param details The details. - * @param photo The photo. - * @param nuisanceConfidence Confidence that this is a nuisance call. - * @param callScreeningPackageName Package name of the {@link CallScreeningService} which - * provided the call identification. - * @param callScreeningAppName App name of the {@link CallScreeningService} which provided the - * call identification. - * @hide - */ - private CallIdentification(@Nullable CharSequence name, @Nullable CharSequence description, - @Nullable CharSequence details, @Nullable Icon photo, - @NuisanceConfidence int nuisanceConfidence, @NonNull String callScreeningPackageName, - @NonNull CharSequence callScreeningAppName) { - mName = name; - mDescription = description; - mDetails = details; - mPhoto = photo; - mNuisanceConfidence = nuisanceConfidence; - mCallScreeningAppName = callScreeningAppName; - mCallScreeningPackageName = callScreeningPackageName; - } - - private CharSequence mName; - private CharSequence mDescription; - private CharSequence mDetails; - private Icon mPhoto; - private int mNuisanceConfidence; - private String mCallScreeningPackageName; - private CharSequence mCallScreeningAppName; - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel parcel, int i) { - parcel.writeCharSequence(mName); - parcel.writeCharSequence(mDescription); - parcel.writeCharSequence(mDetails); - parcel.writeParcelable(mPhoto, 0); - parcel.writeInt(mNuisanceConfidence); - parcel.writeString(mCallScreeningPackageName); - parcel.writeCharSequence(mCallScreeningAppName); - } - - /** - * Responsible for creating CallIdentification objects for deserialized Parcels. - */ - public static final @android.annotation.NonNull Parcelable.Creator<CallIdentification> CREATOR = - new Parcelable.Creator<CallIdentification> () { - - @Override - public CallIdentification createFromParcel(Parcel source) { - CharSequence name = source.readCharSequence(); - CharSequence description = source.readCharSequence(); - CharSequence details = source.readCharSequence(); - Icon photo = source.readParcelable(ClassLoader.getSystemClassLoader()); - int nuisanceConfidence = source.readInt(); - String callScreeningPackageName = source.readString(); - CharSequence callScreeningAppName = source.readCharSequence(); - return new CallIdentification(name, description, details, photo, - nuisanceConfidence, callScreeningPackageName, callScreeningAppName); - } - - @Override - public CallIdentification[] newArray(int size) { - return new CallIdentification[size]; - } - }; - - /** - * The name associated with the number. - * <p> - * The name of the call as identified by a {@link CallScreeningService}. Could be a business - * name, for example. - * - * @return The name associated with the number, or {@code null} if none was provided. - */ - public final @Nullable CharSequence getName() { - return mName; - } - - /** - * Description of the call. - * <p> - * A description of the call as identified by a {@link CallScreeningService}. The description - * is typically presented by Dialer apps after the {@link #getName() name} to provide a short - * piece of relevant information about the call. This could include a location, address, or a - * message regarding the potential nature of the call (e.g. potential telemarketer). - * - * @return The call description, or {@code null} if none was provided. - */ - public final @Nullable CharSequence getDescription() { - return mDescription; - } - - /** - * Details of the call. - * <p> - * Details of the call as identified by a {@link CallScreeningService}. The details - * are typically presented by Dialer apps after the {@link #getName() name} and - * {@link #getDescription() description} to provide further clarifying information about the - * call. This could include, for example, the opening hours of a business, or stats about - * the number of times a call has been reported as spam. - * - * @return The call details, or {@code null} if none was provided. - */ - public final @Nullable CharSequence getDetails() { - return mDetails; - } - - /** - * Photo associated with the call. - * <p> - * A photo associated with the call as identified by a {@link CallScreeningService}. This - * could be, for example, a business logo, or a photo of the caller. - * - * @return The photo associated with the call, or {@code null} if none was provided. - */ - public final @Nullable Icon getPhoto() { - return mPhoto; - } - - /** - * Indicates the likelihood that this call is a nuisance call. - * <p> - * How likely the call is a nuisance call, as identified by a {@link CallScreeningService}. - * - * @return The nuisance confidence. - */ - public final @NuisanceConfidence int getNuisanceConfidence() { - return mNuisanceConfidence; - } - - /** - * The package name of the {@link CallScreeningService} which provided the - * {@link CallIdentification}. - * <p> - * A {@link CallScreeningService} may not set this property; it is set by the system. - * @return the package name - */ - public final @NonNull String getCallScreeningPackageName() { - return mCallScreeningPackageName; - } - - /** - * The {@link android.content.pm.PackageManager#getApplicationLabel(ApplicationInfo) name} of - * the {@link CallScreeningService} which provided the {@link CallIdentification}. - * <p> - * A {@link CallScreeningService} may not set this property; it is set by the system. - * - * @return The name of the app. - */ - public final @NonNull CharSequence getCallScreeningAppName() { - return mCallScreeningAppName; - } - - /** - * Set the package name of the {@link CallScreeningService} which provided this information. - * - * @param callScreeningPackageName The package name. - * @hide - */ - public void setCallScreeningPackageName(@NonNull String callScreeningPackageName) { - mCallScreeningPackageName = callScreeningPackageName; - } - - /** - * Set the app name of the {@link CallScreeningService} which provided this information. - * - * @param callScreeningAppName The app name. - * @hide - */ - public void setCallScreeningAppName(@NonNull CharSequence callScreeningAppName) { - mCallScreeningAppName = callScreeningAppName; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - CallIdentification that = (CallIdentification) o; - // Note: mPhoto purposely omit as no good comparison exists. - return mNuisanceConfidence == that.mNuisanceConfidence - && Objects.equals(mName, that.mName) - && Objects.equals(mDescription, that.mDescription) - && Objects.equals(mDetails, that.mDetails) - && Objects.equals(mCallScreeningAppName, that.mCallScreeningAppName) - && Objects.equals(mCallScreeningPackageName, that.mCallScreeningPackageName); - } - - @Override - public int hashCode() { - return Objects.hash(mName, mDescription, mDetails, mPhoto, mNuisanceConfidence, - mCallScreeningAppName, mCallScreeningPackageName); - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("[CallId mName="); - sb.append(Log.pii(mName)); - sb.append(", mDesc="); - sb.append(mDescription); - sb.append(", mDet="); - sb.append(mDetails); - sb.append(", conf="); - sb.append(mNuisanceConfidence); - sb.append(", appName="); - sb.append(mCallScreeningAppName); - sb.append(", pkgName="); - sb.append(mCallScreeningPackageName); - return sb.toString(); - } -} diff --git a/telecomm/java/android/telecom/CallScreeningService.java b/telecomm/java/android/telecom/CallScreeningService.java index b1aece7fbe81..0e0406d4035b 100644 --- a/telecomm/java/android/telecom/CallScreeningService.java +++ b/telecomm/java/android/telecom/CallScreeningService.java @@ -39,8 +39,8 @@ import java.lang.annotation.RetentionPolicy; /** * This service can be implemented by the default dialer (see * {@link TelecomManager#getDefaultDialerPackage()}) or a third party app to allow or disallow - * incoming calls before they are shown to a user. This service can also provide - * {@link CallIdentification} information for calls. + * incoming calls before they are shown to a user. A {@link CallScreeningService} can also see + * outgoing calls for the purpose of providing caller ID services for those calls. * <p> * Below is an example manifest registration for a {@code CallScreeningService}. * <pre> @@ -58,9 +58,9 @@ import java.lang.annotation.RetentionPolicy; * <ol> * <li>Call blocking/screening - the service can choose which calls will ring on the user's * device, and which will be silently sent to voicemail.</li> - * <li>Call identification - the service can optionally provide {@link CallIdentification} - * information about a {@link Call.Details call} which will be shown to the user in the - * Dialer app.</li> + * <li>Call identification - services which provide call identification functionality can + * display a user-interface of their choosing which contains identifying information for a call. + * </li> * </ol> * <p> * <h2>Becoming the {@link CallScreeningService}</h2> @@ -92,128 +92,6 @@ import java.lang.annotation.RetentionPolicy; * </pre> */ public abstract class CallScreeningService extends Service { - - /** @hide */ - @Retention(RetentionPolicy.SOURCE) - @IntDef( - prefix = { "CALL_DURATION_" }, - value = {CALL_DURATION_VERY_SHORT, CALL_DURATION_SHORT, CALL_DURATION_MEDIUM, - CALL_DURATION_LONG}) - public @interface CallDuration {} - - /** - * Call duration reported with {@link #EXTRA_CALL_DURATION} to indicate to the - * {@link CallScreeningService} the duration of a call for which the user reported the nuisance - * status (see {@link TelecomManager#reportNuisanceCallStatus(Uri, boolean)}). The - * {@link CallScreeningService} can use this as a signal for training nuisance detection - * algorithms. The call duration is reported in coarse grained buckets to minimize exposure of - * identifying call log information to the {@link CallScreeningService}. - * <p> - * Indicates the call was < 3 seconds in duration. - */ - public static final int CALL_DURATION_VERY_SHORT = 1; - - /** - * Call duration reported with {@link #EXTRA_CALL_DURATION} to indicate to the - * {@link CallScreeningService} the duration of a call for which the user reported the nuisance - * status (see {@link TelecomManager#reportNuisanceCallStatus(Uri, boolean)}). The - * {@link CallScreeningService} can use this as a signal for training nuisance detection - * algorithms. The call duration is reported in coarse grained buckets to minimize exposure of - * identifying call log information to the {@link CallScreeningService}. - * <p> - * Indicates the call was greater than 3 seconds, but less than 60 seconds in duration. - */ - public static final int CALL_DURATION_SHORT = 2; - - /** - * Call duration reported with {@link #EXTRA_CALL_DURATION} to indicate to the - * {@link CallScreeningService} the duration of a call for which the user reported the nuisance - * status (see {@link TelecomManager#reportNuisanceCallStatus(Uri, boolean)}). The - * {@link CallScreeningService} can use this as a signal for training nuisance detection - * algorithms. The call duration is reported in coarse grained buckets to minimize exposure of - * identifying call log information to the {@link CallScreeningService}. - * <p> - * Indicates the call was greater than 60 seconds, but less than 120 seconds in duration. - */ - public static final int CALL_DURATION_MEDIUM = 3; - - /** - * Call duration reported with {@link #EXTRA_CALL_DURATION} to indicate to the - * {@link CallScreeningService} the duration of a call for which the user reported the nuisance - * status (see {@link TelecomManager#reportNuisanceCallStatus(Uri, boolean)}). The - * {@link CallScreeningService} can use this as a signal for training nuisance detection - * algorithms. The call duration is reported in coarse grained buckets to minimize exposure of - * identifying call log information to the {@link CallScreeningService}. - * <p> - * Indicates the call was greater than 120 seconds. - */ - public static final int CALL_DURATION_LONG = 4; - - /** - * Telecom sends this intent to the {@link CallScreeningService} which the user has chosen to - * fill the call screening role when the user indicates through the default dialer whether a - * call is a nuisance call or not (see - * {@link TelecomManager#reportNuisanceCallStatus(Uri, boolean)}). - * <p> - * The following extra values are provided for the call: - * <ol> - * <li>{@link #EXTRA_CALL_HANDLE} - the handle of the call.</li> - * <li>{@link #EXTRA_IS_NUISANCE} - {@code true} if the user reported the call as a nuisance - * call, {@code false} otherwise.</li> - * <li>{@link #EXTRA_CALL_TYPE} - reports the type of call (incoming, rejected, missed, - * blocked).</li> - * <li>{@link #EXTRA_CALL_DURATION} - the duration of the call (see - * {@link #EXTRA_CALL_DURATION} for valid values).</li> - * </ol> - * <p> - * {@link CallScreeningService} implementations which want to track whether the user reports - * calls are nuisance calls should use declare a broadcast receiver in their manifest for this - * intent. - * <p> - * Note: Only {@link CallScreeningService} implementations which have provided - * {@link CallIdentification} information for calls at some point will receive this intent. - */ - public static final String ACTION_NUISANCE_CALL_STATUS_CHANGED = - "android.telecom.action.NUISANCE_CALL_STATUS_CHANGED"; - - /** - * Extra used to provide the handle of the call for - * {@link #ACTION_NUISANCE_CALL_STATUS_CHANGED}. The call handle is reported as a - * {@link Uri}. - */ - public static final String EXTRA_CALL_HANDLE = "android.telecom.extra.CALL_HANDLE"; - - /** - * Boolean extra used to indicate whether the user reported a call as a nuisance call. - * When {@code true}, the user reported that a call was a nuisance call, {@code false} - * otherwise. Sent with {@link #ACTION_NUISANCE_CALL_STATUS_CHANGED}. - */ - public static final String EXTRA_IS_NUISANCE = "android.telecom.extra.IS_NUISANCE"; - - /** - * Integer extra used with {@link #ACTION_NUISANCE_CALL_STATUS_CHANGED} to report the type of - * call. Valid values are: - * <UL> - * <li>{@link android.provider.CallLog.Calls#MISSED_TYPE}</li> - * <li>{@link android.provider.CallLog.Calls#INCOMING_TYPE}</li> - * <li>{@link android.provider.CallLog.Calls#BLOCKED_TYPE}</li> - * <li>{@link android.provider.CallLog.Calls#REJECTED_TYPE}</li> - * </UL> - */ - public static final String EXTRA_CALL_TYPE = "android.telecom.extra.CALL_TYPE"; - - /** - * Integer extra used to with {@link #ACTION_NUISANCE_CALL_STATUS_CHANGED} to report how long - * the call lasted. Valid values are: - * <UL> - * <LI>{@link #CALL_DURATION_VERY_SHORT}</LI> - * <LI>{@link #CALL_DURATION_SHORT}</LI> - * <LI>{@link #CALL_DURATION_MEDIUM}</LI> - * <LI>{@link #CALL_DURATION_LONG}</LI> - * </UL> - */ - public static final String EXTRA_CALL_DURATION = "android.telecom.extra.CALL_DURATION"; - /** * The {@link Intent} that must be declared as handled by the service. */ @@ -413,10 +291,6 @@ public abstract class CallScreeningService extends Service { * Your app can tell if a call is an incoming call by checking to see if * {@link Call.Details#getCallDirection()} is {@link Call.Details#DIRECTION_INCOMING}. * <p> - * For incoming or outgoing calls, the {@link CallScreeningService} can call - * {@link #provideCallIdentification(Call.Details, CallIdentification)} in order to provide - * {@link CallIdentification} for the call. - * <p> * Note: The {@link Call.Details} instance provided to a call screening service will only have * the following properties set. The rest of the {@link Call.Details} properties will be set to * their default value or {@code null}. @@ -472,32 +346,4 @@ public abstract class CallScreeningService extends Service { } catch (RemoteException e) { } } - - /** - * Provide {@link CallIdentification} information about a {@link Call.Details call}. - * <p> - * The {@link CallScreeningService} calls this method to provide information it has identified - * about a {@link Call.Details call}. This information will potentially be shown to the user - * in the {@link InCallService dialer} app. It will be logged to the - * {@link android.provider.CallLog}. - * <p> - * A {@link CallScreeningService} should only call this method for calls for which it is able to - * provide some {@link CallIdentification} for. {@link CallIdentification} instances with no - * fields set will be ignored by the system. - * - * @param callDetails The call to provide information for. - * <p> - * Must be the same {@link Call.Details call} which was provided to the - * {@link CallScreeningService} via {@link #onScreenCall(Call.Details)}. - * @param identification An instance of {@link CallIdentification} with information about the - * {@link Call.Details call}. - */ - public final void provideCallIdentification(@NonNull Call.Details callDetails, - @NonNull CallIdentification identification) { - try { - mCallScreeningAdapter.provideCallIdentification(callDetails.getTelecomCallId(), - identification); - } catch (RemoteException e) { - } - } } diff --git a/telecomm/java/android/telecom/ParcelableCall.java b/telecomm/java/android/telecom/ParcelableCall.java index 345707e64c23..aa50991b118c 100644 --- a/telecomm/java/android/telecom/ParcelableCall.java +++ b/telecomm/java/android/telecom/ParcelableCall.java @@ -64,7 +64,6 @@ public final class ParcelableCall implements Parcelable { private final Bundle mIntentExtras; private final Bundle mExtras; private final long mCreationTimeMillis; - private final CallIdentification mCallIdentification; private final int mCallDirection; public ParcelableCall( @@ -94,7 +93,6 @@ public final class ParcelableCall implements Parcelable { Bundle intentExtras, Bundle extras, long creationTimeMillis, - CallIdentification callIdentification, int callDirection) { mId = id; mState = state; @@ -122,7 +120,6 @@ public final class ParcelableCall implements Parcelable { mIntentExtras = intentExtras; mExtras = extras; mCreationTimeMillis = creationTimeMillis; - mCallIdentification = callIdentification; mCallDirection = callDirection; } @@ -314,15 +311,6 @@ public final class ParcelableCall implements Parcelable { } /** - * Contains call identification information returned by a {@link CallScreeningService}. - * @return The {@link CallIdentification} for this call, or {@code null} if a - * {@link CallScreeningService} did not provide information. - */ - public @Nullable CallIdentification getCallIdentification() { - return mCallIdentification; - } - - /** * Indicates whether the call is an incoming or outgoing call. */ public @CallDirection int getCallDirection() { @@ -366,7 +354,6 @@ public final class ParcelableCall implements Parcelable { boolean isRttCallChanged = source.readByte() == 1; ParcelableRttCall rttCall = source.readParcelable(classLoader); long creationTimeMillis = source.readLong(); - CallIdentification callIdentification = source.readParcelable(classLoader); int callDirection = source.readInt(); return new ParcelableCall( id, @@ -395,7 +382,6 @@ public final class ParcelableCall implements Parcelable { intentExtras, extras, creationTimeMillis, - callIdentification, callDirection); } @@ -441,7 +427,6 @@ public final class ParcelableCall implements Parcelable { destination.writeByte((byte) (mIsRttCallChanged ? 1 : 0)); destination.writeParcelable(mRttCall, 0); destination.writeLong(mCreationTimeMillis); - destination.writeParcelable(mCallIdentification, 0); destination.writeInt(mCallDirection); } diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java index 3d0a3c5d6f7f..391d788cfe72 100644 --- a/telecomm/java/android/telecom/TelecomManager.java +++ b/telecomm/java/android/telecom/TelecomManager.java @@ -2002,33 +2002,6 @@ public class TelecomManager { } /** - * Called by the default dialer to report to Telecom when the user has marked a previous - * incoming call as a nuisance call or not. - * <p> - * Where the user has chosen a {@link CallScreeningService} to fill the call screening role, - * Telecom will notify that {@link CallScreeningService} of the user's report. - * <p> - * Requires that the caller is the default dialer app. - * - * @param handle The phone number of an incoming call which the user is reporting as either a - * nuisance of non-nuisance call. - * @param isNuisanceCall {@code true} if the user is reporting the call as a nuisance call, - * {@code false} if the user is reporting the call as a non-nuisance call. - */ - @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) - public void reportNuisanceCallStatus(@NonNull Uri handle, boolean isNuisanceCall) { - ITelecomService service = getTelecomService(); - if (service != null) { - try { - service.reportNuisanceCallStatus(handle, isNuisanceCall, - mContext.getOpPackageName()); - } catch (RemoteException e) { - Log.e(TAG, "Error calling ITelecomService#showCallScreen", e); - } - } - } - - /** * Handles {@link Intent#ACTION_CALL} intents trampolined from UserCallActivity. * @param intent The {@link Intent#ACTION_CALL} intent to handle. * @hide diff --git a/telecomm/java/com/android/internal/telecom/ICallScreeningAdapter.aidl b/telecomm/java/com/android/internal/telecom/ICallScreeningAdapter.aidl index 1160d27e8f7d..3ee3285793c4 100644 --- a/telecomm/java/com/android/internal/telecom/ICallScreeningAdapter.aidl +++ b/telecomm/java/com/android/internal/telecom/ICallScreeningAdapter.aidl @@ -17,7 +17,6 @@ package com.android.internal.telecom; import android.content.ComponentName; -import android.telecom.CallIdentification; /** * Internal remote callback interface for call screening services. @@ -37,8 +36,4 @@ oneway interface ICallScreeningAdapter { boolean shouldAddToCallLog, boolean shouldShowNotification, in ComponentName componentName); - - void provideCallIdentification( - String callId, - in CallIdentification callIdentification); } diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl index 93eea56f6490..a814c03ff9ad 100644 --- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl +++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl @@ -286,8 +286,6 @@ interface ITelecomService { */ boolean isInEmergencyCall(); - oneway void reportNuisanceCallStatus(in Uri address, boolean isNuisance, String callingPackage); - /** * @see TelecomServiceImpl#handleCallIntent */ diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 2ee35b24fb04..a567d03850a6 100755 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -1896,6 +1896,16 @@ public class CarrierConfigManager { public static final String KEY_IMSI_ENCODING_METHOD_INT = "imsi_encoding_method_int"; /** + * Defines the sequence of sending an encrypted IMSI identity for EAP-SIM/AKA authentication. + * The value set as below: + * 1 - encrypted IMSI as EAP-RESPONSE/IDENTITY (default one). + * 2 - anonymous as EAP-RESPONSE/IDENTITY -> encrypted IMSI as EAP-RESPONSE/AKA|SIM-IDENTITY. + * + * @hide + */ + public static final String KEY_EAP_IDENTITY_SEQUENCE_INT = "imsi_eap_identity_sequence_int"; + + /** * Time delay (in ms) after which we show the notification to switch the preferred * network. * @hide @@ -3054,6 +3064,7 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_ALLOW_METERED_NETWORK_FOR_CERT_DOWNLOAD_BOOL, false); sDefaults.putStringArray(KEY_CARRIER_WIFI_STRING_ARRAY, null); sDefaults.putInt(KEY_IMSI_ENCODING_METHOD_INT, 2045); + sDefaults.putInt(KEY_EAP_IDENTITY_SEQUENCE_INT, 1); sDefaults.putInt(KEY_PREF_NETWORK_NOTIFICATION_DELAY_INT, -1); sDefaults.putInt(KEY_EMERGENCY_NOTIFICATION_DELAY_INT, -1); sDefaults.putBoolean(KEY_ALLOW_USSD_REQUESTS_VIA_TELEPHONY_MANAGER_BOOL, true); diff --git a/telephony/java/android/telephony/DataSpecificRegistrationInfo.java b/telephony/java/android/telephony/DataSpecificRegistrationInfo.java index 465c2b1be3d9..2cb369d28029 100644 --- a/telephony/java/android/telephony/DataSpecificRegistrationInfo.java +++ b/telephony/java/android/telephony/DataSpecificRegistrationInfo.java @@ -78,7 +78,7 @@ public final class DataSpecificRegistrationInfo implements Parcelable { * * @hide */ - public final boolean isUsingCarrierAggregation; + public boolean mIsUsingCarrierAggregation; /** * @hide @@ -92,7 +92,7 @@ public final class DataSpecificRegistrationInfo implements Parcelable { this.isNrAvailable = isNrAvailable; this.isEnDcAvailable = isEnDcAvailable; this.mLteVopsSupportInfo = lteVops; - this.isUsingCarrierAggregation = isUsingCarrierAggregation; + this.mIsUsingCarrierAggregation = isUsingCarrierAggregation; } private DataSpecificRegistrationInfo(Parcel source) { @@ -101,7 +101,7 @@ public final class DataSpecificRegistrationInfo implements Parcelable { isNrAvailable = source.readBoolean(); isEnDcAvailable = source.readBoolean(); mLteVopsSupportInfo = LteVopsSupportInfo.CREATOR.createFromParcel(source); - isUsingCarrierAggregation = source.readBoolean(); + mIsUsingCarrierAggregation = source.readBoolean(); } @Override @@ -111,7 +111,7 @@ public final class DataSpecificRegistrationInfo implements Parcelable { dest.writeBoolean(isNrAvailable); dest.writeBoolean(isEnDcAvailable); mLteVopsSupportInfo.writeToParcel(dest, flags); - dest.writeBoolean(isUsingCarrierAggregation); + dest.writeBoolean(mIsUsingCarrierAggregation); } @Override @@ -128,7 +128,7 @@ public final class DataSpecificRegistrationInfo implements Parcelable { .append(" isNrAvailable = " + isNrAvailable) .append(" isEnDcAvailable = " + isEnDcAvailable) .append(" " + mLteVopsSupportInfo.toString()) - .append(" isUsingCarrierAggregation = " + isUsingCarrierAggregation) + .append(" mIsUsingCarrierAggregation = " + mIsUsingCarrierAggregation) .append(" }") .toString(); } @@ -136,7 +136,7 @@ public final class DataSpecificRegistrationInfo implements Parcelable { @Override public int hashCode() { return Objects.hash(maxDataCalls, isDcNrRestricted, isNrAvailable, isEnDcAvailable, - mLteVopsSupportInfo, isUsingCarrierAggregation); + mLteVopsSupportInfo, mIsUsingCarrierAggregation); } @Override @@ -151,7 +151,7 @@ public final class DataSpecificRegistrationInfo implements Parcelable { && this.isNrAvailable == other.isNrAvailable && this.isEnDcAvailable == other.isEnDcAvailable && this.mLteVopsSupportInfo.equals(other.mLteVopsSupportInfo) - && this.isUsingCarrierAggregation == other.isUsingCarrierAggregation; + && this.mIsUsingCarrierAggregation == other.mIsUsingCarrierAggregation; } public static final @NonNull Parcelable.Creator<DataSpecificRegistrationInfo> CREATOR = @@ -174,4 +174,22 @@ public final class DataSpecificRegistrationInfo implements Parcelable { public LteVopsSupportInfo getLteVopsSupportInfo() { return mLteVopsSupportInfo; } + + /** + * Set the flag indicating if using carrier aggregation. + * + * @param isUsingCarrierAggregation {@code true} if using carrier aggregation. + * @hide + */ + public void setIsUsingCarrierAggregation(boolean isUsingCarrierAggregation) { + mIsUsingCarrierAggregation = isUsingCarrierAggregation; + } + + /** + * @return {@code true} if using carrier aggregation. + * @hide + */ + public boolean isUsingCarrierAggregation() { + return mIsUsingCarrierAggregation; + } } diff --git a/telephony/java/android/telephony/NetworkRegistrationInfo.java b/telephony/java/android/telephony/NetworkRegistrationInfo.java index 2bb02e7af1e0..7b9f6d5eb8a1 100644 --- a/telephony/java/android/telephony/NetworkRegistrationInfo.java +++ b/telephony/java/android/telephony/NetworkRegistrationInfo.java @@ -368,6 +368,13 @@ public final class NetworkRegistrationInfo implements Parcelable { * @hide */ public void setAccessNetworkTechnology(@NetworkType int tech) { + if (tech == TelephonyManager.NETWORK_TYPE_LTE_CA) { + // For old device backward compatibility support + tech = TelephonyManager.NETWORK_TYPE_LTE; + if (mDataSpecificInfo != null) { + mDataSpecificInfo.setIsUsingCarrierAggregation(true); + } + } mAccessNetworkTechnology = tech; } diff --git a/telephony/java/android/telephony/PhoneNumberUtils.java b/telephony/java/android/telephony/PhoneNumberUtils.java index 549c04420ce0..b75e51577fdb 100644 --- a/telephony/java/android/telephony/PhoneNumberUtils.java +++ b/telephony/java/android/telephony/PhoneNumberUtils.java @@ -2016,7 +2016,16 @@ public class PhoneNumberUtils { private static boolean isEmergencyNumberInternal(int subId, String number, String defaultCountryIso, boolean useExactMatch) { - return TelephonyManager.getDefault().isEmergencyNumber(number); + try { + if (useExactMatch) { + return TelephonyManager.getDefault().isEmergencyNumber(number); + } else { + return TelephonyManager.getDefault().isPotentialEmergencyNumber(number); + } + } catch (RuntimeException ex) { + Rlog.e(LOG_TAG, "isEmergencyNumberInternal: RuntimeException: " + ex); + } + return false; } /** diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java index a794ba12f202..d2c070593401 100644 --- a/telephony/java/android/telephony/ServiceState.java +++ b/telephony/java/android/telephony/ServiceState.java @@ -312,18 +312,6 @@ public class ServiceState implements Parcelable { private boolean mIsManualNetworkSelection; private boolean mIsEmergencyOnly; - /** - * TODO: remove mRilVoiceRadioTechnology after completely migrate to - * {@link TelephonyManager.NetworkType} - */ - @RilRadioTechnology - private int mRilVoiceRadioTechnology; - /** - * TODO: remove mRilDataRadioTechnology after completely migrate to - * {@link TelephonyManager.NetworkType} - */ - @RilRadioTechnology - private int mRilDataRadioTechnology; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) private boolean mCssIndicator; @@ -340,9 +328,6 @@ public class ServiceState implements Parcelable { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) private int mCdmaEriIconMode; - @UnsupportedAppUsage - private boolean mIsUsingCarrierAggregation; - @FrequencyRange private int mNrFrequencyRange; private int mChannelNumber; @@ -420,8 +405,6 @@ public class ServiceState implements Parcelable { mDataOperatorAlphaShort = s.mDataOperatorAlphaShort; mDataOperatorNumeric = s.mDataOperatorNumeric; mIsManualNetworkSelection = s.mIsManualNetworkSelection; - mRilVoiceRadioTechnology = s.mRilVoiceRadioTechnology; - mRilDataRadioTechnology = s.mRilDataRadioTechnology; mCssIndicator = s.mCssIndicator; mNetworkId = s.mNetworkId; mSystemId = s.mSystemId; @@ -430,7 +413,6 @@ public class ServiceState implements Parcelable { mCdmaEriIconIndex = s.mCdmaEriIconIndex; mCdmaEriIconMode = s.mCdmaEriIconMode; mIsEmergencyOnly = s.mIsEmergencyOnly; - mIsUsingCarrierAggregation = s.mIsUsingCarrierAggregation; mChannelNumber = s.mChannelNumber; mCellBandwidths = s.mCellBandwidths == null ? null : Arrays.copyOf(s.mCellBandwidths, s.mCellBandwidths.length); @@ -457,8 +439,6 @@ public class ServiceState implements Parcelable { mDataOperatorAlphaShort = in.readString(); mDataOperatorNumeric = in.readString(); mIsManualNetworkSelection = in.readInt() != 0; - mRilVoiceRadioTechnology = in.readInt(); - mRilDataRadioTechnology = in.readInt(); mCssIndicator = (in.readInt() != 0); mNetworkId = in.readInt(); mSystemId = in.readInt(); @@ -467,7 +447,6 @@ public class ServiceState implements Parcelable { mCdmaEriIconIndex = in.readInt(); mCdmaEriIconMode = in.readInt(); mIsEmergencyOnly = in.readInt() != 0; - mIsUsingCarrierAggregation = in.readInt() != 0; mLteEarfcnRsrpBoost = in.readInt(); mNetworkRegistrationInfos = new ArrayList<>(); in.readList(mNetworkRegistrationInfos, NetworkRegistrationInfo.class.getClassLoader()); @@ -486,8 +465,6 @@ public class ServiceState implements Parcelable { out.writeString(mDataOperatorAlphaShort); out.writeString(mDataOperatorNumeric); out.writeInt(mIsManualNetworkSelection ? 1 : 0); - out.writeInt(mRilVoiceRadioTechnology); - out.writeInt(mRilDataRadioTechnology); out.writeInt(mCssIndicator ? 1 : 0); out.writeInt(mNetworkId); out.writeInt(mSystemId); @@ -496,7 +473,6 @@ public class ServiceState implements Parcelable { out.writeInt(mCdmaEriIconIndex); out.writeInt(mCdmaEriIconMode); out.writeInt(mIsEmergencyOnly ? 1 : 0); - out.writeInt(mIsUsingCarrierAggregation ? 1 : 0); out.writeInt(mLteEarfcnRsrpBoost); out.writeList(mNetworkRegistrationInfos); out.writeInt(mChannelNumber); @@ -568,7 +544,7 @@ public class ServiceState implements Parcelable { @DuplexMode public int getDuplexMode() { // only support LTE duplex mode - if (!isLte(mRilDataRadioTechnology)) { + if (!isLte(getRilDataRadioTechnology())) { return DUPLEX_MODE_UNKNOWN; } @@ -850,8 +826,6 @@ public class ServiceState implements Parcelable { mDataOperatorAlphaShort, mDataOperatorNumeric, mIsManualNetworkSelection, - mRilVoiceRadioTechnology, - mRilDataRadioTechnology, mCssIndicator, mNetworkId, mSystemId, @@ -860,7 +834,6 @@ public class ServiceState implements Parcelable { mCdmaEriIconIndex, mCdmaEriIconMode, mIsEmergencyOnly, - mIsUsingCarrierAggregation, mLteEarfcnRsrpBoost, mNetworkRegistrationInfos, mNrFrequencyRange); @@ -871,7 +844,7 @@ public class ServiceState implements Parcelable { if (!(o instanceof ServiceState)) return false; ServiceState s = (ServiceState) o; - return (mVoiceRegState == s.mVoiceRegState + return mVoiceRegState == s.mVoiceRegState && mDataRegState == s.mDataRegState && mIsManualNetworkSelection == s.mIsManualNetworkSelection && mChannelNumber == s.mChannelNumber @@ -882,8 +855,6 @@ public class ServiceState implements Parcelable { && equalsHandlesNulls(mDataOperatorAlphaLong, s.mDataOperatorAlphaLong) && equalsHandlesNulls(mDataOperatorAlphaShort, s.mDataOperatorAlphaShort) && equalsHandlesNulls(mDataOperatorNumeric, s.mDataOperatorNumeric) - && equalsHandlesNulls(mRilVoiceRadioTechnology, s.mRilVoiceRadioTechnology) - && equalsHandlesNulls(mRilDataRadioTechnology, s.mRilDataRadioTechnology) && equalsHandlesNulls(mCssIndicator, s.mCssIndicator) && equalsHandlesNulls(mNetworkId, s.mNetworkId) && equalsHandlesNulls(mSystemId, s.mSystemId) @@ -891,7 +862,6 @@ public class ServiceState implements Parcelable { && equalsHandlesNulls(mCdmaDefaultRoamingIndicator, s.mCdmaDefaultRoamingIndicator) && mIsEmergencyOnly == s.mIsEmergencyOnly - && mIsUsingCarrierAggregation == s.mIsUsingCarrierAggregation) && (mNetworkRegistrationInfos == null ? s.mNetworkRegistrationInfos == null : s.mNetworkRegistrationInfos != null && mNetworkRegistrationInfos.containsAll(s.mNetworkRegistrationInfos)) @@ -1035,27 +1005,27 @@ public class ServiceState implements Parcelable { .append(", mDataOperatorAlphaShort=").append(mDataOperatorAlphaShort) .append(", isManualNetworkSelection=").append(mIsManualNetworkSelection) .append(mIsManualNetworkSelection ? "(manual)" : "(automatic)") - .append(", mRilVoiceRadioTechnology=").append(mRilVoiceRadioTechnology) - .append("(" + rilRadioTechnologyToString(mRilVoiceRadioTechnology) + ")") - .append(", mRilDataRadioTechnology=").append(mRilDataRadioTechnology) - .append("(" + rilRadioTechnologyToString(mRilDataRadioTechnology) + ")") + .append(", getRilVoiceRadioTechnology=").append(getRilVoiceRadioTechnology()) + .append("(" + rilRadioTechnologyToString(getRilVoiceRadioTechnology()) + ")") + .append(", getRilDataRadioTechnology=").append(getRilDataRadioTechnology()) + .append("(" + rilRadioTechnologyToString(getRilDataRadioTechnology()) + ")") .append(", mCssIndicator=").append(mCssIndicator ? "supported" : "unsupported") .append(", mNetworkId=").append(mNetworkId) .append(", mSystemId=").append(mSystemId) .append(", mCdmaRoamingIndicator=").append(mCdmaRoamingIndicator) .append(", mCdmaDefaultRoamingIndicator=").append(mCdmaDefaultRoamingIndicator) .append(", mIsEmergencyOnly=").append(mIsEmergencyOnly) - .append(", mIsUsingCarrierAggregation=").append(mIsUsingCarrierAggregation) + .append(", isUsingCarrierAggregation=").append(isUsingCarrierAggregation()) .append(", mLteEarfcnRsrpBoost=").append(mLteEarfcnRsrpBoost) .append(", mNetworkRegistrationInfos=").append(mNetworkRegistrationInfos) .append(", mNrFrequencyRange=").append(mNrFrequencyRange) .append("}").toString(); } - private void setNullState(int state) { - if (DBG) Rlog.d(LOG_TAG, "[ServiceState] setNullState=" + state); - mVoiceRegState = state; - mDataRegState = state; + private void init() { + if (DBG) Rlog.d(LOG_TAG, "init"); + mVoiceRegState = STATE_OUT_OF_SERVICE; + mDataRegState = STATE_OUT_OF_SERVICE; mChannelNumber = -1; mCellBandwidths = new int[0]; mVoiceOperatorAlphaLong = null; @@ -1065,8 +1035,6 @@ public class ServiceState implements Parcelable { mDataOperatorAlphaShort = null; mDataOperatorNumeric = null; mIsManualNetworkSelection = false; - mRilVoiceRadioTechnology = 0; - mRilDataRadioTechnology = 0; mCssIndicator = false; mNetworkId = -1; mSystemId = -1; @@ -1075,18 +1043,28 @@ public class ServiceState implements Parcelable { mCdmaEriIconIndex = -1; mCdmaEriIconMode = -1; mIsEmergencyOnly = false; - mIsUsingCarrierAggregation = false; mLteEarfcnRsrpBoost = 0; - mNetworkRegistrationInfos = new ArrayList<>(); mNrFrequencyRange = FREQUENCY_RANGE_UNKNOWN; + addNetworkRegistrationInfo(new NetworkRegistrationInfo.Builder() + .setDomain(NetworkRegistrationInfo.DOMAIN_CS) + .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN) + .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_UNKNOWN) + .build()); + addNetworkRegistrationInfo(new NetworkRegistrationInfo.Builder() + .setDomain(NetworkRegistrationInfo.DOMAIN_PS) + .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN) + .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_UNKNOWN) + .build()); } public void setStateOutOfService() { - setNullState(STATE_OUT_OF_SERVICE); + init(); } public void setStateOff() { - setNullState(STATE_POWER_OFF); + init(); + mVoiceRegState = STATE_POWER_OFF; + mDataRegState = STATE_POWER_OFF; } public void setState(int state) { @@ -1304,8 +1282,8 @@ public class ServiceState implements Parcelable { m.putString("data-operator-alpha-short", mDataOperatorAlphaShort); m.putString("data-operator-numeric", mDataOperatorNumeric); m.putBoolean("manual", mIsManualNetworkSelection); - m.putInt("radioTechnology", mRilVoiceRadioTechnology); - m.putInt("dataRadioTechnology", mRilDataRadioTechnology); + m.putInt("radioTechnology", getRilVoiceRadioTechnology()); + m.putInt("dataRadioTechnology", getRadioTechnology()); m.putBoolean("cssIndicator", mCssIndicator); m.putInt("networkId", mNetworkId); m.putInt("systemId", mSystemId); @@ -1313,7 +1291,7 @@ public class ServiceState implements Parcelable { m.putInt("cdmaDefaultRoamingIndicator", mCdmaDefaultRoamingIndicator); m.putBoolean("emergencyOnly", mIsEmergencyOnly); m.putBoolean("isDataRoamingFromRegistration", getDataRoamingFromRegistration()); - m.putBoolean("isUsingCarrierAggregation", mIsUsingCarrierAggregation); + m.putBoolean("isUsingCarrierAggregation", isUsingCarrierAggregation()); m.putInt("LteEarfcnRsrpBoost", mLteEarfcnRsrpBoost); m.putInt("ChannelNumber", mChannelNumber); m.putIntArray("CellBandwidths", mCellBandwidths); @@ -1323,13 +1301,9 @@ public class ServiceState implements Parcelable { /** @hide */ @TestApi public void setRilVoiceRadioTechnology(@RilRadioTechnology int rt) { - if (rt == RIL_RADIO_TECHNOLOGY_LTE_CA) { - rt = RIL_RADIO_TECHNOLOGY_LTE; - } - - this.mRilVoiceRadioTechnology = rt; - - // sync to network registration state + Rlog.e(LOG_TAG, "ServiceState.setRilVoiceRadioTechnology() called. It's encouraged to " + + "use addNetworkRegistrationInfo() instead *******"); + // Sync to network registration state NetworkRegistrationInfo regState = getNetworkRegistrationInfo( NetworkRegistrationInfo.DOMAIN_CS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN); if (regState == null) { @@ -1339,24 +1313,18 @@ public class ServiceState implements Parcelable { .build(); addNetworkRegistrationInfo(regState); } - regState.setAccessNetworkTechnology( - rilRadioTechnologyToNetworkType(mRilVoiceRadioTechnology)); + regState.setAccessNetworkTechnology(rilRadioTechnologyToNetworkType(rt)); } + /** @hide */ @TestApi public void setRilDataRadioTechnology(@RilRadioTechnology int rt) { - if (rt == RIL_RADIO_TECHNOLOGY_LTE_CA) { - rt = RIL_RADIO_TECHNOLOGY_LTE; - this.mIsUsingCarrierAggregation = true; - } else { - this.mIsUsingCarrierAggregation = false; - } - this.mRilDataRadioTechnology = rt; - if (VDBG) Rlog.d(LOG_TAG, "[ServiceState] setRilDataRadioTechnology=" + - mRilDataRadioTechnology); - - // sync to network registration state + Rlog.e(LOG_TAG, "ServiceState.setRilDataRadioTechnology() called. It's encouraged to " + + "use addNetworkRegistrationInfo() instead *******"); + // Sync to network registration state. Always write down the WWAN transport. For AP-assisted + // mode device, use addNetworkRegistrationInfo() to set the correct transport if RAT + // is IWLAN. NetworkRegistrationInfo regState = getNetworkRegistrationInfo( NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN); @@ -1367,18 +1335,32 @@ public class ServiceState implements Parcelable { .build(); addNetworkRegistrationInfo(regState); } - regState.setAccessNetworkTechnology( - rilRadioTechnologyToNetworkType(mRilDataRadioTechnology)); + regState.setAccessNetworkTechnology(rilRadioTechnologyToNetworkType(rt)); } /** @hide */ public boolean isUsingCarrierAggregation() { - return mIsUsingCarrierAggregation; + NetworkRegistrationInfo nri = getNetworkRegistrationInfo( + NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN); + if (nri != null) { + DataSpecificRegistrationInfo dsri = nri.getDataSpecificInfo(); + if (dsri != null) { + return dsri.isUsingCarrierAggregation(); + } + } + return false; } /** @hide */ public void setIsUsingCarrierAggregation(boolean ca) { - mIsUsingCarrierAggregation = ca; + NetworkRegistrationInfo nri = getNetworkRegistrationInfo( + NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN); + if (nri != null) { + DataSpecificRegistrationInfo dsri = nri.getDataSpecificInfo(); + if (dsri != null) { + dsri.setIsUsingCarrierAggregation(ca); + } + } } /** @@ -1435,12 +1417,29 @@ public class ServiceState implements Parcelable { /** @hide */ @UnsupportedAppUsage public int getRilVoiceRadioTechnology() { - return this.mRilVoiceRadioTechnology; + NetworkRegistrationInfo wwanRegInfo = getNetworkRegistrationInfo( + NetworkRegistrationInfo.DOMAIN_CS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN); + if (wwanRegInfo != null) { + return networkTypeToRilRadioTechnology(wwanRegInfo.getAccessNetworkTechnology()); + } + return RIL_RADIO_TECHNOLOGY_UNKNOWN; } /** @hide */ @UnsupportedAppUsage public int getRilDataRadioTechnology() { - return this.mRilDataRadioTechnology; + NetworkRegistrationInfo wwanRegInfo = getNetworkRegistrationInfo( + NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN); + NetworkRegistrationInfo wlanRegInfo = getNetworkRegistrationInfo( + NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WLAN); + if (wlanRegInfo != null + && wlanRegInfo.getAccessNetworkTechnology() == TelephonyManager.NETWORK_TYPE_IWLAN + && wlanRegInfo.getRegistrationState() + == NetworkRegistrationInfo.REGISTRATION_STATE_HOME) { + return RIL_RADIO_TECHNOLOGY_IWLAN; + } else if (wwanRegInfo != null) { + return networkTypeToRilRadioTechnology(wwanRegInfo.getAccessNetworkTechnology()); + } + return RIL_RADIO_TECHNOLOGY_UNKNOWN; } /** * @hide diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index f393cba4b8f1..bdd01e2f69fb 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -10861,24 +10861,28 @@ public class TelephonyManager { } /** - * Get whether reboot is required or not after making changes to modem configurations. + * Get whether making changes to modem configurations by {@link #switchMultiSimConfig(int)} will + * trigger device reboot. * The modem configuration change refers to switching from single SIM configuration to DSDS * or the other way around. - * @Return {@code true} if reboot is required after making changes to modem configurations, - * otherwise return {@code false}. * - * @hide + * <p>Requires Permission: + * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} or that the + * calling app has carrier privileges (see {@link #hasCarrierPrivileges}). + * + * @return {@code true} if reboot will be triggered after making changes to modem + * configurations, otherwise return {@code false}. */ - @SystemApi - @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) - public boolean isRebootRequiredForModemConfigChange() { + @RequiresPermission(Manifest.permission.READ_PHONE_STATE) + public boolean doesSwitchMultiSimConfigTriggerReboot() { try { ITelephony service = getITelephony(); if (service != null) { - return service.isRebootRequiredForModemConfigChange(); + return service.doesSwitchMultiSimConfigTriggerReboot(getSubId(), + getOpPackageName()); } } catch (RemoteException e) { - Log.e(TAG, "isRebootRequiredForModemConfigChange RemoteException", e); + Log.e(TAG, "doesSwitchMultiSimConfigTriggerReboot RemoteException", e); } return false; } diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java index cde10ea7cd09..96a2514e8ff0 100644 --- a/telephony/java/android/telephony/euicc/EuiccManager.java +++ b/telephony/java/android/telephony/euicc/EuiccManager.java @@ -485,7 +485,7 @@ public class EuiccManager { public boolean isEnabled() { // In the future, this may reach out to IEuiccController (if non-null) to check any dynamic // restrictions. - return getIEuiccController() != null; + return getIEuiccController() != null && refreshCardIdIfUninitialized(); } /** @@ -499,7 +499,7 @@ public class EuiccManager { */ @Nullable public String getEid() { - if (!refreshCardIdIfUninitialized()) { + if (!isEnabled()) { return null; } try { @@ -522,7 +522,7 @@ public class EuiccManager { @SystemApi @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public int getOtaStatus() { - if (!refreshCardIdIfUninitialized()) { + if (!isEnabled()) { return EUICC_OTA_STATUS_UNAVAILABLE; } try { @@ -557,7 +557,7 @@ public class EuiccManager { @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void downloadSubscription(DownloadableSubscription subscription, boolean switchAfterDownload, PendingIntent callbackIntent) { - if (!refreshCardIdIfUninitialized()) { + if (!isEnabled()) { sendUnavailableError(callbackIntent); return; } @@ -619,7 +619,7 @@ public class EuiccManager { @SystemApi @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void continueOperation(Intent resolutionIntent, Bundle resolutionExtras) { - if (!refreshCardIdIfUninitialized()) { + if (!isEnabled()) { PendingIntent callbackIntent = resolutionIntent.getParcelableExtra( EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_RESOLUTION_CALLBACK_INTENT); @@ -656,7 +656,7 @@ public class EuiccManager { @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void getDownloadableSubscriptionMetadata( DownloadableSubscription subscription, PendingIntent callbackIntent) { - if (!refreshCardIdIfUninitialized()) { + if (!isEnabled()) { sendUnavailableError(callbackIntent); return; } @@ -686,7 +686,7 @@ public class EuiccManager { @SystemApi @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void getDefaultDownloadableSubscriptionList(PendingIntent callbackIntent) { - if (!refreshCardIdIfUninitialized()) { + if (!isEnabled()) { sendUnavailableError(callbackIntent); return; } @@ -705,7 +705,7 @@ public class EuiccManager { */ @Nullable public EuiccInfo getEuiccInfo() { - if (!refreshCardIdIfUninitialized()) { + if (!isEnabled()) { return null; } try { @@ -730,7 +730,7 @@ public class EuiccManager { */ @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void deleteSubscription(int subscriptionId, PendingIntent callbackIntent) { - if (!refreshCardIdIfUninitialized()) { + if (!isEnabled()) { sendUnavailableError(callbackIntent); return; } @@ -770,7 +770,7 @@ public class EuiccManager { */ @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void switchToSubscription(int subscriptionId, PendingIntent callbackIntent) { - if (!refreshCardIdIfUninitialized()) { + if (!isEnabled()) { sendUnavailableError(callbackIntent); return; } @@ -796,7 +796,7 @@ public class EuiccManager { @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void updateSubscriptionNickname( int subscriptionId, @Nullable String nickname, @NonNull PendingIntent callbackIntent) { - if (!refreshCardIdIfUninitialized()) { + if (!isEnabled()) { sendUnavailableError(callbackIntent); return; } @@ -820,7 +820,7 @@ public class EuiccManager { @SystemApi @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void eraseSubscriptions(PendingIntent callbackIntent) { - if (!refreshCardIdIfUninitialized()) { + if (!isEnabled()) { sendUnavailableError(callbackIntent); return; } @@ -850,7 +850,7 @@ public class EuiccManager { * @hide */ public void retainSubscriptionsForFactoryReset(PendingIntent callbackIntent) { - if (!refreshCardIdIfUninitialized()) { + if (!isEnabled()) { sendUnavailableError(callbackIntent); return; } diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index 8332ffe889f3..c8cd2495bdd9 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -1945,10 +1945,10 @@ interface ITelephony { void switchMultiSimConfig(int numOfSims); /** - * Get if reboot is required upon altering modems configurations + * Get if altering modems configurations will trigger reboot. * @hide */ - boolean isRebootRequiredForModemConfigChange(); + boolean doesSwitchMultiSimConfigTriggerReboot(int subId, String callingPackage); /** * Get the mapping from logical slots to physical slots. diff --git a/tests/benchmarks/Android.bp b/tests/benchmarks/Android.bp new file mode 100644 index 000000000000..f16ddb9bc68e --- /dev/null +++ b/tests/benchmarks/Android.bp @@ -0,0 +1,29 @@ +// Copyright (C) 2015 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// build framework base core benchmarks +// ============================================================ + +java_library { + name: "networkStatsFactory-benchmarks", + installable: true, + + srcs: ["src/**/*.java"], + + libs: [ + "caliper-api-target", + "services.core", + ], + +} diff --git a/core/tests/benchmarks/src/com/android/internal/net/NetworkStatsFactoryBenchmark.java b/tests/benchmarks/src/com/android/server/net/NetworkStatsFactoryBenchmark.java index c2134649655a..ef014f0d4e53 100644 --- a/core/tests/benchmarks/src/com/android/internal/net/NetworkStatsFactoryBenchmark.java +++ b/tests/benchmarks/src/com/android/server/net/NetworkStatsFactoryBenchmark.java @@ -14,10 +14,11 @@ * limitations under the License. */ -package com.android.internal.net; +package com.android.server.net; import android.net.NetworkStats; import android.os.SystemClock; +import com.android.server.net.NetworkStatsFactory; import com.google.caliper.AfterExperiment; import com.google.caliper.BeforeExperiment; import java.io.File; diff --git a/tests/net/Android.bp b/tests/net/Android.bp index c62d85e4187e..70b408949dea 100644 --- a/tests/net/Android.bp +++ b/tests/net/Android.bp @@ -1,11 +1,8 @@ //######################################################################## // Build FrameworksNetTests package //######################################################################## - -android_test { - name: "FrameworksNetTests", - // Include all test java files. - srcs: ["java/**/*.java"], +java_defaults { + name: "FrameworksNetTests-jni-defaults", static_libs: [ "frameworks-base-testutils", "framework-protos", @@ -20,6 +17,53 @@ android_test { "android.test.base", "android.test.mock", ], + jni_libs: [ + "ld-android", + "libartbase", + "libbacktrace", + "libbase", + "libbinder", + "libbinderthreadstate", + "libbpf", + "libbpf_android", + "libc++", + "libcgrouprc", + "libcrypto", + "libcutils", + "libdexfile", + "libdl_android", + "libhidl-gen-utils", + "libhidlbase", + "libhidltransport", + "libhwbinder", + "libjsoncpp", + "liblog", + "liblzma", + "libnativehelper", + "libnetdbpf", + "libnetdutils", + "libpackagelistparser", + "libpcre2", + "libprocessgroup", + "libselinux", + "libui", + "libutils", + "libvintf", + "libvndksupport", + "libtinyxml2", + "libunwindstack", + "libutilscallstack", + "libziparchive", + "libz", + "netd_aidl_interface-cpp", + "libnetworkstatsfactorytestjni", + ], +} + +android_test { + name: "FrameworksNetTests", + defaults: ["FrameworksNetTests-jni-defaults"], + srcs: ["java/**/*.java"], platform_apis: true, test_suites: ["device-tests"], certificate: "platform", diff --git a/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java b/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java index 339cc9d2b76e..106cd1fba869 100644 --- a/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java +++ b/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java @@ -501,7 +501,7 @@ public class PermissionMonitorTest { when(mPackageManager.getPackagesForUid(MOCK_UID1)).thenReturn(new String[]{}); mObserver.onPackageRemoved(MOCK_PACKAGE1, MOCK_UID1); - mNetdServiceMonitor.expectPermission(INetd.NO_PERMISSIONS, new int[]{MOCK_UID1}); + mNetdServiceMonitor.expectPermission(INetd.PERMISSION_UNINSTALLED, new int[]{MOCK_UID1}); } @Test @@ -515,7 +515,7 @@ public class PermissionMonitorTest { // Remove and install the same package to simulate the update action when(mPackageManager.getPackagesForUid(MOCK_UID1)).thenReturn(new String[]{}); mObserver.onPackageRemoved(MOCK_PACKAGE1, MOCK_UID1); - mNetdServiceMonitor.expectPermission(INetd.NO_PERMISSIONS, new int[]{MOCK_UID1}); + mNetdServiceMonitor.expectPermission(INetd.PERMISSION_UNINSTALLED, new int[]{MOCK_UID1}); addPackage(MOCK_PACKAGE1, MOCK_UID1, new String[] {INTERNET}); mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET, new int[]{MOCK_UID1}); diff --git a/tests/net/java/com/android/internal/net/NetworkStatsFactoryTest.java b/tests/net/java/com/android/server/net/NetworkStatsFactoryTest.java index 4ec4fdd80a00..95bc7d92d538 100644 --- a/tests/net/java/com/android/internal/net/NetworkStatsFactoryTest.java +++ b/tests/net/java/com/android/server/net/NetworkStatsFactoryTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.internal.net; +package com.android.server.net; import static android.net.NetworkStats.DEFAULT_NETWORK_NO; import static android.net.NetworkStats.METERED_NO; @@ -70,6 +70,10 @@ public class NetworkStatsFactoryTest { IoUtils.deleteContents(mTestProc); } + // The libandroid_servers which have the native method is not available to + // applications. So in order to have a test support native library, the native code + // related to networkStatsFactory is compiled to a minimal native library and loaded here. + System.loadLibrary("networkstatsfactorytestjni"); mFactory = new NetworkStatsFactory(mTestProc, false); } diff --git a/tests/net/java/com/android/server/net/ipmemorystore/NetworkAttributesTest.java b/tests/net/java/com/android/server/net/ipmemorystore/NetworkAttributesTest.java index fb84611cb662..a83faf34776d 100644 --- a/tests/net/java/com/android/server/net/ipmemorystore/NetworkAttributesTest.java +++ b/tests/net/java/com/android/server/net/ipmemorystore/NetworkAttributesTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.net.ipmemorystore; +package com.android.server.connectivity.ipmemorystore; import static org.junit.Assert.assertEquals; diff --git a/tests/net/jni/Android.bp b/tests/net/jni/Android.bp new file mode 100644 index 000000000000..9225ffb24bd8 --- /dev/null +++ b/tests/net/jni/Android.bp @@ -0,0 +1,23 @@ +cc_library_shared { + name: "libnetworkstatsfactorytestjni", + + cflags: [ + "-Wall", + "-Werror", + "-Wno-unused-parameter", + "-Wthread-safety", + ], + + srcs: [ + ":lib_networkStatsFactory_native", + "test_onload.cpp", + ], + + shared_libs: [ + "libbpf_android", + "liblog", + "libnativehelper", + "libnetdbpf", + "libnetdutils", + ], +} diff --git a/tests/net/jni/test_onload.cpp b/tests/net/jni/test_onload.cpp new file mode 100644 index 000000000000..5194ddb0d882 --- /dev/null +++ b/tests/net/jni/test_onload.cpp @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2019 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. + */ + +/* + * this is a mini native libaray for NetworkStatsFactoryTest to run properly. It + * load all the native method related to NetworkStatsFactory when test run + */ +#include <nativehelper/JNIHelp.h> +#include "jni.h" +#include "utils/Log.h" +#include "utils/misc.h" + +namespace android { +int register_android_server_net_NetworkStatsFactory(JNIEnv* env); +}; + +using namespace android; + +extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) +{ + JNIEnv* env = NULL; + jint result = -1; + + if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) { + ALOGE("GetEnv failed!"); + return result; + } + ALOG_ASSERT(env, "Could not retrieve the env!"); + register_android_server_net_NetworkStatsFactory(env); + return JNI_VERSION_1_4; +} diff --git a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java index f5767453a6fb..426201732359 100644 --- a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java +++ b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java @@ -374,12 +374,11 @@ public final class WifiNetworkSuggestion implements Parcelable { } /** - * Create a network suggestion object use in + * Create a network suggestion object for use in * {@link WifiManager#addNetworkSuggestions(List)}. * - * See {@link WifiNetworkSuggestion}. - *<p> - * Note: Apps can set a combination of SSID using {@link #setSsid(String)} and BSSID + *<p class="note"> + * <b>Note:</b> Apps can set a combination of SSID using {@link #setSsid(String)} and BSSID * using {@link #setBssid(MacAddress)} to provide more fine grained network suggestions to * the platform. * </p> @@ -387,7 +386,8 @@ public final class WifiNetworkSuggestion implements Parcelable { * For example: * To provide credentials for one open, one WPA2 and one WPA3 network with their * corresponding SSID's: - * {@code + * + * <pre>{@code * final WifiNetworkSuggestion suggestion1 = * new Builder() * .setSsid("test111111") @@ -403,19 +403,20 @@ public final class WifiNetworkSuggestion implements Parcelable { * .setWpa3Passphrase("test6789") * .build() * final List<WifiNetworkSuggestion> suggestionsList = - * new ArrayList<WifiNetworkSuggestion> {{ + * new ArrayList<WifiNetworkSuggestion> { { * add(suggestion1); * add(suggestion2); * add(suggestion3); - * }}; + * } }; * final WifiManager wifiManager = * context.getSystemService(Context.WIFI_SERVICE); * wifiManager.addNetworkSuggestions(suggestionsList); - * ... - * } + * // ... + * }</pre> * - * @return Instance of {@link WifiNetworkSuggestion}. - * @throws IllegalStateException on invalid params set. + * @return Instance of {@link WifiNetworkSuggestion} + * @throws IllegalStateException on invalid params set + * @see WifiNetworkSuggestion */ public @NonNull WifiNetworkSuggestion build() { if (mSsid == null) { |