diff options
338 files changed, 11659 insertions, 3890 deletions
diff --git a/Android.bp b/Android.bp index 1b81306b3699..4e7a7b4d7ae2 100644 --- a/Android.bp +++ b/Android.bp @@ -357,6 +357,7 @@ java_defaults { "core/java/android/view/autofill/IAutoFillManagerClient.aidl", "core/java/android/view/autofill/IAugmentedAutofillManagerClient.aidl", "core/java/android/view/autofill/IAutofillWindowPresenter.aidl", + "core/java/android/view/contentcapture/IContentCaptureDirectManager.aidl", "core/java/android/view/contentcapture/IContentCaptureManager.aidl", "core/java/android/view/IApplicationToken.aidl", "core/java/android/view/IAppTransitionAnimationSpecsFuture.aidl", @@ -387,6 +388,7 @@ java_defaults { "core/java/android/speech/tts/ITextToSpeechService.aidl", "core/java/com/android/internal/app/IAppOpsActiveCallback.aidl", "core/java/com/android/internal/app/IAppOpsCallback.aidl", + "core/java/com/android/internal/app/IAppOpsNotedCallback.aidl", "core/java/com/android/internal/app/IAppOpsService.aidl", "core/java/com/android/internal/app/IBatteryStats.aidl", "core/java/com/android/internal/app/ISoundTriggerService.aidl", @@ -519,6 +521,8 @@ java_defaults { "telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl", "telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl", "telecomm/java/com/android/internal/telecom/IInCallService.aidl", + "telecomm/java/com/android/internal/telecom/IPhoneAccountSuggestionCallback.aidl", + "telecomm/java/com/android/internal/telecom/IPhoneAccountSuggestionService.aidl", "telecomm/java/com/android/internal/telecom/ITelecomService.aidl", "telecomm/java/com/android/internal/telecom/RemoteServiceCallback.aidl", "telephony/java/android/telephony/data/IDataService.aidl", diff --git a/apct-tests/perftests/multiuser/Android.mk b/apct-tests/perftests/multiuser/Android.mk index 5ff4ebc0eb80..5852044effa8 100644 --- a/apct-tests/perftests/multiuser/Android.mk +++ b/apct-tests/perftests/multiuser/Android.mk @@ -20,7 +20,7 @@ LOCAL_MODULE_TAGS := tests LOCAL_SRC_FILES := $(call all-java-files-under, src) LOCAL_STATIC_JAVA_LIBRARIES := \ - android-support-test \ + androidx.test.rules \ apct-perftests-utils LOCAL_PACKAGE_NAME := MultiUserPerfTests diff --git a/apct-tests/perftests/multiuser/AndroidManifest.xml b/apct-tests/perftests/multiuser/AndroidManifest.xml index adb316fe6c43..e96771cf1c17 100644 --- a/apct-tests/perftests/multiuser/AndroidManifest.xml +++ b/apct-tests/perftests/multiuser/AndroidManifest.xml @@ -25,7 +25,7 @@ <uses-library android:name="android.test.runner" /> </application> - <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner" + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" android:targetPackage="com.android.perftests.multiuser"/> </manifest> diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/BenchmarkResultsReporter.java b/apct-tests/perftests/multiuser/src/android/multiuser/BenchmarkResultsReporter.java index d3a3ce54e378..ba33e6439fbd 100644 --- a/apct-tests/perftests/multiuser/src/android/multiuser/BenchmarkResultsReporter.java +++ b/apct-tests/perftests/multiuser/src/android/multiuser/BenchmarkResultsReporter.java @@ -18,9 +18,10 @@ package android.multiuser; import android.app.Activity; import android.app.Instrumentation; import android.os.Bundle; -import android.support.test.InstrumentationRegistry; import android.util.Log; +import androidx.test.InstrumentationRegistry; + import org.junit.rules.TestRule; import org.junit.runner.Description; import org.junit.runners.model.Statement; diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java index 855be0859520..2fdba0af2c1b 100644 --- a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java +++ b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java @@ -27,9 +27,10 @@ import android.content.pm.UserInfo; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.LargeTest; -import android.support.test.runner.AndroidJUnit4; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.LargeTest; +import androidx.test.runner.AndroidJUnit4; import org.junit.After; import org.junit.Before; @@ -50,7 +51,7 @@ import java.util.concurrent.TimeUnit; * adb install -r \ * ${ANDROID_PRODUCT_OUT}/data/app/MultiUserPerfTests/MultiUserPerfTests.apk && * adb shell am instrument -e class android.multiuser.UserLifecycleTests \ - * -w com.android.perftests.multiuser/android.support.test.runner.AndroidJUnitRunner + * -w com.android.perftests.multiuser/androidx.test.runner.AndroidJUnitRunner * * or * diff --git a/api/current.txt b/api/current.txt index 11f7137ac49d..b272b9b9292a 100644 --- a/api/current.txt +++ b/api/current.txt @@ -5730,7 +5730,6 @@ package android.app { public final class NotificationChannelGroup implements android.os.Parcelable { ctor public NotificationChannelGroup(java.lang.String, java.lang.CharSequence); - method public boolean canOverlayApps(); method public android.app.NotificationChannelGroup clone(); method public int describeContents(); method public java.util.List<android.app.NotificationChannel> getChannels(); @@ -5738,7 +5737,6 @@ package android.app { method public java.lang.String getId(); method public java.lang.CharSequence getName(); method public boolean isBlocked(); - method public void setAllowAppOverlay(boolean); method public void setDescription(java.lang.String); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.app.NotificationChannelGroup> CREATOR; @@ -5746,6 +5744,7 @@ package android.app { public class NotificationManager { method public java.lang.String addAutomaticZenRule(android.app.AutomaticZenRule); + method public boolean areAppOverlaysAllowed(); method public boolean areNotificationsEnabled(); method public boolean canNotifyAsPackage(java.lang.String); method public void cancel(int); @@ -10268,6 +10267,7 @@ package android.content { field public static final java.lang.String CATEGORY_OPENABLE = "android.intent.category.OPENABLE"; field public static final java.lang.String CATEGORY_PREFERENCE = "android.intent.category.PREFERENCE"; field public static final java.lang.String CATEGORY_SAMPLE_CODE = "android.intent.category.SAMPLE_CODE"; + field public static final java.lang.String CATEGORY_SECONDARY_HOME = "android.intent.category.SECONDARY_HOME"; field public static final java.lang.String CATEGORY_SELECTED_ALTERNATIVE = "android.intent.category.SELECTED_ALTERNATIVE"; field public static final java.lang.String CATEGORY_TAB = "android.intent.category.TAB"; field public static final java.lang.String CATEGORY_TEST = "android.intent.category.TEST"; @@ -24988,8 +24988,9 @@ package android.media { field public static final int RATING_KEY_BY_USER = 268435457; // 0x10000001 } - public class MediaMetadataRetriever { + public class MediaMetadataRetriever implements java.lang.AutoCloseable { ctor public MediaMetadataRetriever(); + method public void close(); method public java.lang.String extractMetadata(int); method public byte[] getEmbeddedPicture(); method public android.graphics.Bitmap getFrameAtIndex(int, android.media.MediaMetadataRetriever.BitmapParams); @@ -26076,7 +26077,12 @@ package android.media { public class ThumbnailUtils { ctor public ThumbnailUtils(); - method public static android.graphics.Bitmap createVideoThumbnail(java.lang.String, int); + method public static deprecated android.graphics.Bitmap createAudioThumbnail(java.lang.String, int); + method public static android.graphics.Bitmap createAudioThumbnail(java.io.File, android.util.Size, android.os.CancellationSignal) throws java.io.IOException; + method public static deprecated android.graphics.Bitmap createImageThumbnail(java.lang.String, int); + method public static android.graphics.Bitmap createImageThumbnail(java.io.File, android.util.Size, android.os.CancellationSignal) throws java.io.IOException; + method public static deprecated android.graphics.Bitmap createVideoThumbnail(java.lang.String, int); + method public static android.graphics.Bitmap createVideoThumbnail(java.io.File, android.util.Size, android.os.CancellationSignal) throws java.io.IOException; method public static android.graphics.Bitmap extractThumbnail(android.graphics.Bitmap, int, int); method public static android.graphics.Bitmap extractThumbnail(android.graphics.Bitmap, int, int, int); field public static final int OPTIONS_RECYCLE_INPUT = 2; // 0x2 @@ -38342,6 +38348,11 @@ package android.provider { field public static final java.lang.String VALUE = "value"; } + public static final class Settings.Panel { + field public static final java.lang.String ACTION_INTERNET_CONNECTIVITY = "android.settings.panel.action.INTERNET_CONNECTIVITY"; + field public static final java.lang.String ACTION_VOLUME = "android.settings.panel.action.VOLUME"; + } + public static final class Settings.Secure extends android.provider.Settings.NameValueTable { ctor public Settings.Secure(); method public static float getFloat(android.content.ContentResolver, java.lang.String, float); @@ -41230,10 +41241,12 @@ package android.service.quicksettings { method public android.graphics.drawable.Icon getIcon(); method public java.lang.CharSequence getLabel(); method public int getState(); + method public java.lang.CharSequence getSubtitle(); method public void setContentDescription(java.lang.CharSequence); method public void setIcon(android.graphics.drawable.Icon); method public void setLabel(java.lang.CharSequence); method public void setState(int); + method public void setSubtitle(java.lang.CharSequence); method public void updateTile(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.service.quicksettings.Tile> CREATOR; @@ -43625,6 +43638,10 @@ package android.telephony { field public static final java.lang.String KEY_MONTHLY_DATA_CYCLE_DAY_INT = "monthly_data_cycle_day_int"; field public static final java.lang.String KEY_ONLY_SINGLE_DC_ALLOWED_INT_ARRAY = "only_single_dc_allowed_int_array"; field public static final java.lang.String KEY_OPERATOR_SELECTION_EXPAND_BOOL = "operator_selection_expand_bool"; + field public static final java.lang.String KEY_OPPORTUNISTIC_NETWORK_ENTRY_THRESHOLD_RSRP_INT = "opportunistic_network_entry_threshold_rsrp_int"; + field public static final java.lang.String KEY_OPPORTUNISTIC_NETWORK_ENTRY_THRESHOLD_RSSNR_INT = "opportunistic_network_entry_threshold_rssnr_int"; + field public static final java.lang.String KEY_OPPORTUNISTIC_NETWORK_EXIT_THRESHOLD_RSRP_INT = "opportunistic_network_exit_threshold_rsrp_int"; + field public static final java.lang.String KEY_OPPORTUNISTIC_NETWORK_EXIT_THRESHOLD_RSSNR_INT = "opportunistic_network_exit_threshold_rssnr_int"; field public static final java.lang.String KEY_PREFER_2G_BOOL = "prefer_2g_bool"; field public static final java.lang.String KEY_RADIO_RESTART_FAILURE_CAUSES_INT_ARRAY = "radio_restart_failure_causes_int_array"; field public static final java.lang.String KEY_RCS_CONFIG_SERVER_URL_STRING = "rcs_config_server_url_string"; @@ -52206,13 +52223,13 @@ package android.view.contentcapture { method public void setContentCaptureEnabled(boolean); } - public final class ContentCaptureSession implements java.lang.AutoCloseable { + public abstract class ContentCaptureSession implements java.lang.AutoCloseable { method public void close(); - method public void destroy(); - method public android.view.contentcapture.ContentCaptureSessionId getContentCaptureSessionId(); - method public void notifyViewAppeared(android.view.ViewStructure); - method public void notifyViewDisappeared(android.view.autofill.AutofillId); - method public void notifyViewTextChanged(android.view.autofill.AutofillId, java.lang.CharSequence, int); + method public final void destroy(); + method public final android.view.contentcapture.ContentCaptureSessionId getContentCaptureSessionId(); + method public final void notifyViewAppeared(android.view.ViewStructure); + method public final void notifyViewDisappeared(android.view.autofill.AutofillId); + method public final void notifyViewTextChanged(android.view.autofill.AutofillId, java.lang.CharSequence, int); field public static final int FLAG_USER_INPUT = 1; // 0x1 } diff --git a/api/system-current.txt b/api/system-current.txt index 7d890c619503..5014abfd79e7 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -27,6 +27,7 @@ package android { field public static final java.lang.String BIND_KEYGUARD_APPWIDGET = "android.permission.BIND_KEYGUARD_APPWIDGET"; field public static final java.lang.String BIND_NETWORK_RECOMMENDATION_SERVICE = "android.permission.BIND_NETWORK_RECOMMENDATION_SERVICE"; field public static final java.lang.String BIND_NOTIFICATION_ASSISTANT_SERVICE = "android.permission.BIND_NOTIFICATION_ASSISTANT_SERVICE"; + field public static final java.lang.String BIND_PHONE_ACCOUNT_SUGGESTION_SERVICE = "android.permission.BIND_PHONE_ACCOUNT_SUGGESTION_SERVICE"; field public static final java.lang.String BIND_PRINT_RECOMMENDATION_SERVICE = "android.permission.BIND_PRINT_RECOMMENDATION_SERVICE"; field public static final java.lang.String BIND_RESOLVER_RANKER_SERVICE = "android.permission.BIND_RESOLVER_RANKER_SERVICE"; field public static final java.lang.String BIND_RUNTIME_PERMISSION_PRESENTER_SERVICE = "android.permission.BIND_RUNTIME_PERMISSION_PRESENTER_SERVICE"; @@ -1471,6 +1472,8 @@ package android.hardware.display { public final class BrightnessConfiguration implements android.os.Parcelable { method public int describeContents(); + method public android.hardware.display.BrightnessCorrection getCorrectionByCategory(int); + method public android.hardware.display.BrightnessCorrection getCorrectionByPackageName(java.lang.String); method public android.util.Pair<float[], float[]> getCurve(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.hardware.display.BrightnessConfiguration> CREATOR; @@ -1478,10 +1481,22 @@ package android.hardware.display { public static class BrightnessConfiguration.Builder { ctor public BrightnessConfiguration.Builder(float[], float[]); + method public android.hardware.display.BrightnessConfiguration.Builder addCorrectionByCategory(int, android.hardware.display.BrightnessCorrection); + method public android.hardware.display.BrightnessConfiguration.Builder addCorrectionByPackageName(java.lang.String, android.hardware.display.BrightnessCorrection); method public android.hardware.display.BrightnessConfiguration build(); + method public int getMaxCorrectionsByCategory(); + method public int getMaxCorrectionsByPackageName(); method public android.hardware.display.BrightnessConfiguration.Builder setDescription(java.lang.String); } + public final class BrightnessCorrection implements android.os.Parcelable { + method public float apply(float); + method public static android.hardware.display.BrightnessCorrection createScaleAndTranslateLog(float, float); + method public int describeContents(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.hardware.display.BrightnessCorrection> CREATOR; + } + public final class DisplayManager { method public java.util.List<android.hardware.display.AmbientBrightnessDayStats> getAmbientBrightnessStats(); method public android.hardware.display.BrightnessConfiguration getBrightnessConfiguration(); @@ -2520,7 +2535,37 @@ package android.hardware.usb { } public class UsbManager { + method public java.util.List<android.hardware.usb.UsbPort> getPorts(); method public void grantPermission(android.hardware.usb.UsbDevice, java.lang.String); + field public static final java.lang.String ACTION_USB_PORT_CHANGED = "android.hardware.usb.action.USB_PORT_CHANGED"; + } + + public final class UsbPort { + method public android.hardware.usb.UsbPortStatus getStatus(); + method public void setRoles(int, int); + } + + public final class UsbPortStatus implements android.os.Parcelable { + method public int describeContents(); + method public int getCurrentDataRole(); + method public int getCurrentMode(); + method public int getCurrentPowerRole(); + method public int getSupportedRoleCombinations(); + method public boolean isConnected(); + method public boolean isRoleCombinationSupported(int, int); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.hardware.usb.UsbPortStatus> CREATOR; + field public static final int DATA_ROLE_DEVICE = 2; // 0x2 + field public static final int DATA_ROLE_HOST = 1; // 0x1 + field public static final int DATA_ROLE_NONE = 0; // 0x0 + field public static final int MODE_AUDIO_ACCESSORY = 4; // 0x4 + field public static final int MODE_DEBUG_ACCESSORY = 8; // 0x8 + field public static final int MODE_DFP = 2; // 0x2 + field public static final int MODE_NONE = 0; // 0x0 + field public static final int MODE_UFP = 1; // 0x1 + field public static final int POWER_ROLE_NONE = 0; // 0x0 + field public static final int POWER_ROLE_SINK = 2; // 0x2 + field public static final int POWER_ROLE_SOURCE = 1; // 0x1 } } @@ -5017,7 +5062,7 @@ package android.service.contentcapture { method public final java.util.Set<android.content.ComponentName> getContentCaptureDisabledActivities(); method public final java.util.Set<java.lang.String> getContentCaptureDisabledPackages(); method public void onActivitySnapshot(android.view.contentcapture.ContentCaptureSessionId, android.service.contentcapture.SnapshotData); - method public abstract void onContentCaptureEventsRequest(android.view.contentcapture.ContentCaptureSessionId, android.service.contentcapture.ContentCaptureEventsRequest); + method public void onContentCaptureEventsRequest(android.view.contentcapture.ContentCaptureSessionId, android.service.contentcapture.ContentCaptureEventsRequest); method public void onCreateContentCaptureSession(android.view.contentcapture.ContentCaptureContext, android.view.contentcapture.ContentCaptureSessionId); method public void onDestroyContentCaptureSession(android.view.contentcapture.ContentCaptureSessionId); method public final void setActivityContentCaptureEnabled(android.content.ComponentName, boolean); @@ -5605,6 +5650,14 @@ package android.telecom { ctor public PhoneAccountSuggestion(android.telecom.PhoneAccountHandle, int, boolean); } + public class PhoneAccountSuggestionService extends android.app.Service { + ctor public PhoneAccountSuggestionService(); + method public void onAccountSuggestionRequest(java.lang.String); + method public android.os.IBinder onBind(android.content.Intent); + method public final void suggestPhoneAccounts(java.lang.String, java.util.List<android.telecom.PhoneAccountSuggestion>); + field public static final java.lang.String SERVICE_INTERFACE = "android.telecom.PhoneAccountSuggestionService"; + } + public final class RemoteConference { method public deprecated void setAudioState(android.telecom.AudioState); } @@ -6097,6 +6150,7 @@ package android.telephony { method public boolean getEmergencyCallbackMode(); method public java.lang.String getIsimDomain(); method public int getPreferredNetworkType(int); + method public int getPreferredNetworkTypeBitmap(); method public int getRadioPowerState(); method public int getSimApplicationState(); method public int getSimCardState(); @@ -6125,6 +6179,7 @@ package android.telephony { method public void setDataActivationState(int); method public deprecated void setDataEnabled(int, boolean); method public void setDataRoamingEnabled(boolean); + method public boolean setPreferredNetworkTypeBitmap(int); method public boolean setRadio(boolean); method public boolean setRadioPower(boolean); method public void setSimPowerState(int); diff --git a/api/test-current.txt b/api/test-current.txt index 46e7683c3cb7..1e15792fd92c 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -182,7 +182,6 @@ package android.app { method public int getUserLockedFields(); method public void lockFields(int); method public void setBlocked(boolean); - field public static final int USER_LOCKED_ALLOW_APP_OVERLAY = 2; // 0x2 } public class NotificationManager { @@ -1282,6 +1281,14 @@ package android.telecom { ctor public PhoneAccountSuggestion(android.telecom.PhoneAccountHandle, int, boolean); } + public class PhoneAccountSuggestionService extends android.app.Service { + ctor public PhoneAccountSuggestionService(); + method public void onAccountSuggestionRequest(java.lang.String); + method public android.os.IBinder onBind(android.content.Intent); + method public final void suggestPhoneAccounts(java.lang.String, java.util.List<android.telecom.PhoneAccountSuggestion>); + field public static final java.lang.String SERVICE_INTERFACE = "android.telecom.PhoneAccountSuggestionService"; + } + } package android.telephony { diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java index e3748f1653ab..3defdc5467c8 100644 --- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java +++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java @@ -247,7 +247,7 @@ public class Bmgr { } try { - mBmgr.dataChanged(pkg); + mBmgr.dataChangedForUser(userId, pkg); } catch (RemoteException e) { System.err.println(e.toString()); System.err.println(BMGR_NOT_RUNNING_ERR); @@ -264,7 +264,8 @@ public class Bmgr { } if (allPkgs.size() > 0) { try { - mBmgr.fullTransportBackup(allPkgs.toArray(new String[allPkgs.size()])); + mBmgr.fullTransportBackupForUser( + userId, allPkgs.toArray(new String[allPkgs.size()])); } catch (RemoteException e) { System.err.println(e.toString()); System.err.println(BMGR_NOT_RUNNING_ERR); @@ -393,7 +394,7 @@ public class Bmgr { installedPackages.stream().map(p -> p.packageName).toArray(String[]::new); String[] filteredPackages = {}; try { - filteredPackages = mBmgr.filterAppsEligibleForBackup(packages); + filteredPackages = mBmgr.filterAppsEligibleForBackupForUser(userId, packages); } catch (RemoteException e) { System.err.println(e.toString()); System.err.println(BMGR_NOT_RUNNING_ERR); @@ -498,11 +499,11 @@ public class Bmgr { } if ("-c".equals(which)) { - doTransportByComponent(); + doTransportByComponent(userId); return; } - String old = mBmgr.selectBackupTransport(which); + String old = mBmgr.selectBackupTransportForUser(userId, which); if (old == null) { System.out.println("Unknown transport '" + which + "' specified; no changes made."); @@ -516,7 +517,7 @@ public class Bmgr { } } - private void doTransportByComponent() { + private void doTransportByComponent(@UserIdInt int userId) { String which = nextArg(); if (which == null) { showUsage(); @@ -526,7 +527,9 @@ public class Bmgr { final CountDownLatch latch = new CountDownLatch(1); try { - mBmgr.selectBackupTransportAsync(ComponentName.unflattenFromString(which), + mBmgr.selectBackupTransportAsyncForUser( + userId, + ComponentName.unflattenFromString(which), new ISelectBackupTransportCallback.Stub() { @Override public void onSuccess(String transportName) { @@ -567,7 +570,7 @@ public class Bmgr { } try { - mBmgr.clearBackupData(transport, pkg); + mBmgr.clearBackupDataForUser(userId, transport, pkg); System.out.println("Wiped backup data for " + pkg + " on " + transport); } catch (RemoteException e) { System.err.println(e.toString()); @@ -599,7 +602,8 @@ public class Bmgr { InitObserver observer = new InitObserver(); try { System.out.println("Initializing transports: " + transports); - mBmgr.initializeTransports(transports.toArray(new String[transports.size()]), observer); + mBmgr.initializeTransportsForUser( + userId, transports.toArray(new String[transports.size()]), observer); observer.waitForCompletion(30*1000L); System.out.println("Initialization result: " + observer.result); } catch (RemoteException e) { @@ -611,13 +615,13 @@ public class Bmgr { private void doList(@UserIdInt int userId) { String arg = nextArg(); // sets, transports, packages set# if ("transports".equals(arg)) { - doListTransports(); + doListTransports(userId); return; } // The rest of the 'list' options work with a restore session on the current transport try { - mRestore = mBmgr.beginRestoreSession(null, null); + mRestore = mBmgr.beginRestoreSessionForUser(userId, null, null); if (mRestore == null) { System.err.println(BMGR_NOT_RUNNING_ERR); return; @@ -634,19 +638,19 @@ public class Bmgr { } } - private void doListTransports() { + private void doListTransports(@UserIdInt int userId) { String arg = nextArg(); try { if ("-c".equals(arg)) { - for (ComponentName transport : mBmgr.listAllTransportComponents()) { + for (ComponentName transport : mBmgr.listAllTransportComponentsForUser(userId)) { System.out.println(transport.flattenToShortString()); } return; } - String current = mBmgr.getCurrentTransport(); - String[] transports = mBmgr.listAllTransports(); + String current = mBmgr.getCurrentTransportForUser(userId); + String[] transports = mBmgr.listAllTransportsForUser(userId); if (transports == null || transports.length == 0) { System.out.println("No transports available."); return; @@ -756,7 +760,7 @@ public class Bmgr { filter.add(arg); } - doRestoreAll(token, filter); + doRestoreAll(userId, token, filter); } catch (NumberFormatException e) { showUsage(); return; @@ -769,12 +773,12 @@ public class Bmgr { System.err.println("'restore <token> <package>'."); } - private void doRestoreAll(long token, HashSet<String> filter) { + private void doRestoreAll(@UserIdInt int userId, long token, HashSet<String> filter) { RestoreObserver observer = new RestoreObserver(); try { boolean didRestore = false; - mRestore = mBmgr.beginRestoreSession(null, null); + mRestore = mBmgr.beginRestoreSessionForUser(userId, null, null); if (mRestore == null) { System.err.println(BMGR_NOT_RUNNING_ERR); return; diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp index a9819972cfc7..69cb264ef237 100644 --- a/cmds/statsd/src/StatsLogProcessor.cpp +++ b/cmds/statsd/src/StatsLogProcessor.cpp @@ -293,7 +293,7 @@ void StatsLogProcessor::onDumpReport(const ConfigKey& key, const int64_t dumpTim // Then, check stats-data directory to see there's any file containing // ConfigMetricsReport from previous shutdowns to concatenate to reports. - StorageManager::appendConfigMetricsReport(key, proto); + StorageManager::appendConfigMetricsReport(key, proto, erase_data); auto it = mMetricsManagers.find(key); if (it != mMetricsManagers.end()) { diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp index 04173b217dcb..50b64b986866 100644 --- a/cmds/statsd/src/StatsService.cpp +++ b/cmds/statsd/src/StatsService.cpp @@ -296,6 +296,7 @@ void StatsService::dumpIncidentSection(int out) { ADB_DUMP, &proto); proto.end(reportsListToken); proto.flush(out); + proto.clear(); } } @@ -466,23 +467,12 @@ status_t StatsService::cmd_trigger_broadcast(int out, Vector<String8>& args) { name.assign(args[1].c_str(), args[1].size()); good = true; } else if (argCount == 3) { - // If it's a userdebug or eng build, then the shell user can - // impersonate other uids. - if (mEngBuild) { - const char* s = args[1].c_str(); - if (*s != '\0') { - char* end = NULL; - uid = strtol(s, &end, 0); - if (*end == '\0') { - name.assign(args[2].c_str(), args[2].size()); - good = true; - } - } - } else { - dprintf(out, - "The metrics can only be dumped for other UIDs on eng or userdebug " - "builds.\n"); + good = getUidFromArgs(args, 1, uid); + if (!good) { + dprintf(out, "Invalid UID. Note that the metrics can only be dumped for " + "other UIDs on eng or userdebug builds.\n"); } + name.assign(args[2].c_str(), args[2].size()); } if (!good) { print_cmd_help(out); @@ -518,23 +508,12 @@ status_t StatsService::cmd_config(int in, int out, int err, Vector<String8>& arg name.assign(args[2].c_str(), args[2].size()); good = true; } else if (argCount == 4) { - // If it's a userdebug or eng build, then the shell user can - // impersonate other uids. - if (mEngBuild) { - const char* s = args[2].c_str(); - if (*s != '\0') { - char* end = NULL; - uid = strtol(s, &end, 0); - if (*end == '\0') { - name.assign(args[3].c_str(), args[3].size()); - good = true; - } - } - } else { - dprintf(err, - "The config can only be set for other UIDs on eng or userdebug " - "builds.\n"); + good = getUidFromArgs(args, 2, uid); + if (!good) { + dprintf(err, "Invalid UID. Note that the config can only be set for " + "other UIDs on eng or userdebug builds.\n"); } + name.assign(args[3].c_str(), args[3].size()); } else if (argCount == 2 && args[1] == "remove") { good = true; } @@ -612,23 +591,12 @@ status_t StatsService::cmd_dump_report(int out, const Vector<String8>& args) { name.assign(args[1].c_str(), args[1].size()); good = true; } else if (argCount == 3) { - // If it's a userdebug or eng build, then the shell user can - // impersonate other uids. - if (mEngBuild) { - const char* s = args[1].c_str(); - if (*s != '\0') { - char* end = NULL; - uid = strtol(s, &end, 0); - if (*end == '\0') { - name.assign(args[2].c_str(), args[2].size()); - good = true; - } - } - } else { - dprintf(out, - "The metrics can only be dumped for other UIDs on eng or userdebug " - "builds.\n"); + good = getUidFromArgs(args, 1, uid); + if (!good) { + dprintf(out, "Invalid UID. Note that the metrics can only be dumped for " + "other UIDs on eng or userdebug builds.\n"); } + name.assign(args[2].c_str(), args[2].size()); } if (good) { vector<uint8_t> data; @@ -714,18 +682,14 @@ status_t StatsService::cmd_log_app_breadcrumb(int out, const Vector<String8>& ar state = atoi(args[2].c_str()); good = true; } else if (argCount == 4) { - uid = atoi(args[1].c_str()); - // If it's a userdebug or eng build, then the shell user can impersonate other uids. - // Otherwise, the uid must match the actual caller's uid. - if (mEngBuild || (uid >= 0 && (uid_t)uid == IPCThreadState::self()->getCallingUid())) { - label = atoi(args[2].c_str()); - state = atoi(args[3].c_str()); - good = true; - } else { + good = getUidFromArgs(args, 1, uid); + if (!good) { dprintf(out, - "Selecting a UID for writing AppBreadcrumb can only be done for other UIDs " - "on eng or userdebug builds.\n"); + "Invalid UID. Note that selecting a UID for writing AppBreadcrumb can only be " + "done for other UIDs on eng or userdebug builds.\n"); } + label = atoi(args[2].c_str()); + state = atoi(args[3].c_str()); } if (good) { dprintf(out, "Logging AppBreadcrumbReported(%d, %d, %d) to statslog.\n", uid, label, state); @@ -792,6 +756,28 @@ status_t StatsService::cmd_print_logs(int out, const Vector<String8>& args) { } } +bool StatsService::getUidFromArgs(const Vector<String8>& args, size_t uidArgIndex, int32_t& uid) { + const char* s = args[uidArgIndex].c_str(); + if (*s == '\0') { + return false; + } + char* endc = NULL; + int64_t longUid = strtol(s, &endc, 0); + if (*endc != '\0') { + return false; + } + int32_t goodUid = static_cast<int32_t>(longUid); + if (longUid < 0 || static_cast<uint64_t>(longUid) != static_cast<uid_t>(goodUid)) { + return false; // It was not of uid_t type. + } + uid = goodUid; + + int32_t callingUid = IPCThreadState::self()->getCallingUid(); + return mEngBuild // UserDebug/EngBuild are allowed to impersonate uids. + || (callingUid == goodUid) // Anyone can 'impersonate' themselves. + || (callingUid == AID_ROOT && goodUid == AID_SHELL); // ROOT can impersonate SHELL. +} + Status StatsService::informAllUidData(const vector<int32_t>& uid, const vector<int64_t>& version, const vector<String16>& version_string, const vector<String16>& app, diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h index cd4d601a606f..135a3c9cde51 100644 --- a/cmds/statsd/src/StatsService.h +++ b/cmds/statsd/src/StatsService.h @@ -291,6 +291,15 @@ private: status_t cmd_print_logs(int outFd, const Vector<String8>& args); /** + * Writes the value of args[uidArgIndex] into uid. + * Returns whether the uid is reasonable (type uid_t) and whether + * 1. it is equal to the calling uid, or + * 2. the device is mEngBuild, or + * 3. the caller is AID_ROOT and the uid is AID_SHELL (i.e. ROOT can impersonate SHELL). + */ + bool getUidFromArgs(const Vector<String8>& args, size_t uidArgIndex, int32_t& uid); + + /** * Adds a configuration after checking permissions and obtaining UID from binder call. */ bool addConfigurationChecked(int uid, int64_t key, const vector<uint8_t>& config); @@ -340,6 +349,7 @@ private: FRIEND_TEST(StatsServiceTest, TestAddConfig_simple); FRIEND_TEST(StatsServiceTest, TestAddConfig_empty); FRIEND_TEST(StatsServiceTest, TestAddConfig_invalid); + FRIEND_TEST(StatsServiceTest, TestGetUidFromArgs); FRIEND_TEST(PartialBucketE2eTest, TestCountMetricNoSplitOnNewApp); FRIEND_TEST(PartialBucketE2eTest, TestCountMetricSplitOnUpgrade); FRIEND_TEST(PartialBucketE2eTest, TestCountMetricSplitOnRemoval); diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index 5899e0cfbca1..bdcdc536759d 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -171,6 +171,7 @@ message Atom { DocsUIUserActionReported docs_ui_user_action_reported = 112; WifiEnabledStateChanged wifi_enabled_state_changed = 113; WifiRunningStateChanged wifi_running_state_changed = 114; + AppCompacted app_compacted = 115; } // Pulled events will start at field 10000. @@ -3649,3 +3650,65 @@ message DocsUIUserActionReported { message DocsUIInvalidScopedAccessRequestReported { optional android.stats.docsui.InvalidScopedAccess type = 1; } + +/** + * Logs when an app's memory is compacted. + * + * Logged from: + * frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java + */ +message AppCompacted { + // The pid of the process being compacted. + optional int32 pid = 1; + + // The name of the process being compacted. + optional string process_name = 2; + + // The type of compaction. + enum Action { + UNKNOWN = 0; + SOME = 1; + FULL = 2; + } + optional Action action = 3; + + // Total RSS in kilobytes consumed by the process prior to compaction. + optional int64 before_rss_total_kilobytes = 4; + + // File RSS in kilobytes consumed by the process prior to compaction. + optional int64 before_rss_file_kilobytes = 5; + + // Anonymous RSS in kilobytes consumed by the process prior to compaction. + optional int64 before_rss_anon_kilobytes = 6; + + // Swap in kilobytes consumed by the process prior to compaction. + optional int64 before_swap_kilobytes = 7; + + // Total RSS in kilobytes consumed by the process after compaction. + optional int64 after_rss_total_kilobytes = 8; + + // File RSS in kilobytes consumed by the process after compaction. + optional int64 after_rss_file_kilobytes = 9; + + // Anonymous RSS in kilobytes consumed by the process after compaction. + optional int64 after_rss_anon_kilobytes = 10; + + // Swap in kilobytes consumed by the process after compaction. + optional int64 after_swap_kilobytes = 11; + + // The time taken to perform compaction in milliseconds. + optional int64 time_to_compact_millis = 12; + + // The last compaction action performed for this app. + optional Action last_action = 13; + + // The last time that compaction was attempted on this process in milliseconds + // since boot, not including sleep (see SystemClock.uptimeMillis()). + optional int64 last_compact_timestamp_ms_since_boot = 14; + + // The oom_score_adj at the time of compaction. + optional int32 oom_score_adj = 15; + + // The process state at the time of compaction. + optional android.app.ProcessStateEnum process_state = 16 [default = PROCESS_STATE_UNKNOWN]; +} diff --git a/cmds/statsd/src/storage/StorageManager.cpp b/cmds/statsd/src/storage/StorageManager.cpp index 2f19a02ecafe..90f641a34b85 100644 --- a/cmds/statsd/src/storage/StorageManager.cpp +++ b/cmds/statsd/src/storage/StorageManager.cpp @@ -188,7 +188,9 @@ bool StorageManager::hasConfigMetricsReport(const ConfigKey& key) { return false; } -void StorageManager::appendConfigMetricsReport(const ConfigKey& key, ProtoOutputStream* proto) { +void StorageManager::appendConfigMetricsReport(const ConfigKey& key, + ProtoOutputStream* proto, + bool erasa_data) { unique_ptr<DIR, decltype(&closedir)> dir(opendir(STATS_DATA_DIR), closedir); if (dir == NULL) { VLOG("Path %s does not exist", STATS_DATA_DIR); @@ -224,8 +226,9 @@ void StorageManager::appendConfigMetricsReport(const ConfigKey& key, ProtoOutput close(fd); } - // Remove file from disk after reading. - remove(file_name.c_str()); + if (erasa_data) { + remove(file_name.c_str()); + } } } } diff --git a/cmds/statsd/src/storage/StorageManager.h b/cmds/statsd/src/storage/StorageManager.h index 8fbc89e4f657..dcf3bb607380 100644 --- a/cmds/statsd/src/storage/StorageManager.h +++ b/cmds/statsd/src/storage/StorageManager.h @@ -68,10 +68,12 @@ public: static bool hasConfigMetricsReport(const ConfigKey& key); /** - * Appends ConfigMetricsReport found on disk to the specific proto and - * delete it. + * Appends the ConfigMetricsReport found on disk to the specifid proto + * and, if erase_data, deletes it from disk. */ - static void appendConfigMetricsReport(const ConfigKey& key, ProtoOutputStream* proto); + static void appendConfigMetricsReport(const ConfigKey& key, + ProtoOutputStream* proto, + bool erase_data); /** * Call to load the saved configs from disk. diff --git a/cmds/statsd/tests/StatsLogProcessor_test.cpp b/cmds/statsd/tests/StatsLogProcessor_test.cpp index 237f8b902015..d52be441f6b6 100644 --- a/cmds/statsd/tests/StatsLogProcessor_test.cpp +++ b/cmds/statsd/tests/StatsLogProcessor_test.cpp @@ -279,7 +279,10 @@ TEST(StatsLogProcessorTest, TestOnDumpReportEraseData) { // Dump report again. There should be no data since we erased it. processor->onDumpReport(cfgKey, 5, true, true /* DO erase data. */, ADB_DUMP, &bytes); output.ParseFromArray(bytes.data(), bytes.size()); - bool noData = (output.reports_size() == 0) || (output.reports(0).metrics_size() == 0); + // We don't care whether statsd has a report, as long as it has no count metrics in it. + bool noData = output.reports_size() == 0 + || output.reports(0).metrics_size() == 0 + || output.reports(0).metrics(0).count_metrics().data_size() == 0; EXPECT_TRUE(noData); } diff --git a/cmds/statsd/tests/StatsService_test.cpp b/cmds/statsd/tests/StatsService_test.cpp index a7b413666266..560fb9f02174 100644 --- a/cmds/statsd/tests/StatsService_test.cpp +++ b/cmds/statsd/tests/StatsService_test.cpp @@ -58,6 +58,45 @@ TEST(StatsServiceTest, TestAddConfig_invalid) { service.addConfigurationChecked(123, 12345, {serialized.begin(), serialized.end()})); } +TEST(StatsServiceTest, TestGetUidFromArgs) { + Vector<String8> args; + args.push(String8("-1")); + args.push(String8("0")); + args.push(String8("1")); + args.push(String8("9999999999999999999999999999999999")); + args.push(String8("a1")); + args.push(String8("")); + + int32_t uid; + + StatsService service(nullptr); + service.mEngBuild = true; + + // "-1" + EXPECT_FALSE(service.getUidFromArgs(args, 0, uid)); + + // "0" + EXPECT_TRUE(service.getUidFromArgs(args, 1, uid)); + EXPECT_EQ(0, uid); + + // "1" + EXPECT_TRUE(service.getUidFromArgs(args, 2, uid)); + EXPECT_EQ(1, uid); + + // "999999999999999999" + EXPECT_FALSE(service.getUidFromArgs(args, 3, uid)); + + // "a1" + EXPECT_FALSE(service.getUidFromArgs(args, 4, uid)); + + // "" + EXPECT_FALSE(service.getUidFromArgs(args, 5, uid)); + + // For a non-userdebug, uid "1" cannot be impersonated. + service.mEngBuild = false; + EXPECT_FALSE(service.getUidFromArgs(args, 2, uid)); +} + #else GTEST_LOG_(INFO) << "This test does nothing.\n"; #endif diff --git a/cmds/telecom/src/com/android/commands/telecom/Telecom.java b/cmds/telecom/src/com/android/commands/telecom/Telecom.java index a39f5e30bd51..4174ad7cd586 100644 --- a/cmds/telecom/src/com/android/commands/telecom/Telecom.java +++ b/cmds/telecom/src/com/android/commands/telecom/Telecom.java @@ -51,6 +51,8 @@ public final class Telecom extends BaseCommand { private static final String COMMAND_ADD_OR_REMOVE_CALL_COMPANION_APP = "add-or-remove-call-companion-app"; private static final String COMMAND_SET_TEST_AUTO_MODE_APP = "set-test-auto-mode-app"; + private static final String COMMAND_SET_PHONE_ACCOUNT_SUGGESTION_COMPONENT = + "set-phone-acct-suggestion-component"; private static final String COMMAND_UNREGISTER_PHONE_ACCOUNT = "unregister-phone-account"; private static final String COMMAND_SET_DEFAULT_DIALER = "set-default-dialer"; private static final String COMMAND_GET_DEFAULT_DIALER = "get-default-dialer"; @@ -64,36 +66,37 @@ public final class Telecom extends BaseCommand { @Override public void onShowUsage(PrintStream out) { - out.println( - "usage: telecom [subcommand] [options]\n" + - "usage: telecom set-phone-account-enabled <COMPONENT> <ID> <USER_SN>\n" + - "usage: telecom set-phone-account-disabled <COMPONENT> <ID> <USER_SN>\n" + - "usage: telecom register-phone-account <COMPONENT> <ID> <USER_SN> <LABEL>\n" + - "usage: telecom set-test-call-redirection-app <PACKAGE>\n" + - "usage: telecom set-test-call-screening-app <PACKAGE>\n" + - "usage: telecom set-test-auto-mode-app <PACKAGE>\n" + - "usage: telecom add-or-remove-call-companion-app <PACKAGE> <1/0>\n" + - "usage: telecom register-sim-phone-account <COMPONENT> <ID> <USER_SN> <LABEL> <ADDRESS>\n" + - "usage: telecom unregister-phone-account <COMPONENT> <ID> <USER_SN>\n" + - "usage: telecom set-default-dialer <PACKAGE>\n" + - "usage: telecom get-default-dialer\n" + - "usage: telecom get-system-dialer\n" + - "usage: telecom wait-on-handlers\n" + - "\n" + - "telecom set-phone-account-enabled: Enables the given phone account, if it has \n" + - " already been registered with Telecom.\n" + - "\n" + - "telecom set-phone-account-disabled: Disables the given phone account, if it \n" + - " has already been registered with telecom.\n" + - "\n" + - "telecom set-default-dialer: Sets the default dialer to the given component. \n" + - "\n" + - "telecom get-default-dialer: Displays the current default dialer. \n" + - "\n" + - "telecom get-system-dialer: Displays the current system dialer. \n" + - "\n" + - "telecom wait-on-handlers: Wait until all handlers finish their work. \n" - ); + out.println("usage: telecom [subcommand] [options]\n" + + "usage: telecom set-phone-account-enabled <COMPONENT> <ID> <USER_SN>\n" + + "usage: telecom set-phone-account-disabled <COMPONENT> <ID> <USER_SN>\n" + + "usage: telecom register-phone-account <COMPONENT> <ID> <USER_SN> <LABEL>\n" + + "usage: telecom set-test-call-redirection-app <PACKAGE>\n" + + "usage: telecom set-test-call-screening-app <PACKAGE>\n" + + "usage: telecom set-test-auto-mode-app <PACKAGE>\n" + + "usage: telecom set-phone-acct-suggestion-component <COMPONENT>\n" + + "usage: telecom add-or-remove-call-companion-app <PACKAGE> <1/0>\n" + + "usage: telecom register-sim-phone-account <COMPONENT> <ID> <USER_SN>" + + " <LABEL> <ADDRESS>\n" + + "usage: telecom unregister-phone-account <COMPONENT> <ID> <USER_SN>\n" + + "usage: telecom set-default-dialer <PACKAGE>\n" + + "usage: telecom get-default-dialer\n" + + "usage: telecom get-system-dialer\n" + + "usage: telecom wait-on-handlers\n" + + "\n" + + "telecom set-phone-account-enabled: Enables the given phone account, if it has \n" + + " already been registered with Telecom.\n" + + "\n" + + "telecom set-phone-account-disabled: Disables the given phone account, if it \n" + + " has already been registered with telecom.\n" + + "\n" + + "telecom set-default-dialer: Sets the default dialer to the given component. \n" + + "\n" + + "telecom get-default-dialer: Displays the current default dialer. \n" + + "\n" + + "telecom get-system-dialer: Displays the current system dialer. \n" + + "\n" + + "telecom wait-on-handlers: Wait until all handlers finish their work. \n" + ); } @Override @@ -134,6 +137,9 @@ public final class Telecom extends BaseCommand { case COMMAND_SET_TEST_AUTO_MODE_APP: runSetTestAutoModeApp(); break; + case COMMAND_SET_PHONE_ACCOUNT_SUGGESTION_COMPONENT: + runSetTestPhoneAcctSuggestionComponent(); + break; case COMMAND_REGISTER_SIM_PHONE_ACCOUNT: runRegisterSimPhoneAccount(); break; @@ -216,6 +222,11 @@ public final class Telecom extends BaseCommand { mTelecomService.setTestAutoModeApp(packageName); } + private void runSetTestPhoneAcctSuggestionComponent() throws RemoteException { + final String componentName = nextArg(); + mTelecomService.setTestPhoneAcctSuggestionComponent(componentName); + } + private void runUnregisterPhoneAccount() throws RemoteException { final PhoneAccountHandle handle = getPhoneAccountHandleFromArgs(); mTelecomService.unregisterPhoneAccount(handle); diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 1b45d172cb89..497e193c38ec 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -6152,7 +6152,7 @@ public final class ActivityThread extends ClientTransactionHandler { try { synchronized (getGetProviderLock(auth, userId)) { holder = ActivityManager.getService().getContentProvider( - getApplicationThread(), auth, userId, stable); + getApplicationThread(), c.getOpPackageName(), auth, userId, stable); } } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 6905cb5cea73..a2784237247c 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -41,8 +41,10 @@ import android.os.UserManager; import android.provider.Settings; import android.util.ArrayMap; +import com.android.internal.annotations.GuardedBy; import com.android.internal.app.IAppOpsActiveCallback; import com.android.internal.app.IAppOpsCallback; +import com.android.internal.app.IAppOpsNotedCallback; import com.android.internal.app.IAppOpsService; import com.android.internal.util.Preconditions; @@ -86,10 +88,20 @@ public class AppOpsManager { */ final Context mContext; + @UnsupportedAppUsage final IAppOpsService mService; - final ArrayMap<OnOpChangedListener, IAppOpsCallback> mModeWatchers = new ArrayMap<>(); - final ArrayMap<OnOpActiveChangedListener, IAppOpsActiveCallback> mActiveWatchers = + + @GuardedBy("mModeWatchers") + private final ArrayMap<OnOpChangedListener, IAppOpsCallback> mModeWatchers = + new ArrayMap<>(); + + @GuardedBy("mActiveWatchers") + private final ArrayMap<OnOpActiveChangedListener, IAppOpsActiveCallback> mActiveWatchers = + new ArrayMap<>(); + + @GuardedBy("mNotedWatchers") + private final ArrayMap<OnOpNotedListener, IAppOpsNotedCallback> mNotedWatchers = new ArrayMap<>(); static IBinder sToken; @@ -2471,6 +2483,23 @@ public class AppOpsManager { } /** + * Callback for notification of an op being noted. + * + * @hide + */ + public interface OnOpNotedListener { + /** + * Called when an op was noted. + * + * @param code The op code. + * @param uid The UID performing the operation. + * @param packageName The package performing the operation. + * @param result The result of the note. + */ + void onOpNoted(String code, int uid, String packageName, int result); + } + + /** * Callback for notification of changes to operation state. * This allows you to see the raw op codes instead of strings. * @hide @@ -2819,7 +2848,7 @@ public class AppOpsManager { */ public void stopWatchingMode(OnOpChangedListener callback) { synchronized (mModeWatchers) { - IAppOpsCallback cb = mModeWatchers.get(callback); + IAppOpsCallback cb = mModeWatchers.remove(callback); if (cb != null) { try { mService.stopWatchingMode(cb); @@ -2893,7 +2922,7 @@ public class AppOpsManager { @TestApi public void stopWatchingActive(@NonNull OnOpActiveChangedListener callback) { synchronized (mActiveWatchers) { - final IAppOpsActiveCallback cb = mActiveWatchers.get(callback); + final IAppOpsActiveCallback cb = mActiveWatchers.remove(callback); if (cb != null) { try { mService.stopWatchingActive(cb); @@ -2904,6 +2933,74 @@ public class AppOpsManager { } } + /** + * Start watching for noted app ops. An app op may be immediate or long running. + * Immediate ops are noted while long running ones are started and stopped. This + * method allows registering a listener to be notified when an app op is noted. If + * an op is being noted by any package you will get a callback. To change the + * watched ops for a registered callback you need to unregister and register it again. + * + * <p> If you don't hold the {@link android.Manifest.permission#WATCH_APPOPS} permission + * you can watch changes only for your UID. + * + * @param ops The ops to watch. + * @param callback Where to report changes. + * + * @see #startWatchingActive(int[], OnOpActiveChangedListener) + * @see #stopWatchingNoted(OnOpNotedListener) + * @see #noteOp(String, int, String) + * + * @hide + */ + @RequiresPermission(value=Manifest.permission.WATCH_APPOPS, conditional=true) + public void startWatchingNoted(@NonNull String[] ops, @NonNull OnOpNotedListener callback) { + IAppOpsNotedCallback cb; + synchronized (mNotedWatchers) { + cb = mNotedWatchers.get(callback); + if (cb != null) { + return; + } + cb = new IAppOpsNotedCallback.Stub() { + @Override + public void opNoted(int op, int uid, String packageName, int mode) { + callback.onOpNoted(sOpToString[op], uid, packageName, mode); + } + }; + mNotedWatchers.put(callback, cb); + } + try { + final int[] opCodes = new int[ops.length]; + for (int i = 0; i < opCodes.length; i++) { + opCodes[i] = strOpToOp(ops[i]); + } + mService.startWatchingNoted(opCodes, cb); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Stop watching for noted app ops. An app op may be immediate or long running. + * Unregistering a non-registered callback has no effect. + * + * @see #startWatchingNoted(String[], OnOpNotedListener) + * @see #noteOp(String, int, String) + * + * @hide + */ + public void stopWatchingNoted(@NonNull OnOpNotedListener callback) { + synchronized (mNotedWatchers) { + final IAppOpsNotedCallback cb = mNotedWatchers.get(callback); + if (cb != null) { + try { + mService.stopWatchingNoted(cb); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + } + private String buildSecurityExceptionMsg(int op, int uid, String packageName) { return packageName + " from uid " + uid + " not allowed to perform " + sOpNames[op]; } diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index 88fb025bf406..fb519b625012 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -124,7 +124,7 @@ interface IActivityManager { int ignoreWindowingMode); void moveTaskToFront(int task, int flags, in Bundle options); int getTaskForActivity(in IBinder token, in boolean onlyRoot); - ContentProviderHolder getContentProvider(in IApplicationThread caller, + ContentProviderHolder getContentProvider(in IApplicationThread caller, in String callingPackage, in String name, int userId, boolean stable); void publishContentProviders(in IApplicationThread caller, in List<ContentProviderHolder> providers); diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index 00567523e393..163be8efc8fd 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -65,6 +65,10 @@ interface INotificationManager boolean areNotificationsEnabled(String pkg); int getPackageImportance(String pkg); + void setAppOverlaysAllowed(String pkg, int uid, boolean allowed); + boolean areAppOverlaysAllowed(String pkg); + boolean areAppOverlaysAllowedForPackage(String pkg, int uid); + void createNotificationChannelGroups(String pkg, in ParceledListSlice channelGroupList); void createNotificationChannels(String pkg, in ParceledListSlice channelsList); void createNotificationChannelsForPackage(String pkg, int uid, in ParceledListSlice channelsList); diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index b9d590741263..e066f06542c3 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -3130,6 +3130,10 @@ public class Notification implements Parcelable return mAppOverlayIntent; } + /** + * Returns whether the platform is allowed (by the app developer) to generate contextual actions + * for this notification. + */ public boolean getAllowSystemGeneratedContextualActions() { return mAllowSystemGeneratedContextualActions; } diff --git a/core/java/android/app/NotificationChannelGroup.java b/core/java/android/app/NotificationChannelGroup.java index 2322a42c93d5..34cd9f029746 100644 --- a/core/java/android/app/NotificationChannelGroup.java +++ b/core/java/android/app/NotificationChannelGroup.java @@ -50,20 +50,12 @@ public final class NotificationChannelGroup implements Parcelable { private static final String ATT_DESC = "desc"; private static final String ATT_ID = "id"; private static final String ATT_BLOCKED = "blocked"; - private static final String ATT_ALLOW_APP_OVERLAY = "app_overlay"; private static final String ATT_USER_LOCKED = "locked"; - private static final boolean DEFAULT_ALLOW_APP_OVERLAY = true; - /** * @hide */ public static final int USER_LOCKED_BLOCKED_STATE = 0x00000001; - /** - * @hide - */ - @TestApi - public static final int USER_LOCKED_ALLOW_APP_OVERLAY = 0x00000002; /** * @see #getId() @@ -74,7 +66,6 @@ public final class NotificationChannelGroup implements Parcelable { private String mDescription; private boolean mBlocked; private List<NotificationChannel> mChannels = new ArrayList<>(); - private boolean mAllowAppOverlay = DEFAULT_ALLOW_APP_OVERLAY; // Bitwise representation of fields that have been changed by the user private int mUserLockedFields; @@ -110,7 +101,6 @@ public final class NotificationChannelGroup implements Parcelable { } in.readParcelableList(mChannels, NotificationChannel.class.getClassLoader()); mBlocked = in.readBoolean(); - mAllowAppOverlay = in.readBoolean(); mUserLockedFields = in.readInt(); } @@ -138,7 +128,6 @@ public final class NotificationChannelGroup implements Parcelable { } dest.writeParcelableList(mChannels, flags); dest.writeBoolean(mBlocked); - dest.writeBoolean(mAllowAppOverlay); dest.writeInt(mUserLockedFields); } @@ -181,15 +170,6 @@ public final class NotificationChannelGroup implements Parcelable { } /** - * Returns whether notifications posted to this channel group can display outside of the - * notification shade, in a floating window on top of other apps. These may additionally be - * blocked at the notification channel level, see {@link NotificationChannel#canOverlayApps()}. - */ - public boolean canOverlayApps() { - return mAllowAppOverlay; - } - - /** * Sets the user visible description of this group. * * <p>The recommended maximum length is 300 characters; the value may be truncated if it is too @@ -200,21 +180,6 @@ public final class NotificationChannelGroup implements Parcelable { } /** - * Sets whether notifications posted to this channel group can appear outside of the - * notification shade, floating over other apps' content. - * - * <p>This value will be ignored for notifications that are posted to channels that do not - * allow app overlays ({@link NotificationChannel#canOverlayApps()}. - * - * <p>Only modifiable before the channel is submitted to - * {@link NotificationManager#createNotificationChannelGroup(NotificationChannelGroup)}.</p> - * @see Notification#getAppOverlayIntent() - */ - public void setAllowAppOverlay(boolean allowAppOverlay) { - mAllowAppOverlay = allowAppOverlay; - } - - /** * @hide */ @TestApi @@ -266,7 +231,6 @@ public final class NotificationChannelGroup implements Parcelable { // Name, id, and importance are set in the constructor. setDescription(parser.getAttributeValue(null, ATT_DESC)); setBlocked(safeBool(parser, ATT_BLOCKED, false)); - setAllowAppOverlay(safeBool(parser, ATT_ALLOW_APP_OVERLAY, DEFAULT_ALLOW_APP_OVERLAY)); } private static boolean safeBool(XmlPullParser parser, String att, boolean defValue) { @@ -289,9 +253,6 @@ public final class NotificationChannelGroup implements Parcelable { out.attribute(null, ATT_DESC, getDescription().toString()); } out.attribute(null, ATT_BLOCKED, Boolean.toString(isBlocked())); - if (canOverlayApps() != DEFAULT_ALLOW_APP_OVERLAY) { - out.attribute(null, ATT_ALLOW_APP_OVERLAY, Boolean.toString(canOverlayApps())); - } out.attribute(null, ATT_USER_LOCKED, Integer.toString(mUserLockedFields)); out.endTag(null, TAG_GROUP); @@ -307,7 +268,6 @@ public final class NotificationChannelGroup implements Parcelable { record.put(ATT_NAME, getName()); record.put(ATT_DESC, getDescription()); record.put(ATT_BLOCKED, isBlocked()); - record.put(ATT_ALLOW_APP_OVERLAY, canOverlayApps()); record.put(ATT_USER_LOCKED, mUserLockedFields); return record; } @@ -336,7 +296,6 @@ public final class NotificationChannelGroup implements Parcelable { if (o == null || getClass() != o.getClass()) return false; NotificationChannelGroup that = (NotificationChannelGroup) o; return isBlocked() == that.isBlocked() && - mAllowAppOverlay == that.mAllowAppOverlay && mUserLockedFields == that.mUserLockedFields && Objects.equals(getId(), that.getId()) && Objects.equals(getName(), that.getName()) && @@ -347,7 +306,7 @@ public final class NotificationChannelGroup implements Parcelable { @Override public int hashCode() { return Objects.hash(getId(), getName(), getDescription(), isBlocked(), getChannels(), - mAllowAppOverlay, mUserLockedFields); + mUserLockedFields); } @Override @@ -356,7 +315,6 @@ public final class NotificationChannelGroup implements Parcelable { cloned.setDescription(getDescription()); cloned.setBlocked(isBlocked()); cloned.setChannels(getChannels()); - cloned.setAllowAppOverlay(canOverlayApps()); cloned.lockFields(mUserLockedFields); return cloned; } @@ -369,7 +327,6 @@ public final class NotificationChannelGroup implements Parcelable { + ", mDescription=" + (!TextUtils.isEmpty(mDescription) ? "hasDescription " : "") + ", mBlocked=" + mBlocked + ", mChannels=" + mChannels - + ", mAllowAppOverlay=" + mAllowAppOverlay + ", mUserLockedFields=" + mUserLockedFields + '}'; } @@ -385,7 +342,6 @@ public final class NotificationChannelGroup implements Parcelable { for (NotificationChannel channel : mChannels) { channel.writeToProto(proto, NotificationChannelGroupProto.CHANNELS); } - proto.write(NotificationChannelGroupProto.ALLOW_APP_OVERLAY, mAllowAppOverlay); proto.end(token); } } diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index 306c366a86e4..a782ced7f9f8 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -1074,6 +1074,25 @@ public class NotificationManager { } } + + /** + * Sets whether notifications posted by this app can appear outside of the + * notification shade, floating over other apps' content. + * + * <p>This value will be ignored for notifications that are posted to channels that do not + * allow app overlays ({@link NotificationChannel#canOverlayApps()}. + * + * @see Notification#getAppOverlayIntent() + */ + public boolean areAppOverlaysAllowed() { + INotificationManager service = getService(); + try { + return service.areAppOverlaysAllowed(mContext.getPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * Checks the ability to modify notification do not disturb policy for the calling package. * @@ -1838,12 +1857,17 @@ public class NotificationManager { * Recover a list of active notifications: ones that have been posted by the calling app that * have not yet been dismissed by the user or {@link #cancel(String, int)}ed by the app. * - * Each notification is embedded in a {@link StatusBarNotification} object, including the + * <p><Each notification is embedded in a {@link StatusBarNotification} object, including the * original <code>tag</code> and <code>id</code> supplied to * {@link #notify(String, int, Notification) notify()} * (via {@link StatusBarNotification#getTag() getTag()} and * {@link StatusBarNotification#getId() getId()}) as well as a copy of the original * {@link Notification} object (via {@link StatusBarNotification#getNotification()}). + * </p> + * <p>From {@link Build.VERSION_CODES#Q}, will also return notifications you've posted as an + * app's notification delegate via + * {@link NotificationManager#notifyAsPackage(String, String, int, Notification)}. + * </p> * * @return An array of {@link StatusBarNotification}. */ diff --git a/core/java/android/app/backup/BackupManager.java b/core/java/android/app/backup/BackupManager.java index c6086f10bd00..a6f6d06ae71a 100644 --- a/core/java/android/app/backup/BackupManager.java +++ b/core/java/android/app/backup/BackupManager.java @@ -335,7 +335,8 @@ public class BackupManager { if (sService != null) { try { // All packages, current transport - IRestoreSession binder = sService.beginRestoreSession(null, null); + IRestoreSession binder = + sService.beginRestoreSessionForUser(mContext.getUserId(), null, null); if (binder != null) { session = new RestoreSession(mContext, binder); } @@ -465,7 +466,7 @@ public class BackupManager { checkServiceBinder(); if (sService != null) { try { - return sService.getCurrentTransportComponent(); + return sService.getCurrentTransportComponentForUser(mContext.getUserId()); } catch (RemoteException e) { Log.e(TAG, "getCurrentTransportComponent() couldn't connect"); } @@ -530,7 +531,8 @@ public class BackupManager { checkServiceBinder(); if (sService != null) { try { - sService.updateTransportAttributes( + sService.updateTransportAttributesForUser( + mContext.getUserId(), transportComponent, name, configurationIntent, @@ -590,7 +592,8 @@ public class BackupManager { try { SelectTransportListenerWrapper wrapper = listener == null ? null : new SelectTransportListenerWrapper(mContext, listener); - sService.selectBackupTransportAsync(transport, wrapper); + sService.selectBackupTransportAsyncForUser( + mContext.getUserId(), transport, wrapper); } catch (RemoteException e) { Log.e(TAG, "selectBackupTransportAsync() couldn't connect"); } @@ -637,7 +640,7 @@ public class BackupManager { checkServiceBinder(); if (sService != null) { try { - return sService.getAvailableRestoreToken(packageName); + return sService.getAvailableRestoreTokenForUser(mContext.getUserId(), packageName); } catch (RemoteException e) { Log.e(TAG, "getAvailableRestoreToken() couldn't connect"); } @@ -659,7 +662,7 @@ public class BackupManager { checkServiceBinder(); if (sService != null) { try { - return sService.isAppEligibleForBackup(packageName); + return sService.isAppEligibleForBackupForUser(mContext.getUserId(), packageName); } catch (RemoteException e) { Log.e(TAG, "isAppEligibleForBackup(pkg) couldn't connect"); } @@ -760,7 +763,7 @@ public class BackupManager { public Intent getConfigurationIntent(String transportName) { if (sService != null) { try { - return sService.getConfigurationIntent(transportName); + return sService.getConfigurationIntentForUser(mContext.getUserId(), transportName); } catch (RemoteException e) { Log.e(TAG, "getConfigurationIntent() couldn't connect"); } @@ -781,7 +784,7 @@ public class BackupManager { public String getDestinationString(String transportName) { if (sService != null) { try { - return sService.getDestinationString(transportName); + return sService.getDestinationStringForUser(mContext.getUserId(), transportName); } catch (RemoteException e) { Log.e(TAG, "getDestinationString() couldn't connect"); } @@ -802,7 +805,7 @@ public class BackupManager { public Intent getDataManagementIntent(String transportName) { if (sService != null) { try { - return sService.getDataManagementIntent(transportName); + return sService.getDataManagementIntentForUser(mContext.getUserId(), transportName); } catch (RemoteException e) { Log.e(TAG, "getDataManagementIntent() couldn't connect"); } @@ -825,7 +828,7 @@ public class BackupManager { public String getDataManagementLabel(String transportName) { if (sService != null) { try { - return sService.getDataManagementLabel(transportName); + return sService.getDataManagementLabelForUser(mContext.getUserId(), transportName); } catch (RemoteException e) { Log.e(TAG, "getDataManagementLabel() couldn't connect"); } diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl index f1e6b06e8bac..19de19a820b1 100644 --- a/core/java/android/app/backup/IBackupManager.aidl +++ b/core/java/android/app/backup/IBackupManager.aidl @@ -43,6 +43,15 @@ interface IBackupManager { * Any application can invoke this method for its own package, but * only callers who hold the android.permission.BACKUP permission * may invoke it for arbitrary packages. + * If {@code userId} is different from the calling user id, then the caller must hold the + * android.permission.INTERACT_ACROSS_USERS_FULL permission. + * + * @param userId User id for which the caller has made changes to its data. + */ + void dataChangedForUser(int userId, String packageName); + + /** + * {@link android.app.backup.IBackupManager.dataChangedForUser} for the calling user id. */ void dataChanged(String packageName); @@ -53,6 +62,15 @@ interface IBackupManager { * Any application can invoke this method for its own package, but * only callers who hold the android.permission.BACKUP permission * may invoke it for arbitrary packages. + * If {@code userId} is different from the calling user id, then the caller must hold the + * android.permission.INTERACT_ACROSS_USERS_FULL permission. + * + * @param userId User id for which backup data should be erased. + */ + void clearBackupDataForUser(int userId, String transportName, String packageName); + + /** + * {@link android.app.backup.IBackupManager.clearBackupDataForUser} for the calling user id. */ void clearBackupData(String transportName, String packageName); @@ -62,24 +80,59 @@ interface IBackupManager { * operations. * * <p>Callers must hold the android.permission.BACKUP permission to use this method. + * If {@code userId} is different from the calling user id, then the caller must hold the + * android.permission.INTERACT_ACROSS_USERS_FULL permission. + * + * @param userId User id for which the given transports should be initialized. */ - void initializeTransports(in String[] transportNames, IBackupObserver observer); + void initializeTransportsForUser(int userId, in String[] transportNames, + IBackupObserver observer); /** * Notifies the Backup Manager Service that an agent has become available. This * method is only invoked by the Activity Manager. + * + * If {@code userId} is different from the calling user id, then the caller must hold the + * android.permission.INTERACT_ACROSS_USERS_FULL permission. + * + * @param userId User id for which an agent has become available. + */ + void agentConnectedForUser(int userId, String packageName, IBinder agent); + + /** + * {@link android.app.backup.IBackupManager.agentConnected} for the calling user id. */ void agentConnected(String packageName, IBinder agent); /** * Notify the Backup Manager Service that an agent has unexpectedly gone away. * This method is only invoked by the Activity Manager. + * + * If {@code userId} is different from the calling user id, then the caller must hold the + * android.permission.INTERACT_ACROSS_USERS_FULL permission. + * + * @param userId User id for which an agent has unexpectedly gone away. + */ + void agentDisconnectedForUser(int userId, String packageName); + + /** + * {@link android.app.backup.IBackupManager.agentDisconnected} for the calling user id. */ void agentDisconnected(String packageName); /** * Notify the Backup Manager Service that an application being installed will * need a data-restore pass. This method is only invoked by the Package Manager. + * + * If {@code userId} is different from the calling user id, then the caller must hold the + * android.permission.INTERACT_ACROSS_USERS_FULL permission. + * + * @param userId User id for which the application will need a data-restore pass. + */ + void restoreAtInstallForUser(int userId, String packageName, int token); + + /** + * {@link android.app.backup.IBackupManager.restoreAtInstallForUser} for the calling user id. */ void restoreAtInstall(String packageName, int token); @@ -112,10 +165,18 @@ interface IBackupManager { * is made generally available for launch. * * <p>Callers must hold the android.permission.BACKUP permission to use this method. + * If {@code userId} is different from the calling user id, then the caller must hold the + * android.permission.INTERACT_ACROSS_USERS_FULL permission. * + * @param userId User id for which automatic restore should be enabled/disabled. * @param doAutoRestore When true, enables the automatic app-data restore facility. When * false, this facility will be disabled. */ + void setAutoRestoreForUser(int userId, boolean doAutoRestore); + + /** + * {@link android.app.backup.IBackupManager.setAutoRestoreForUser} for the calling user id. + */ void setAutoRestore(boolean doAutoRestore); /** @@ -220,9 +281,13 @@ interface IBackupManager { * Perform a full-dataset backup of the given applications via the currently active * transport. * + * If {@code userId} is different from the calling user id, then the caller must hold the + * android.permission.INTERACT_ACROSS_USERS_FULL permission. + * + * @param userId User id for which the full-dataset backup should be performed. * @param packageNames The package names of the apps whose data are to be backed up. */ - void fullTransportBackup(in String[] packageNames); + void fullTransportBackupForUser(int userId, in String[] packageNames); /** * Restore device content from the data stream passed through the given socket. The @@ -250,6 +315,18 @@ interface IBackupManager { * backup dataset being used for restore. * * <p>Callers must hold the android.permission.BACKUP permission to use this method. + * If {@code userId} is different from the calling user id, then the caller must hold the + * android.permission.INTERACT_ACROSS_USERS_FULL permission. + * + * @param userId User id for which the requested backup/restore operation can proceed. + */ + void acknowledgeFullBackupOrRestoreForUser(int userId, int token, boolean allow, + in String curPassword, in String encryptionPassword, + IFullBackupRestoreObserver observer); + + /** + * {@link android.app.backup.IBackupManager.acknowledgeFullBackupOrRestoreForUser} for the + * calling user id. */ void acknowledgeFullBackupOrRestore(int token, boolean allow, in String curPassword, in String encryptionPassword, @@ -260,7 +337,10 @@ interface IBackupManager { * specified transport has not been bound at least once (for registration), this call will be * ignored. Only the host process of the transport can change its description, otherwise a * {@link SecurityException} will be thrown. + * If {@code userId} is different from the calling user id, then the caller must hold the + * android.permission.INTERACT_ACROSS_USERS_FULL permission. * + * @param userId User id for which the attributes of the transport should be updated. * @param transportComponent The identity of the transport being described. * @param name A {@link String} with the new name for the transport. This is NOT for * identification. MUST NOT be {@code null}. @@ -279,13 +359,23 @@ interface IBackupManager { * @throws SecurityException If the UID of the calling process differs from the package UID of * {@code transportComponent} or if the caller does NOT have BACKUP permission. */ - void updateTransportAttributes(in ComponentName transportComponent, in String name, + void updateTransportAttributesForUser(int userId, in ComponentName transportComponent, + in String name, in Intent configurationIntent, in String currentDestinationString, in Intent dataManagementIntent, in String dataManagementLabel); /** * Identify the currently selected transport. Callers must hold the * android.permission.BACKUP permission to use this method. + * If {@code userId} is different from the calling user id, then the caller must hold the + * android.permission.INTERACT_ACROSS_USERS_FULL permission. + * + * @param userId User id for which the currently selected transport should be identified. + */ + String getCurrentTransportForUser(int userId); + + /** + * {@link android.app.backup.IBackupManager.getCurrentTransportForUser} for the calling user id. */ String getCurrentTransport(); @@ -293,16 +383,35 @@ interface IBackupManager { * Returns the {@link ComponentName} of the host service of the selected transport or {@code * null} if no transport selected or if the transport selected is not registered. Callers must * hold the android.permission.BACKUP permission to use this method. + * If {@code userId} is different from the calling user id, then the caller must hold the + * android.permission.INTERACT_ACROSS_USERS_FULL permission. + * + * @param userId User id for which the currently selected transport should be identified. */ - ComponentName getCurrentTransportComponent(); + ComponentName getCurrentTransportComponentForUser(int userId); /** * Request a list of all available backup transports' names. Callers must * hold the android.permission.BACKUP permission to use this method. + * If {@code userId} is different from the calling user id, then the caller must hold the + * android.permission.INTERACT_ACROSS_USERS_FULL permission. + * + * @param userId User id for which all available backup transports' names should be listed. + */ + String[] listAllTransportsForUser(int userId); + + /** + * {@link android.app.backup.IBackupManager.listAllTransportsForUser} for the calling user id. */ String[] listAllTransports(); - ComponentName[] listAllTransportComponents(); + /** + * If {@code userId} is different from the calling user id, then the caller must hold the + * android.permission.INTERACT_ACROSS_USERS_FULL permission. + * + * @param userId User id for which all available backup transports should be listed. + */ + ComponentName[] listAllTransportComponentsForUser(int userId); /** * Retrieve the list of whitelisted transport components. Callers do </i>not</i> need @@ -315,13 +424,22 @@ interface IBackupManager { /** * Specify the current backup transport. Callers must hold the * android.permission.BACKUP permission to use this method. + * If {@code userId} is different from the calling user id, then the caller must hold the + * android.permission.INTERACT_ACROSS_USERS_FULL permission. * + * @param userId User id for which the transport should be selected. * @param transport The name of the transport to select. This should be one * of {@link BackupManager.TRANSPORT_GOOGLE} or {@link BackupManager.TRANSPORT_ADB}. * @return The name of the previously selected transport. If the given transport * name is not one of the currently available transports, no change is made to * the current transport setting and the method returns null. */ + String selectBackupTransportForUser(int userId, String transport); + + /** + * {@link android.app.backup.IBackupManager.selectBackupTransportForUser} for the calling user + * id. + */ String selectBackupTransport(String transport); /** @@ -330,43 +448,85 @@ interface IBackupManager { * which is in a separate process. * * <p>Callers must hold the android.permission.BACKUP permission to use this method. + * If {@code userId} is different from the calling user id, then the caller must hold the + * android.permission.INTERACT_ACROSS_USERS_FULL permission. * + * @param userId User id for which the transport should be selected. * @param transport ComponentName of the service hosting the transport. This is different from * the transport's name that is returned by {@link BackupTransport#name()}. * @param listener A listener object to get a callback on the transport being selected. */ - void selectBackupTransportAsync(in ComponentName transport, ISelectBackupTransportCallback listener); + void selectBackupTransportAsyncForUser(int userId, in ComponentName transport, + ISelectBackupTransportCallback listener); /** * Get the configuration Intent, if any, from the given transport. Callers must * hold the android.permission.BACKUP permission in order to use this method. + * If {@code userId} is different from the calling user id, then the caller must hold the + * android.permission.INTERACT_ACROSS_USERS_FULL permission. * + * @param userId User id for which the configuration Intent should be reported. * @param transport The name of the transport to query. * @return An Intent to use with Activity#startActivity() to bring up the configuration * UI supplied by the transport. If the transport has no configuration UI, it should * return {@code null} here. */ + Intent getConfigurationIntentForUser(int userId, String transport); + + /** + * {@link android.app.backup.IBackupManager.getConfigurationIntentForUser} for the calling user + * id. + */ Intent getConfigurationIntent(String transport); /** * Get the destination string supplied by the given transport. Callers must * hold the android.permission.BACKUP permission in order to use this method. + * If {@code userId} is different from the calling user id, then the caller must hold the + * android.permission.INTERACT_ACROSS_USERS_FULL permission. * + * @param userId User id for which the transport destination string should be reported. * @param transport The name of the transport to query. * @return A string describing the current backup destination. This string is used * verbatim by the Settings UI as the summary text of the "configure..." item. */ + String getDestinationStringForUser(int userId, String transport); + + /** + * {@link android.app.backup.IBackupManager.getDestinationStringForUser} for the calling user + * id. + */ String getDestinationString(String transport); /** * Get the manage-data UI intent, if any, from the given transport. Callers must * hold the android.permission.BACKUP permission in order to use this method. + * If {@code userId} is different from the calling user id, then the caller must hold the + * android.permission.INTERACT_ACROSS_USERS_FULL permission. + * + * @param userId User id for which the manage-data UI intent should be reported. + */ + Intent getDataManagementIntentForUser(int userId, String transport); + + /** + * {@link android.app.backup.IBackupManager.getDataManagementIntentForUser} for the calling user + * id. */ Intent getDataManagementIntent(String transport); /** * Get the manage-data menu label, if any, from the given transport. Callers must * hold the android.permission.BACKUP permission in order to use this method. + * If {@code userId} is different from the calling user id, then the caller must hold the + * android.permission.INTERACT_ACROSS_USERS_FULL permission. + * + * @param userId User id for which the manage-data menu label should be reported. + */ + String getDataManagementLabelForUser(int userId, String transport); + + /** + * {@link android.app.backup.IBackupManager.getDataManagementLabelForUser} for the calling user + * id. */ String getDataManagementLabel(String transport); @@ -381,7 +541,10 @@ interface IBackupManager { * package. In that case, the restore session returned is suitable for supporting * the BackupManager.requestRestore() functionality via RestoreSession.restorePackage() * without requiring the app to hold any special permission. + * If {@code userId} is different from the calling user id, then the caller must hold the + * android.permission.INTERACT_ACROSS_USERS_FULL permission. * + * @param userId User id for which a restore session should be begun. * @param packageName The name of the single package for which a restore will * be requested. May be null, in which case all packages in the restore * set can be restored. @@ -389,7 +552,7 @@ interface IBackupManager { * May be null, in which case the current active transport is used. * @return An interface to the restore session, or null on error. */ - IRestoreSession beginRestoreSession(String packageName, String transportID); + IRestoreSession beginRestoreSessionForUser(int userId, String packageName, String transportID); /** * Notify the backup manager that a BackupAgent has completed the operation @@ -427,13 +590,16 @@ interface IBackupManager { * restored from if we were to install it right now. * * <p>Callers must hold the android.permission.BACKUP permission to use this method. + * If {@code userId} is different from the calling user id, then the caller must hold the + * android.permission.INTERACT_ACROSS_USERS_FULL permission. * + * @param userId User id for which this operation should be performed. * @param packageName The name of the package whose most-suitable dataset we * wish to look up * @return The dataset token from which a restore should be attempted, or zero if * no suitable data is available. */ - long getAvailableRestoreToken(String packageName); + long getAvailableRestoreTokenForUser(int userId, String packageName); /** * Ask the framework whether this app is eligible for backup. @@ -442,21 +608,27 @@ interface IBackupManager { * {@link #filterAppsEligibleForBackup(String[])} to save resources. * * <p>Callers must hold the android.permission.BACKUP permission to use this method. + * If {@code userId} is different from the calling user id, then the caller must hold the + * android.permission.INTERACT_ACROSS_USERS_FULL permission. * + * @param userId User id for which this operation should be performed. * @param packageName The name of the package. * @return Whether this app is eligible for backup. */ - boolean isAppEligibleForBackup(String packageName); + boolean isAppEligibleForBackupForUser(int userId, String packageName); /** * Filter the packages that are eligible for backup and return the result. * * <p>Callers must hold the android.permission.BACKUP permission to use this method. + * If {@code userId} is different from the calling user id, then the caller must hold the + * android.permission.INTERACT_ACROSS_USERS_FULL permission. * + * @param userId User id for which the filter should be performed. * @param packages The list of packages to filter. * @return The packages eligible for backup. */ - String[] filterAppsEligibleForBackup(in String[] packages); + String[] filterAppsEligibleForBackupForUser(int userId, in String[] packages); /** * Request an immediate backup, providing an observer to which results of the backup operation diff --git a/core/java/android/bluetooth/BluetoothManager.java b/core/java/android/bluetooth/BluetoothManager.java index e08d405324ea..adedff3e9386 100644 --- a/core/java/android/bluetooth/BluetoothManager.java +++ b/core/java/android/bluetooth/BluetoothManager.java @@ -67,7 +67,9 @@ public final class BluetoothManager { } // Legacy api - getDefaultAdapter does not take in the context mAdapter = BluetoothAdapter.getDefaultAdapter(); - mAdapter.setContext(context); + if (mAdapter != null) { + mAdapter.setContext(context); + } } /** diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 2f0618cf64b3..d5c6c63243f6 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -4222,6 +4222,11 @@ public class Intent implements Parcelable, Cloneable { @SdkConstant(SdkConstantType.INTENT_CATEGORY) public static final String CATEGORY_HOME_MAIN = "android.intent.category.HOME_MAIN"; /** + * The home activity shown on secondary displays that support showing home activities. + */ + @SdkConstant(SdkConstantType.INTENT_CATEGORY) + public static final String CATEGORY_SECONDARY_HOME = "android.intent.category.SECONDARY_HOME"; + /** * This is the setup wizard activity, that is the first activity that is displayed * when the user sets up the device for the first time. * @hide diff --git a/core/java/android/hardware/display/BrightnessConfiguration.java b/core/java/android/hardware/display/BrightnessConfiguration.java index 7e52ca331f9f..be054297c769 100644 --- a/core/java/android/hardware/display/BrightnessConfiguration.java +++ b/core/java/android/hardware/display/BrightnessConfiguration.java @@ -19,26 +19,54 @@ package android.hardware.display; import android.annotation.Nullable; import android.annotation.SystemApi; import android.annotation.TestApi; +import android.content.pm.ApplicationInfo; import android.os.Parcel; import android.os.Parcelable; import android.util.Pair; import com.android.internal.util.Preconditions; +import com.android.internal.util.XmlUtils; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.IOException; +import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; import java.util.Objects; /** @hide */ @SystemApi @TestApi public final class BrightnessConfiguration implements Parcelable { + private static final String TAG_BRIGHTNESS_CURVE = "brightness-curve"; + private static final String TAG_BRIGHTNESS_POINT = "brightness-point"; + private static final String TAG_BRIGHTNESS_CORRECTIONS = "brightness-corrections"; + private static final String TAG_BRIGHTNESS_CORRECTION = "brightness-correction"; + private static final String ATTR_LUX = "lux"; + private static final String ATTR_NITS = "nits"; + private static final String ATTR_DESCRIPTION = "description"; + private static final String ATTR_PACKAGE_NAME = "package-name"; + private static final String ATTR_CATEGORY = "category"; + private final float[] mLux; private final float[] mNits; + private final Map<String, BrightnessCorrection> mCorrectionsByPackageName; + private final Map<Integer, BrightnessCorrection> mCorrectionsByCategory; private final String mDescription; - private BrightnessConfiguration(float[] lux, float[] nits, String description) { + private BrightnessConfiguration(float[] lux, float[] nits, + Map<String, BrightnessCorrection> correctionsByPackageName, + Map<Integer, BrightnessCorrection> correctionsByCategory, String description) { mLux = lux; mNits = nits; + mCorrectionsByPackageName = correctionsByPackageName; + mCorrectionsByCategory = correctionsByCategory; mDescription = description; } @@ -56,6 +84,38 @@ public final class BrightnessConfiguration implements Parcelable { } /** + * Returns a brightness correction by app, or null. + * + * @param packageName + * The app's package name. + * + * @return The matching brightness correction, or null. + * + * @hide + */ + @SystemApi + @Nullable + public BrightnessCorrection getCorrectionByPackageName(String packageName) { + return mCorrectionsByPackageName.get(packageName); + } + + /** + * Returns a brightness correction by app category, or null. + * + * @param category + * The app category. + * + * @return The matching brightness correction, or null. + * + * @hide + */ + @SystemApi + @Nullable + public BrightnessCorrection getCorrectionByCategory(int category) { + return mCorrectionsByCategory.get(category); + } + + /** * Returns description string. * @hide */ @@ -67,6 +127,20 @@ public final class BrightnessConfiguration implements Parcelable { public void writeToParcel(Parcel dest, int flags) { dest.writeFloatArray(mLux); dest.writeFloatArray(mNits); + dest.writeInt(mCorrectionsByPackageName.size()); + for (Entry<String, BrightnessCorrection> entry : mCorrectionsByPackageName.entrySet()) { + final String packageName = entry.getKey(); + final BrightnessCorrection correction = entry.getValue(); + dest.writeString(packageName); + correction.writeToParcel(dest, flags); + } + dest.writeInt(mCorrectionsByCategory.size()); + for (Entry<Integer, BrightnessCorrection> entry : mCorrectionsByCategory.entrySet()) { + final int category = entry.getKey(); + final BrightnessCorrection correction = entry.getValue(); + dest.writeInt(category); + correction.writeToParcel(dest, flags); + } dest.writeString(mDescription); } @@ -85,7 +159,14 @@ public final class BrightnessConfiguration implements Parcelable { } sb.append("(").append(mLux[i]).append(", ").append(mNits[i]).append(")"); } - sb.append("], '"); + sb.append("], {"); + for (Entry<String, BrightnessCorrection> entry : mCorrectionsByPackageName.entrySet()) { + sb.append("'" + entry.getKey() + "': " + entry.getValue() + ", "); + } + for (Entry<Integer, BrightnessCorrection> entry : mCorrectionsByCategory.entrySet()) { + sb.append(entry.getKey() + ": " + entry.getValue() + ", "); + } + sb.append("}, '"); if (mDescription != null) { sb.append(mDescription); } @@ -98,6 +179,8 @@ public final class BrightnessConfiguration implements Parcelable { int result = 1; result = result * 31 + Arrays.hashCode(mLux); result = result * 31 + Arrays.hashCode(mNits); + result = result * 31 + mCorrectionsByPackageName.hashCode(); + result = result * 31 + mCorrectionsByCategory.hashCode(); if (mDescription != null) { result = result * 31 + mDescription.hashCode(); } @@ -114,6 +197,8 @@ public final class BrightnessConfiguration implements Parcelable { } final BrightnessConfiguration other = (BrightnessConfiguration) o; return Arrays.equals(mLux, other.mLux) && Arrays.equals(mNits, other.mNits) + && mCorrectionsByPackageName.equals(other.mCorrectionsByPackageName) + && mCorrectionsByCategory.equals(other.mCorrectionsByCategory) && Objects.equals(mDescription, other.mDescription); } @@ -123,7 +208,25 @@ public final class BrightnessConfiguration implements Parcelable { float[] lux = in.createFloatArray(); float[] nits = in.createFloatArray(); Builder builder = new Builder(lux, nits); - builder.setDescription(in.readString()); + + int n = in.readInt(); + for (int i = 0; i < n; i++) { + final String packageName = in.readString(); + final BrightnessCorrection correction = + BrightnessCorrection.CREATOR.createFromParcel(in); + builder.addCorrectionByPackageName(packageName, correction); + } + + n = in.readInt(); + for (int i = 0; i < n; i++) { + final int category = in.readInt(); + final BrightnessCorrection correction = + BrightnessCorrection.CREATOR.createFromParcel(in); + builder.addCorrectionByCategory(category, correction); + } + + final String description = in.readString(); + builder.setDescription(description); return builder.build(); } @@ -133,11 +236,146 @@ public final class BrightnessConfiguration implements Parcelable { }; /** + * Writes the configuration to an XML serializer. + * + * @param serializer + * The XML serializer. + * + * @hide + */ + public void saveToXml(XmlSerializer serializer) throws IOException { + serializer.startTag(null, TAG_BRIGHTNESS_CURVE); + if (mDescription != null) { + serializer.attribute(null, ATTR_DESCRIPTION, mDescription); + } + for (int i = 0; i < mLux.length; i++) { + serializer.startTag(null, TAG_BRIGHTNESS_POINT); + serializer.attribute(null, ATTR_LUX, Float.toString(mLux[i])); + serializer.attribute(null, ATTR_NITS, Float.toString(mNits[i])); + serializer.endTag(null, TAG_BRIGHTNESS_POINT); + } + serializer.endTag(null, TAG_BRIGHTNESS_CURVE); + serializer.startTag(null, TAG_BRIGHTNESS_CORRECTIONS); + for (Map.Entry<String, BrightnessCorrection> entry : + mCorrectionsByPackageName.entrySet()) { + final String packageName = entry.getKey(); + final BrightnessCorrection correction = entry.getValue(); + serializer.startTag(null, TAG_BRIGHTNESS_CORRECTION); + serializer.attribute(null, ATTR_PACKAGE_NAME, packageName); + correction.saveToXml(serializer); + serializer.endTag(null, TAG_BRIGHTNESS_CORRECTION); + } + for (Map.Entry<Integer, BrightnessCorrection> entry : mCorrectionsByCategory.entrySet()) { + final int category = entry.getKey(); + final BrightnessCorrection correction = entry.getValue(); + serializer.startTag(null, TAG_BRIGHTNESS_CORRECTION); + serializer.attribute(null, ATTR_CATEGORY, Integer.toString(category)); + correction.saveToXml(serializer); + serializer.endTag(null, TAG_BRIGHTNESS_CORRECTION); + } + serializer.endTag(null, TAG_BRIGHTNESS_CORRECTIONS); + } + + /** + * Read a configuration from an XML parser. + * + * @param parser + * The XML parser. + * + * @throws IOException + * The parser failed to read the XML file. + * @throws XmlPullParserException + * The parser failed to parse the XML file. + * + * @hide + */ + public static BrightnessConfiguration loadFromXml(XmlPullParser parser) + throws IOException, XmlPullParserException { + String description = null; + List<Float> luxList = new ArrayList<>(); + List<Float> nitsList = new ArrayList<>(); + Map<String, BrightnessCorrection> correctionsByPackageName = new HashMap<>(); + Map<Integer, BrightnessCorrection> correctionsByCategory = new HashMap<>(); + final int configDepth = parser.getDepth(); + while (XmlUtils.nextElementWithin(parser, configDepth)) { + if (TAG_BRIGHTNESS_CURVE.equals(parser.getName())) { + description = parser.getAttributeValue(null, ATTR_DESCRIPTION); + final int curveDepth = parser.getDepth(); + while (XmlUtils.nextElementWithin(parser, curveDepth)) { + if (!TAG_BRIGHTNESS_POINT.equals(parser.getName())) { + continue; + } + final float lux = loadFloatFromXml(parser, ATTR_LUX); + final float nits = loadFloatFromXml(parser, ATTR_NITS); + luxList.add(lux); + nitsList.add(nits); + } + } + if (TAG_BRIGHTNESS_CORRECTIONS.equals(parser.getName())) { + final int correctionsDepth = parser.getDepth(); + while (XmlUtils.nextElementWithin(parser, correctionsDepth)) { + if (!TAG_BRIGHTNESS_CORRECTION.equals(parser.getName())) { + continue; + } + final String packageName = parser.getAttributeValue(null, ATTR_PACKAGE_NAME); + final String categoryText = parser.getAttributeValue(null, ATTR_CATEGORY); + BrightnessCorrection correction = BrightnessCorrection.loadFromXml(parser); + if (packageName != null) { + correctionsByPackageName.put(packageName, correction); + } else if (categoryText != null) { + try { + final int category = Integer.parseInt(categoryText); + correctionsByCategory.put(category, correction); + } catch (NullPointerException | NumberFormatException e) { + continue; + } + } + } + } + } + final int n = luxList.size(); + float[] lux = new float[n]; + float[] nits = new float[n]; + for (int i = 0; i < n; i++) { + lux[i] = luxList.get(i); + nits[i] = nitsList.get(i); + } + final BrightnessConfiguration.Builder builder = new BrightnessConfiguration.Builder(lux, + nits); + builder.setDescription(description); + for (Map.Entry<String, BrightnessCorrection> entry : correctionsByPackageName.entrySet()) { + final String packageName = entry.getKey(); + final BrightnessCorrection correction = entry.getValue(); + builder.addCorrectionByPackageName(packageName, correction); + } + for (Map.Entry<Integer, BrightnessCorrection> entry : correctionsByCategory.entrySet()) { + final int category = entry.getKey(); + final BrightnessCorrection correction = entry.getValue(); + builder.addCorrectionByCategory(category, correction); + } + return builder.build(); + } + + private static float loadFloatFromXml(XmlPullParser parser, String attribute) { + final String string = parser.getAttributeValue(null, attribute); + try { + return Float.parseFloat(string); + } catch (NullPointerException | NumberFormatException e) { + return Float.NaN; + } + } + + /** * A builder class for {@link BrightnessConfiguration}s. */ public static class Builder { + private static final int MAX_CORRECTIONS_BY_PACKAGE_NAME = 20; + private static final int MAX_CORRECTIONS_BY_CATEGORY = 20; + private float[] mCurveLux; private float[] mCurveNits; + private Map<String, BrightnessCorrection> mCorrectionsByPackageName; + private Map<Integer, BrightnessCorrection> mCorrectionsByCategory; private String mDescription; /** @@ -169,6 +407,88 @@ public final class BrightnessConfiguration implements Parcelable { checkMonotonic(nits, false /*strictly increasing*/, "nits"); mCurveLux = lux; mCurveNits = nits; + mCorrectionsByPackageName = new HashMap<>(); + mCorrectionsByCategory = new HashMap<>(); + } + + /** + * Returns the maximum number of corrections by package name allowed. + * + * @return The maximum number of corrections by package name allowed. + * + * @hide + */ + @SystemApi + public int getMaxCorrectionsByPackageName() { + return MAX_CORRECTIONS_BY_PACKAGE_NAME; + } + + /** + * Returns the maximum number of corrections by category allowed. + * + * @return The maximum number of corrections by category allowed. + * + * @hide + */ + @SystemApi + public int getMaxCorrectionsByCategory() { + return MAX_CORRECTIONS_BY_CATEGORY; + } + + /** + * Add a brightness correction by app package name. + * This correction is applied whenever an app with this package name has the top activity + * of the focused stack. + * + * @param packageName + * The app's package name. + * @param correction + * The brightness correction. + * + * @return The builder. + * + * @throws IllegalArgumentExceptions + * Maximum number of corrections by package name exceeded (see + * {@link #getMaxCorrectionsByPackageName}). + * + * @hide + */ + @SystemApi + public Builder addCorrectionByPackageName(String packageName, + BrightnessCorrection correction) { + if (mCorrectionsByPackageName.size() >= getMaxCorrectionsByPackageName()) { + throw new IllegalArgumentException("Too many corrections by package name"); + } + mCorrectionsByPackageName.put(packageName, correction); + return this; + } + + /** + * Add a brightness correction by app category. + * This correction is applied whenever an app with this category has the top activity of + * the focused stack, and only if a correction by package name has not been applied. + * + * @param category + * The {@link android.content.pm.ApplicationInfo#category app category}. + * @param correction + * The brightness correction. + * + * @return The builder. + * + * @throws IllegalArgumentException + * Maximum number of corrections by category exceeded (see + * {@link #getMaxCorrectionsByCategory}). + * + * @hide + */ + @SystemApi + public Builder addCorrectionByCategory(@ApplicationInfo.Category int category, + BrightnessCorrection correction) { + if (mCorrectionsByCategory.size() >= getMaxCorrectionsByCategory()) { + throw new IllegalArgumentException("Too many corrections by category"); + } + mCorrectionsByCategory.put(category, correction); + return this; } /** @@ -191,7 +511,8 @@ public final class BrightnessConfiguration implements Parcelable { if (mCurveLux == null || mCurveNits == null) { throw new IllegalStateException("A curve must be set!"); } - return new BrightnessConfiguration(mCurveLux, mCurveNits, mDescription); + return new BrightnessConfiguration(mCurveLux, mCurveNits, mCorrectionsByPackageName, + mCorrectionsByCategory, mDescription); } private static void checkMonotonic(float[] vals, boolean strictlyIncreasing, String name) { diff --git a/core/java/android/hardware/display/BrightnessCorrection.aidl b/core/java/android/hardware/display/BrightnessCorrection.aidl new file mode 100644 index 000000000000..3abe29cc0076 --- /dev/null +++ b/core/java/android/hardware/display/BrightnessCorrection.aidl @@ -0,0 +1,19 @@ +/* + * 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.hardware.display; + +parcelable BrightnessCorrection; diff --git a/core/java/android/hardware/display/BrightnessCorrection.java b/core/java/android/hardware/display/BrightnessCorrection.java new file mode 100644 index 000000000000..c4e0e3b723cd --- /dev/null +++ b/core/java/android/hardware/display/BrightnessCorrection.java @@ -0,0 +1,245 @@ +/* + * 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.hardware.display; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.MathUtils; + +import com.android.internal.util.XmlUtils; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.IOException; + +/** + * BrightnessCorrection encapsulates a correction to the brightness, without comitting to the + * actual correction scheme. + * It is used by the BrightnessConfiguration, which maps context (e.g. the foreground app's package + * name and category) to corrections that need to be applied to the brightness within that context. + * Corrections are currently done by the app that has the top activity of the focused stack, either + * by its package name, or (if its package name is not mapped to any correction) by its category. + * + * @hide + */ +@SystemApi +public final class BrightnessCorrection implements Parcelable { + + private static final int SCALE_AND_TRANSLATE_LOG = 1; + + private static final String TAG_SCALE_AND_TRANSLATE_LOG = "scale-and-translate-log"; + + private BrightnessCorrectionImplementation mImplementation; + + // Parcelable classes must be final, and protected methods are not allowed in APIs, so we can't + // make this class abstract and use composition instead of inheritence. + private BrightnessCorrection(BrightnessCorrectionImplementation implementation) { + mImplementation = implementation; + } + + /** + * Creates a BrightnessCorrection that given {@code brightness}, corrects it to be + * {@code exp(scale * log(brightness) + translate)}. + * + * @param scale + * How much to scale the log brightness. + * @param translate + * How much to translate the log brightness. + * + * @return A BrightnessCorrection that given {@code brightness}, corrects it to be + * {@code exp(scale * log(brightness) + translate)}. + * + * @throws IllegalArgumentException + * - scale or translate are NaN. + */ + @NonNull + public static BrightnessCorrection createScaleAndTranslateLog(float scale, float translate) { + BrightnessCorrectionImplementation implementation = + new ScaleAndTranslateLog(scale, translate); + return new BrightnessCorrection(implementation); + } + + /** + * Applies the brightness correction to a given brightness. + * + * @param brightness + * The brightness. + * + * @return The corrected brightness. + */ + public float apply(float brightness) { + return mImplementation.apply(brightness); + } + + /** + * Returns a string representation. + * + * @return A string representation. + */ + public String toString() { + return mImplementation.toString(); + } + + public static final Creator<BrightnessCorrection> CREATOR = + new Creator<BrightnessCorrection>() { + public BrightnessCorrection createFromParcel(Parcel in) { + final int type = in.readInt(); + switch (type) { + case SCALE_AND_TRANSLATE_LOG: + return ScaleAndTranslateLog.readFromParcel(in); + } + return null; + } + + public BrightnessCorrection[] newArray(int size) { + return new BrightnessCorrection[size]; + } + }; + + @Override + public void writeToParcel(Parcel dest, int flags) { + mImplementation.writeToParcel(dest); + } + + @Override + public int describeContents() { + return 0; + } + + /** + * Writes the correction to an XML serializer. + * + * @param serializer + * The XML serializer. + * + * @hide + */ + public void saveToXml(XmlSerializer serializer) throws IOException { + mImplementation.saveToXml(serializer); + } + + /** + * Read a correction from an XML parser. + * + * @param parser + * The XML parser. + * + * @throws IOException + * The parser failed to read the XML file. + * @throws XmlPullParserException + * The parser failed to parse the XML file. + * + * @hide + */ + public static BrightnessCorrection loadFromXml(XmlPullParser parser) throws IOException, + XmlPullParserException { + final int depth = parser.getDepth(); + while (XmlUtils.nextElementWithin(parser, depth)) { + if (TAG_SCALE_AND_TRANSLATE_LOG.equals(parser.getName())) { + return ScaleAndTranslateLog.loadFromXml(parser); + } + } + return null; + } + + private static float loadFloatFromXml(XmlPullParser parser, String attribute) { + final String string = parser.getAttributeValue(null, attribute); + try { + return Float.parseFloat(string); + } catch (NullPointerException | NumberFormatException e) { + return Float.NaN; + } + } + + private interface BrightnessCorrectionImplementation { + float apply(float brightness); + String toString(); + void writeToParcel(Parcel dest); + void saveToXml(XmlSerializer serializer) throws IOException; + // Package-private static methods: + // static BrightnessCorrection readFromParcel(Parcel in); + // static BrightnessCorrection loadFromXml(XmlPullParser parser) throws IOException, + // XmlPullParserException; + } + + /** + * A BrightnessCorrection that given {@code brightness}, corrects it to be + * {@code exp(scale * log(brightness) + translate)}. + */ + private static class ScaleAndTranslateLog implements BrightnessCorrectionImplementation { + private static final float MIN_SCALE = 0.5f; + private static final float MAX_SCALE = 2.0f; + private static final float MIN_TRANSLATE = -0.6f; + private static final float MAX_TRANSLATE = 0.7f; + + private static final String ATTR_SCALE = "scale"; + private static final String ATTR_TRANSLATE = "translate"; + + private final float mScale; + private final float mTranslate; + + ScaleAndTranslateLog(float scale, float translate) { + if (Float.isNaN(scale) || Float.isNaN(translate)) { + throw new IllegalArgumentException("scale and translate must be numbers"); + } + mScale = MathUtils.constrain(scale, MIN_SCALE, MAX_SCALE); + mTranslate = MathUtils.constrain(translate, MIN_TRANSLATE, MAX_TRANSLATE); + } + + @Override + public float apply(float brightness) { + return MathUtils.exp(mScale * MathUtils.log(brightness) + mTranslate); + } + + @Override + public String toString() { + return "ScaleAndTranslateLog(" + mScale + ", " + mTranslate + ")"; + } + + @Override + public void writeToParcel(Parcel dest) { + dest.writeInt(SCALE_AND_TRANSLATE_LOG); + dest.writeFloat(mScale); + dest.writeFloat(mTranslate); + } + + @Override + public void saveToXml(XmlSerializer serializer) throws IOException { + serializer.startTag(null, TAG_SCALE_AND_TRANSLATE_LOG); + serializer.attribute(null, ATTR_SCALE, Float.toString(mScale)); + serializer.attribute(null, ATTR_TRANSLATE, Float.toString(mTranslate)); + serializer.endTag(null, TAG_SCALE_AND_TRANSLATE_LOG); + } + + static BrightnessCorrection readFromParcel(Parcel in) { + float scale = in.readFloat(); + float translate = in.readFloat(); + return BrightnessCorrection.createScaleAndTranslateLog(scale, translate); + } + + static BrightnessCorrection loadFromXml(XmlPullParser parser) throws IOException, + XmlPullParserException { + final float scale = loadFloatFromXml(parser, ATTR_SCALE); + final float translate = loadFloatFromXml(parser, ATTR_TRANSLATE); + return BrightnessCorrection.createScaleAndTranslateLog(scale, translate); + } + } +} diff --git a/core/java/android/hardware/usb/IUsbManager.aidl b/core/java/android/hardware/usb/IUsbManager.aidl index f4e776cdb4b9..edc3f9466efc 100644 --- a/core/java/android/hardware/usb/IUsbManager.aidl +++ b/core/java/android/hardware/usb/IUsbManager.aidl @@ -20,7 +20,7 @@ import android.app.PendingIntent; import android.content.ComponentName; import android.hardware.usb.UsbAccessory; import android.hardware.usb.UsbDevice; -import android.hardware.usb.UsbPort; +import android.hardware.usb.ParcelableUsbPort; import android.hardware.usb.UsbPortStatus; import android.os.Bundle; import android.os.ParcelFileDescriptor; @@ -112,7 +112,7 @@ interface IUsbManager ParcelFileDescriptor getControlFd(long function); /* Gets the list of USB ports. */ - UsbPort[] getPorts(); + List<ParcelableUsbPort> getPorts(); /* Gets the status of the specified USB port. */ UsbPortStatus getPortStatus(in String portId); diff --git a/core/java/android/hardware/usb/UsbPort.aidl b/core/java/android/hardware/usb/ParcelableUsbPort.aidl index b7a79202914e..4431551fec92 100644 --- a/core/java/android/hardware/usb/UsbPort.aidl +++ b/core/java/android/hardware/usb/ParcelableUsbPort.aidl @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015, The Android Open Source Project + * 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. @@ -16,4 +16,4 @@ package android.hardware.usb; -parcelable UsbPort; +parcelable ParcelableUsbPort; diff --git a/core/java/android/hardware/usb/ParcelableUsbPort.java b/core/java/android/hardware/usb/ParcelableUsbPort.java new file mode 100644 index 000000000000..7f7ba96b40f2 --- /dev/null +++ b/core/java/android/hardware/usb/ParcelableUsbPort.java @@ -0,0 +1,87 @@ +/* + * 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.hardware.usb; + +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.annotations.Immutable; + +/** + * A parcelable wrapper to send UsbPorts over binders. + * + * @hide + */ +@Immutable +public final class ParcelableUsbPort implements Parcelable { + private final @NonNull String mId; + private final int mSupportedModes; + + private ParcelableUsbPort(@NonNull String id, int supportedModes) { + mId = id; + mSupportedModes = supportedModes; + } + + /** + * Create the parcelable version of a {@link UsbPort}. + * + * @param port The port to create a parcealable version of + * + * @return The parcelable version of the port + */ + public static @NonNull ParcelableUsbPort of(@NonNull UsbPort port) { + return new ParcelableUsbPort(port.getId(), port.getSupportedModes()); + } + + /** + * Create a {@link UsbPort} from this object. + * + * @param usbManager A link to the usbManager in the current context + * + * @return The UsbPort for this object + */ + public @NonNull UsbPort getUsbPort(@NonNull UsbManager usbManager) { + return new UsbPort(usbManager, mId, mSupportedModes); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mId); + dest.writeInt(mSupportedModes); + } + + public static final Creator<ParcelableUsbPort> CREATOR = + new Creator<ParcelableUsbPort>() { + @Override + public ParcelableUsbPort createFromParcel(Parcel in) { + String id = in.readString(); + int supportedModes = in.readInt(); + return new ParcelableUsbPort(id, supportedModes); + } + + @Override + public ParcelableUsbPort[] newArray(int size) { + return new ParcelableUsbPort[size]; + } + }; +} diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java index 41119416e419..601447814952 100644 --- a/core/java/android/hardware/usb/UsbManager.java +++ b/core/java/android/hardware/usb/UsbManager.java @@ -18,6 +18,7 @@ package android.hardware.usb; import android.Manifest; +import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresFeature; import android.annotation.RequiresPermission; @@ -39,9 +40,10 @@ import android.os.Process; import android.os.RemoteException; import android.util.Log; -import com.android.internal.util.Preconditions; - +import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.StringJoiner; @@ -97,15 +99,11 @@ public class UsbManager { * Broadcast Action: A broadcast for USB port changes. * * This intent is sent when a USB port is added, removed, or changes state. - * <ul> - * <li> {@link #EXTRA_PORT} containing the {@link android.hardware.usb.UsbPort} - * for the port. - * <li> {@link #EXTRA_PORT_STATUS} containing the {@link android.hardware.usb.UsbPortStatus} - * for the port, or null if the port has been removed - * </ul> * * @hide */ + @SystemApi + @RequiresPermission(Manifest.permission.MANAGE_USB) public static final String ACTION_USB_PORT_CHANGED = "android.hardware.usb.action.USB_PORT_CHANGED"; @@ -796,34 +794,44 @@ public class UsbManager { * device class (which supports all types of ports despite its name). * </p> * - * @return The list of USB ports, or null if none. + * @return The list of USB ports * * @hide */ - @UnsupportedAppUsage - public UsbPort[] getPorts() { + @SystemApi + @RequiresPermission(Manifest.permission.MANAGE_USB) + public @NonNull List<UsbPort> getPorts() { if (mService == null) { - return null; + return Collections.emptyList(); } + + List<ParcelableUsbPort> parcelablePorts; try { - return mService.getPorts(); + parcelablePorts = mService.getPorts(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } + + if (parcelablePorts == null) { + return Collections.emptyList(); + } else { + int numPorts = parcelablePorts.size(); + + ArrayList<UsbPort> ports = new ArrayList<>(numPorts); + for (int i = 0; i < numPorts; i++) { + ports.add(parcelablePorts.get(i).getUsbPort(this)); + } + + return ports; + } } /** - * Gets the status of the specified USB port. - * - * @param port The port to query. - * @return The status of the specified USB port, or null if unknown. + * Should only be called by {@link UsbPort#getStatus}. * * @hide */ - @UnsupportedAppUsage - public UsbPortStatus getPortStatus(UsbPort port) { - Preconditions.checkNotNull(port, "port must not be null"); - + UsbPortStatus getPortStatus(UsbPort port) { try { return mService.getPortStatus(port.getId()); } catch (RemoteException e) { @@ -832,29 +840,11 @@ public class UsbManager { } /** - * Sets the desired role combination of the port. - * <p> - * The supported role combinations depend on what is connected to the port and may be - * determined by consulting - * {@link UsbPortStatus#isRoleCombinationSupported UsbPortStatus.isRoleCombinationSupported}. - * </p><p> - * Note: This function is asynchronous and may fail silently without applying - * the requested changes. If this function does cause a status change to occur then - * a {@link #ACTION_USB_PORT_CHANGED} broadcast will be sent. - * </p> - * - * @param powerRole The desired power role: {@link UsbPort#POWER_ROLE_SOURCE} - * or {@link UsbPort#POWER_ROLE_SINK}, or 0 if no power role. - * @param dataRole The desired data role: {@link UsbPort#DATA_ROLE_HOST} - * or {@link UsbPort#DATA_ROLE_DEVICE}, or 0 if no data role. + * Should only be called by {@link UsbPort#setRoles}. * * @hide */ - @UnsupportedAppUsage - public void setPortRoles(UsbPort port, int powerRole, int dataRole) { - Preconditions.checkNotNull(port, "port must not be null"); - UsbPort.checkRoles(powerRole, dataRole); - + void setPortRoles(UsbPort port, int powerRole, int dataRole) { Log.d(TAG, "setPortRoles Package:" + mContext.getPackageName()); try { mService.setPortRoles(port.getId(), powerRole, dataRole); diff --git a/core/java/android/hardware/usb/UsbPort.java b/core/java/android/hardware/usb/UsbPort.java index afdb202211dd..37154e4c81b2 100644 --- a/core/java/android/hardware/usb/UsbPort.java +++ b/core/java/android/hardware/usb/UsbPort.java @@ -16,104 +16,53 @@ package android.hardware.usb; +import static android.hardware.usb.UsbPortStatus.DATA_ROLE_DEVICE; +import static android.hardware.usb.UsbPortStatus.DATA_ROLE_HOST; +import static android.hardware.usb.UsbPortStatus.DATA_ROLE_NONE; +import static android.hardware.usb.UsbPortStatus.MODE_AUDIO_ACCESSORY; +import static android.hardware.usb.UsbPortStatus.MODE_DEBUG_ACCESSORY; +import static android.hardware.usb.UsbPortStatus.MODE_DFP; +import static android.hardware.usb.UsbPortStatus.MODE_DUAL; +import static android.hardware.usb.UsbPortStatus.MODE_NONE; +import static android.hardware.usb.UsbPortStatus.MODE_UFP; +import static android.hardware.usb.UsbPortStatus.POWER_ROLE_NONE; +import static android.hardware.usb.UsbPortStatus.POWER_ROLE_SINK; +import static android.hardware.usb.UsbPortStatus.POWER_ROLE_SOURCE; + +import android.Manifest; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; import android.hardware.usb.V1_0.Constants; -import android.os.Parcel; -import android.os.Parcelable; import com.android.internal.util.Preconditions; /** * Represents a physical USB port and describes its characteristics. - * <p> - * This object is immutable. - * </p> * * @hide */ -public final class UsbPort implements Parcelable { +@SystemApi +public final class UsbPort { private final String mId; private final int mSupportedModes; - - public static final int MODE_NONE = Constants.PortMode.NONE; - /** - * Mode bit: This USB port can act as a downstream facing port (host). - * <p> - * Implies that the port supports the {@link #POWER_ROLE_SOURCE} and {@link #DATA_ROLE_HOST} - * combination of roles (and possibly others as well). - * </p> - */ - public static final int MODE_DFP = Constants.PortMode.DFP; - - /** - * Mode bit: This USB port can act as an upstream facing port (device). - * <p> - * Implies that the port supports the {@link #POWER_ROLE_SINK} and {@link #DATA_ROLE_DEVICE} - * combination of roles (and possibly others as well). - * </p> - */ - public static final int MODE_UFP = Constants.PortMode.UFP; - - /** - * Mode bit: This USB port can act either as an downstream facing port (host) or as - * an upstream facing port (device). - * <p> - * Implies that the port supports the {@link #POWER_ROLE_SOURCE} and {@link #DATA_ROLE_HOST} - * combination of roles and the {@link #POWER_ROLE_SINK} and {@link #DATA_ROLE_DEVICE} - * combination of roles (and possibly others as well). - * </p> - */ - public static final int MODE_DUAL = Constants.PortMode.DRP; - - /** - * Mode bit: This USB port can support USB Type-C Audio accessory. - */ - public static final int MODE_AUDIO_ACCESSORY = - android.hardware.usb.V1_1.Constants.PortMode_1_1.AUDIO_ACCESSORY; - - /** - * Mode bit: This USB port can support USB Type-C debug accessory. - */ - public static final int MODE_DEBUG_ACCESSORY = - android.hardware.usb.V1_1.Constants.PortMode_1_1.DEBUG_ACCESSORY; - - /** - * Power role: This USB port does not have a power role. - */ - public static final int POWER_ROLE_NONE = Constants.PortPowerRole.NONE; - - /** - * Power role: This USB port can act as a source (provide power). - */ - public static final int POWER_ROLE_SOURCE = Constants.PortPowerRole.SOURCE; - - /** - * Power role: This USB port can act as a sink (receive power). - */ - public static final int POWER_ROLE_SINK = Constants.PortPowerRole.SINK; - - /** - * Power role: This USB port does not have a data role. - */ - public static final int DATA_ROLE_NONE = Constants.PortDataRole.NONE; - - /** - * Data role: This USB port can act as a host (access data services). - */ - public static final int DATA_ROLE_HOST = Constants.PortDataRole.HOST; - - /** - * Data role: This USB port can act as a device (offer data services). - */ - public static final int DATA_ROLE_DEVICE = Constants.PortDataRole.DEVICE; + private final UsbManager mUsbManager; private static final int NUM_DATA_ROLES = Constants.PortDataRole.NUM_DATA_ROLES; + /** * Points to the first power role in the IUsb HAL. */ private static final int POWER_ROLE_OFFSET = Constants.PortPowerRole.NONE; /** @hide */ - public UsbPort(String id, int supportedModes) { + public UsbPort(@NonNull UsbManager usbManager, @NonNull String id, int supportedModes) { + Preconditions.checkNotNull(id); + Preconditions.checkFlagsArgument(supportedModes, + MODE_DFP | MODE_UFP | MODE_AUDIO_ACCESSORY | MODE_DEBUG_ACCESSORY); + + mUsbManager = usbManager; mId = id; mSupportedModes = supportedModes; } @@ -122,6 +71,8 @@ public final class UsbPort implements Parcelable { * Gets the unique id of the port. * * @return The unique id of the port; not intended for display. + * + * @hide */ public String getId() { return mId; @@ -133,23 +84,62 @@ public final class UsbPort implements Parcelable { * The actual mode of the port may vary depending on what is plugged into it. * </p> * - * @return The supported modes: one of {@link #MODE_DFP}, {@link #MODE_UFP}, or - * {@link #MODE_DUAL}. + * @return The supported modes: one of {@link UsbPortStatus#MODE_DFP}, + * {@link UsbPortStatus#MODE_UFP}, or {@link UsbPortStatus#MODE_DUAL}. + * + * @hide */ public int getSupportedModes() { return mSupportedModes; } /** + * Gets the status of this USB port. + * + * @return The status of the this port, or {@code null} if port is unknown. + */ + @RequiresPermission(Manifest.permission.MANAGE_USB) + public @Nullable UsbPortStatus getStatus() { + return mUsbManager.getPortStatus(this); + } + + /** + * Sets the desired role combination of the port. + * <p> + * The supported role combinations depend on what is connected to the port and may be + * determined by consulting + * {@link UsbPortStatus#isRoleCombinationSupported UsbPortStatus.isRoleCombinationSupported}. + * </p><p> + * Note: This function is asynchronous and may fail silently without applying + * the requested changes. If this function does cause a status change to occur then + * a {@link UsbManager#ACTION_USB_PORT_CHANGED} broadcast will be sent. + * </p> + * + * @param powerRole The desired power role: {@link UsbPortStatus#POWER_ROLE_SOURCE} or + * {@link UsbPortStatus#POWER_ROLE_SINK}, or + * {@link UsbPortStatus#POWER_ROLE_NONE} if no power role. + * @param dataRole The desired data role: {@link UsbPortStatus#DATA_ROLE_HOST} or + * {@link UsbPortStatus#DATA_ROLE_DEVICE}, or + * {@link UsbPortStatus#DATA_ROLE_NONE} if no data role. + */ + @RequiresPermission(Manifest.permission.MANAGE_USB) + public void setRoles(@UsbPortStatus.UsbPowerRole int powerRole, + @UsbPortStatus.UsbDataRole int dataRole) { + UsbPort.checkRoles(powerRole, dataRole); + + mUsbManager.setPortRoles(this, powerRole, dataRole); + } + + /** * Combines one power and one data role together into a unique value with * exactly one bit set. This can be used to efficiently determine whether * a combination of roles is supported by testing whether that bit is present * in a bit-field. * - * @param powerRole The desired power role: {@link UsbPort#POWER_ROLE_SOURCE} - * or {@link UsbPort#POWER_ROLE_SINK}, or 0 if no power role. - * @param dataRole The desired data role: {@link UsbPort#DATA_ROLE_HOST} - * or {@link UsbPort#DATA_ROLE_DEVICE}, or 0 if no data role. + * @param powerRole The desired power role: {@link UsbPortStatus#POWER_ROLE_SOURCE} + * or {@link UsbPortStatus#POWER_ROLE_SINK}, or 0 if no power role. + * @param dataRole The desired data role: {@link UsbPortStatus#DATA_ROLE_HOST} + * or {@link UsbPortStatus#DATA_ROLE_DEVICE}, or 0 if no data role. * @hide */ public static int combineRolesAsBit(int powerRole, int dataRole) { @@ -276,30 +266,4 @@ public final class UsbPort implements Parcelable { public String toString() { return "UsbPort{id=" + mId + ", supportedModes=" + modeToString(mSupportedModes) + "}"; } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeString(mId); - dest.writeInt(mSupportedModes); - } - - public static final Parcelable.Creator<UsbPort> CREATOR = - new Parcelable.Creator<UsbPort>() { - @Override - public UsbPort createFromParcel(Parcel in) { - String id = in.readString(); - int supportedModes = in.readInt(); - return new UsbPort(id, supportedModes); - } - - @Override - public UsbPort[] newArray(int size) { - return new UsbPort[size]; - } - }; } diff --git a/core/java/android/hardware/usb/UsbPortStatus.java b/core/java/android/hardware/usb/UsbPortStatus.java index 2cd8209fccda..d30201a597bf 100644 --- a/core/java/android/hardware/usb/UsbPortStatus.java +++ b/core/java/android/hardware/usb/UsbPortStatus.java @@ -16,27 +16,134 @@ package android.hardware.usb; -import android.annotation.UnsupportedAppUsage; +import android.annotation.IntDef; +import android.annotation.SystemApi; +import android.hardware.usb.V1_0.Constants; import android.os.Parcel; import android.os.Parcelable; +import com.android.internal.annotations.Immutable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * Describes the status of a USB port. - * <p> - * This object is immutable. - * </p> * * @hide */ +@Immutable +@SystemApi public final class UsbPortStatus implements Parcelable { private final int mCurrentMode; - private final int mCurrentPowerRole; - private final int mCurrentDataRole; + private final @UsbPowerRole int mCurrentPowerRole; + private final @UsbDataRole int mCurrentDataRole; private final int mSupportedRoleCombinations; + /** + * Power role: This USB port does not have a power role. + */ + public static final int POWER_ROLE_NONE = Constants.PortPowerRole.NONE; + + /** + * Power role: This USB port can act as a source (provide power). + */ + public static final int POWER_ROLE_SOURCE = Constants.PortPowerRole.SOURCE; + + /** + * Power role: This USB port can act as a sink (receive power). + */ + public static final int POWER_ROLE_SINK = Constants.PortPowerRole.SINK; + + @IntDef(prefix = { "POWER_ROLE_" }, value = { + POWER_ROLE_NONE, + POWER_ROLE_SOURCE, + POWER_ROLE_SINK + }) + @Retention(RetentionPolicy.SOURCE) + @interface UsbPowerRole{} + + /** + * Power role: This USB port does not have a data role. + */ + public static final int DATA_ROLE_NONE = Constants.PortDataRole.NONE; + + /** + * Data role: This USB port can act as a host (access data services). + */ + public static final int DATA_ROLE_HOST = Constants.PortDataRole.HOST; + + /** + * Data role: This USB port can act as a device (offer data services). + */ + public static final int DATA_ROLE_DEVICE = Constants.PortDataRole.DEVICE; + + @IntDef(prefix = { "DATA_ROLE_" }, value = { + DATA_ROLE_NONE, + DATA_ROLE_HOST, + DATA_ROLE_DEVICE + }) + @Retention(RetentionPolicy.SOURCE) + @interface UsbDataRole{} + + /** + * There is currently nothing connected to this USB port. + */ + public static final int MODE_NONE = Constants.PortMode.NONE; + + /** + * This USB port can act as a downstream facing port (host). + * + * <p> Implies that the port supports the {@link #POWER_ROLE_SOURCE} and + * {@link #DATA_ROLE_HOST} combination of roles (and possibly others as well). + */ + public static final int MODE_DFP = Constants.PortMode.DFP; + + /** + * This USB port can act as an upstream facing port (device). + * + * <p> Implies that the port supports the {@link #POWER_ROLE_SINK} and + * {@link #DATA_ROLE_DEVICE} combination of roles (and possibly others as well). + */ + public static final int MODE_UFP = Constants.PortMode.UFP; + + /** + * This USB port can act either as an downstream facing port (host) or as + * an upstream facing port (device). + * + * <p> Implies that the port supports the {@link #POWER_ROLE_SOURCE} and + * {@link #DATA_ROLE_HOST} combination of roles and the {@link #POWER_ROLE_SINK} and + * {@link #DATA_ROLE_DEVICE} combination of roles (and possibly others as well). + * + * @hide + */ + public static final int MODE_DUAL = Constants.PortMode.DRP; + + /** + * This USB port can support USB Type-C Audio accessory. + */ + public static final int MODE_AUDIO_ACCESSORY = + android.hardware.usb.V1_1.Constants.PortMode_1_1.AUDIO_ACCESSORY; + + /** + * This USB port can support USB Type-C debug accessory. + */ + public static final int MODE_DEBUG_ACCESSORY = + android.hardware.usb.V1_1.Constants.PortMode_1_1.DEBUG_ACCESSORY; + + @IntDef(prefix = { "MODE_" }, flag = true, value = { + MODE_NONE, + MODE_DFP, + MODE_UFP, + MODE_AUDIO_ACCESSORY, + MODE_DEBUG_ACCESSORY, + }) + @Retention(RetentionPolicy.SOURCE) + @interface UsbPortMode{} + /** @hide */ - public UsbPortStatus(int currentMode, int currentPowerRole, int currentDataRole, - int supportedRoleCombinations) { + public UsbPortStatus(int currentMode, @UsbPowerRole int currentPowerRole, + @UsbDataRole int currentDataRole, int supportedRoleCombinations) { mCurrentMode = currentMode; mCurrentPowerRole = currentPowerRole; mCurrentDataRole = currentDataRole; @@ -46,9 +153,8 @@ public final class UsbPortStatus implements Parcelable { /** * Returns true if there is anything connected to the port. * - * @return True if there is anything connected to the port. + * @return {@code true} iff there is anything connected to the port. */ - @UnsupportedAppUsage public boolean isConnected() { return mCurrentMode != 0; } @@ -56,33 +162,31 @@ public final class UsbPortStatus implements Parcelable { /** * Gets the current mode of the port. * - * @return The current mode: {@link UsbPort#MODE_DFP}, {@link UsbPort#MODE_UFP}, - * or 0 if nothing is connected. + * @return The current mode: {@link #MODE_DFP}, {@link #MODE_UFP}, + * {@link #MODE_AUDIO_ACCESSORY}, {@link #MODE_DEBUG_ACCESSORY}, or {@link {@link #MODE_NONE} if + * nothing is connected. */ - @UnsupportedAppUsage - public int getCurrentMode() { + public @UsbPortMode int getCurrentMode() { return mCurrentMode; } /** * Gets the current power role of the port. * - * @return The current power role: {@link UsbPort#POWER_ROLE_SOURCE}, - * {@link UsbPort#POWER_ROLE_SINK}, or 0 if nothing is connected. + * @return The current power role: {@link #POWER_ROLE_SOURCE}, {@link #POWER_ROLE_SINK}, or + * {@link #POWER_ROLE_NONE} if nothing is connected. */ - @UnsupportedAppUsage - public int getCurrentPowerRole() { + public @UsbPowerRole int getCurrentPowerRole() { return mCurrentPowerRole; } /** * Gets the current data role of the port. * - * @return The current data role: {@link UsbPort#DATA_ROLE_HOST}, - * {@link UsbPort#DATA_ROLE_DEVICE}, or 0 if nothing is connected. + * @return The current data role: {@link #DATA_ROLE_HOST}, {@link #DATA_ROLE_DEVICE}, or + * {@link #DATA_ROLE_NONE} if nothing is connected. */ - @UnsupportedAppUsage - public int getCurrentDataRole() { + public @UsbDataRole int getCurrentDataRole() { return mCurrentDataRole; } @@ -90,19 +194,20 @@ public final class UsbPortStatus implements Parcelable { * Returns true if the specified power and data role combination is supported * given what is currently connected to the port. * - * @param powerRole The power role to check: {@link UsbPort#POWER_ROLE_SOURCE} - * or {@link UsbPort#POWER_ROLE_SINK}, or 0 if no power role. - * @param dataRole The data role to check: either {@link UsbPort#DATA_ROLE_HOST} - * or {@link UsbPort#DATA_ROLE_DEVICE}, or 0 if no data role. + * @param powerRole The power role to check: {@link #POWER_ROLE_SOURCE} or + * {@link #POWER_ROLE_SINK}, or {@link #POWER_ROLE_NONE} if no power role. + * @param dataRole The data role to check: either {@link #DATA_ROLE_HOST} or + * {@link #DATA_ROLE_DEVICE}, or {@link #DATA_ROLE_NONE} if no data role. */ - @UnsupportedAppUsage - public boolean isRoleCombinationSupported(int powerRole, int dataRole) { + public boolean isRoleCombinationSupported(@UsbPowerRole int powerRole, + @UsbDataRole int dataRole) { return (mSupportedRoleCombinations & UsbPort.combineRolesAsBit(powerRole, dataRole)) != 0; } - /** @hide */ - @UnsupportedAppUsage + /** + * Get the supported role combinations. + */ public int getSupportedRoleCombinations() { return mSupportedRoleCombinations; } diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java index 8a5f43de6883..c41a56c7305f 100644 --- a/core/java/android/net/NetworkCapabilities.java +++ b/core/java/android/net/NetworkCapabilities.java @@ -1467,7 +1467,7 @@ public final class NetworkCapabilities implements Parcelable { appendStringRepresentationOfBitMaskToStringBuilder(sb, mNetworkCapabilities, NetworkCapabilities::capabilityNameOf, "&"); } - if (0 != mNetworkCapabilities) { + if (0 != mUnwantedNetworkCapabilities) { sb.append(" Unwanted: "); appendStringRepresentationOfBitMaskToStringBuilder(sb, mUnwantedNetworkCapabilities, NetworkCapabilities::capabilityNameOf, "&"); diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java index 124d7b174739..8a0d916187b7 100644 --- a/core/java/android/os/GraphicsEnvironment.java +++ b/core/java/android/os/GraphicsEnvironment.java @@ -17,8 +17,10 @@ package android.os; import android.content.Context; +import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; import android.content.res.AssetFileDescriptor; import android.content.res.AssetManager; import android.opengl.EGL14; @@ -57,9 +59,9 @@ public class GraphicsEnvironment { private static final String TAG = "GraphicsEnvironment"; private static final String PROPERTY_GFX_DRIVER = "ro.gfx.driver.0"; private static final String PROPERTY_GFX_DRIVER_WHITELIST = "ro.gfx.driver.whitelist.0"; - private static final String ANGLE_PACKAGE_NAME = "com.google.android.angle"; private static final String ANGLE_RULES_FILE = "a4a_rules.json"; private static final String ANGLE_TEMP_RULES = "debug.angle.rules"; + private static final String ACTION_ANGLE_FOR_ANDROID = "android.app.action.ANGLE_FOR_ANDROID"; private ClassLoader mClassLoader; private String mLayerPath; @@ -276,6 +278,27 @@ public class GraphicsEnvironment { } /** + * Get the ANGLE package name. + */ + private String getAnglePackageName(Context context) { + Intent intent = new Intent(ACTION_ANGLE_FOR_ANDROID); + + List<ResolveInfo> resolveInfos = context.getPackageManager() + .queryIntentActivities(intent, PackageManager.MATCH_SYSTEM_ONLY); + if (resolveInfos.size() != 1) { + Log.e(TAG, "Invalid number of ANGLE packages. Required: 1, Found: " + + resolveInfos.size()); + for (ResolveInfo resolveInfo : resolveInfos) { + Log.e(TAG, "Found ANGLE package: " + resolveInfo.activityInfo.packageName); + } + return ""; + } + + // Must be exactly 1 ANGLE PKG found to get here. + return resolveInfos.get(0).activityInfo.packageName; + } + + /** * Pass ANGLE details down to trigger enable logic */ private void setupAngle(Context context, Bundle bundle, String packageName) { @@ -286,12 +309,18 @@ public class GraphicsEnvironment { + "set to: '" + devOptIn + "'"); } + String anglePkgName = getAnglePackageName(context); + if (anglePkgName.isEmpty()) { + Log.e(TAG, "Failed to find ANGLE package."); + return; + } + ApplicationInfo angleInfo; try { - angleInfo = context.getPackageManager().getApplicationInfo(ANGLE_PACKAGE_NAME, + angleInfo = context.getPackageManager().getApplicationInfo(anglePkgName, PackageManager.MATCH_SYSTEM_ONLY); } catch (PackageManager.NameNotFoundException e) { - Log.w(TAG, "ANGLE package '" + ANGLE_PACKAGE_NAME + "' not installed"); + Log.w(TAG, "ANGLE package '" + anglePkgName + "' not installed"); return; } @@ -351,7 +380,7 @@ public class GraphicsEnvironment { angleAssets = context.getPackageManager().getResourcesForApplication(angleInfo).getAssets(); } catch (PackageManager.NameNotFoundException e) { - Log.w(TAG, "Failed to get AssetManager for '" + ANGLE_PACKAGE_NAME + "'"); + Log.w(TAG, "Failed to get AssetManager for '" + anglePkgName + "'"); return; } @@ -360,7 +389,7 @@ public class GraphicsEnvironment { assetsFd = angleAssets.openFd(ANGLE_RULES_FILE); } catch (IOException e) { Log.w(TAG, "Failed to get AssetFileDescriptor for " + ANGLE_RULES_FILE + " from " - + "'" + ANGLE_PACKAGE_NAME + "'"); + + "'" + anglePkgName + "'"); return; } diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java index 6de1ff4bc097..63912ec327a4 100644 --- a/core/java/android/os/ParcelFileDescriptor.java +++ b/core/java/android/os/ParcelFileDescriptor.java @@ -17,7 +17,11 @@ package android.os; import static android.system.OsConstants.AF_UNIX; +import static android.system.OsConstants.F_DUPFD; +import static android.system.OsConstants.F_DUPFD_CLOEXEC; +import static android.system.OsConstants.O_CLOEXEC; import static android.system.OsConstants.SEEK_SET; +import static android.system.OsConstants.SOCK_CLOEXEC; import static android.system.OsConstants.SOCK_SEQPACKET; import static android.system.OsConstants.SOCK_STREAM; import static android.system.OsConstants.S_IROTH; @@ -37,6 +41,7 @@ import android.system.StructStat; import android.util.Log; import dalvik.system.CloseGuard; +import dalvik.system.VMRuntime; import libcore.io.IoUtils; import libcore.io.Memory; @@ -293,7 +298,7 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { } private static FileDescriptor openInternal(File file, int mode) throws FileNotFoundException { - final int flags = FileUtils.translateModePfdToPosix(mode); + final int flags = FileUtils.translateModePfdToPosix(mode) | ifAtLeastQ(O_CLOEXEC); int realMode = S_IRWXU | S_IRWXG; if ((mode & MODE_WORLD_READABLE) != 0) realMode |= S_IROTH; @@ -315,7 +320,9 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { */ public static ParcelFileDescriptor dup(FileDescriptor orig) throws IOException { try { - final FileDescriptor fd = Os.dup(orig); + final FileDescriptor fd = new FileDescriptor(); + int intfd = Os.fcntlInt(orig, (isAtLeastQ() ? F_DUPFD_CLOEXEC : F_DUPFD), 0); + fd.setInt$(intfd); return new ParcelFileDescriptor(fd); } catch (ErrnoException e) { throw e.rethrowAsIOException(); @@ -351,7 +358,9 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { original.setInt$(fd); try { - final FileDescriptor dup = Os.dup(original); + final FileDescriptor dup = new FileDescriptor(); + int intfd = Os.fcntlInt(original, (isAtLeastQ() ? F_DUPFD_CLOEXEC : F_DUPFD), 0); + dup.setInt$(intfd); return new ParcelFileDescriptor(dup); } catch (ErrnoException e) { throw e.rethrowAsIOException(); @@ -413,7 +422,7 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { */ public static ParcelFileDescriptor[] createPipe() throws IOException { try { - final FileDescriptor[] fds = Os.pipe(); + final FileDescriptor[] fds = Os.pipe2(ifAtLeastQ(O_CLOEXEC)); return new ParcelFileDescriptor[] { new ParcelFileDescriptor(fds[0]), new ParcelFileDescriptor(fds[1]) }; @@ -435,7 +444,7 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { public static ParcelFileDescriptor[] createReliablePipe() throws IOException { try { final FileDescriptor[] comm = createCommSocketPair(); - final FileDescriptor[] fds = Os.pipe(); + final FileDescriptor[] fds = Os.pipe2(ifAtLeastQ(O_CLOEXEC)); return new ParcelFileDescriptor[] { new ParcelFileDescriptor(fds[0], comm[0]), new ParcelFileDescriptor(fds[1], comm[1]) }; @@ -459,7 +468,7 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { try { final FileDescriptor fd0 = new FileDescriptor(); final FileDescriptor fd1 = new FileDescriptor(); - Os.socketpair(AF_UNIX, type, 0, fd0, fd1); + Os.socketpair(AF_UNIX, type | ifAtLeastQ(SOCK_CLOEXEC), 0, fd0, fd1); return new ParcelFileDescriptor[] { new ParcelFileDescriptor(fd0), new ParcelFileDescriptor(fd1) }; @@ -489,7 +498,7 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { final FileDescriptor[] comm = createCommSocketPair(); final FileDescriptor fd0 = new FileDescriptor(); final FileDescriptor fd1 = new FileDescriptor(); - Os.socketpair(AF_UNIX, type, 0, fd0, fd1); + Os.socketpair(AF_UNIX, type | ifAtLeastQ(SOCK_CLOEXEC), 0, fd0, fd1); return new ParcelFileDescriptor[] { new ParcelFileDescriptor(fd0, comm[0]), new ParcelFileDescriptor(fd1, comm[1]) }; @@ -505,7 +514,7 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { // across multiple IO operations. final FileDescriptor comm1 = new FileDescriptor(); final FileDescriptor comm2 = new FileDescriptor(); - Os.socketpair(AF_UNIX, SOCK_SEQPACKET, 0, comm1, comm2); + Os.socketpair(AF_UNIX, SOCK_SEQPACKET | ifAtLeastQ(SOCK_CLOEXEC), 0, comm1, comm2); IoUtils.setBlocking(comm1, false); IoUtils.setBlocking(comm2, false); return new FileDescriptor[] { comm1, comm2 }; @@ -1111,4 +1120,12 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { return "{" + status + ": " + msg + "}"; } } + + private static boolean isAtLeastQ() { + return (VMRuntime.getRuntime().getTargetSdkVersion() >= Build.VERSION_CODES.Q); + } + + private static int ifAtLeastQ(int value) { + return isAtLeastQ() ? value : 0; + } } diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java index f38f74046934..457453fd210b 100644 --- a/core/java/android/provider/MediaStore.java +++ b/core/java/android/provider/MediaStore.java @@ -1002,6 +1002,7 @@ public final class MediaStore { public static final int MICRO_KIND = 3; public static final Point MINI_SIZE = new Point(512, 384); + public static final Point FULL_SCREEN_SIZE = new Point(1024, 786); public static final Point MICRO_SIZE = new Point(96, 96); } @@ -1127,6 +1128,8 @@ public final class MediaStore { final Point size; if (kind == ThumbnailConstants.MICRO_KIND) { size = ThumbnailConstants.MICRO_SIZE; + } else if (kind == ThumbnailConstants.FULL_SCREEN_KIND) { + size = ThumbnailConstants.FULL_SCREEN_SIZE; } else if (kind == ThumbnailConstants.MINI_KIND) { size = ThumbnailConstants.MINI_SIZE; } else { diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 299db730b3c6..a39f696aed73 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -12949,14 +12949,21 @@ public final class Settings { /** * Property used by {@code com.android.server.SystemServer} on start to decide whether - * the Content Capture service should be created or not + * the Content Capture service should be created or not. * - * <p>By default it should *NOT* be set (in which case the decision is based on whether - * the OEM provides an implementation for the service), but it can be overridden to: + * <p>Possible values are: * * <ul> - * <li>Provide a "kill switch" so OEMs can disable it remotely in case of emergency. - * <li>Enable the CTS tests to be run on AOSP builds + * <li>If set to {@link #CONTENT_CAPTURE_SERVICE_EXPLICITLY_ENABLED_DEFAULT}, it will only + * be set if the OEM provides and defines the service name by overlaying + * {@code config_defaultContentCaptureService} (this is the "default" mode) + * <li>If set to {@link #CONTENT_CAPTURE_SERVICE_EXPLICITLY_ENABLED_ALWAYS}, it will + * always be enabled, even when the resource is not overlaid (this is useful during + * development and/or to run the CTS tests on AOSP builds). + * <li>Otherwise, it's explicitly disabled (this could work as a "kill switch" so OEMs + * can disable it remotely in case of emergency by setting to something else (like + * {@code "false"}); notice that it's also disabled if the OEM doesn't explicitly set one + * of the values above). * </ul> * * @hide @@ -12964,6 +12971,11 @@ public final class Settings { public static final String CONTENT_CAPTURE_SERVICE_EXPLICITLY_ENABLED = "content_capture_service_explicitly_enabled"; + /** @hide */ + public static final String CONTENT_CAPTURE_SERVICE_EXPLICITLY_ENABLED_DEFAULT = "default"; + /** @hide */ + public static final String CONTENT_CAPTURE_SERVICE_EXPLICITLY_ENABLED_ALWAYS = "always"; + /** {@hide} */ public static final String ISOLATED_STORAGE_LOCAL = "isolated_storage_local"; /** {@hide} */ @@ -14260,6 +14272,44 @@ public final class Settings { } } + /** + * <p> + * A Settings panel is floating UI that contains a fixed subset of settings to address a + * particular user problem. For example, the + * {@link #ACTION_INTERNET_CONNECTIVITY Internet Panel} surfaces settings related to + * connecting to the internet. + * <p> + * Settings panels appear above the calling app to address the problem without + * the user needing to open Settings and thus leave their current screen. + */ + public static final class Panel { + private Panel() { + } + + /** + * Activity Action: Show a settings dialog containing settings to enable internet + * connection. + * <p> + * Input: Nothing. + * <p> + * Output: Nothing. + */ + @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_INTERNET_CONNECTIVITY = + "android.settings.panel.action.INTERNET_CONNECTIVITY"; + + /** + * Activity Action: Show a settings dialog containing all volume streams. + * <p> + * Input: Nothing. + * <p> + * Output: Nothing. + */ + @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_VOLUME = + "android.settings.panel.action.VOLUME"; + } + private static final String[] PM_WRITE_SETTINGS = { android.Manifest.permission.WRITE_SETTINGS }; diff --git a/core/java/android/service/contentcapture/ContentCaptureEventsRequest.java b/core/java/android/service/contentcapture/ContentCaptureEventsRequest.java index df58f52dc59c..028180dd3bf0 100644 --- a/core/java/android/service/contentcapture/ContentCaptureEventsRequest.java +++ b/core/java/android/service/contentcapture/ContentCaptureEventsRequest.java @@ -17,6 +17,7 @@ package android.service.contentcapture; import android.annotation.NonNull; import android.annotation.SystemApi; +import android.content.pm.ParceledListSlice; import android.os.Parcel; import android.os.Parcelable; import android.view.contentcapture.ContentCaptureEvent; @@ -31,10 +32,10 @@ import java.util.List; @SystemApi public final class ContentCaptureEventsRequest implements Parcelable { - private final List<ContentCaptureEvent> mEvents; + private final ParceledListSlice<ContentCaptureEvent> mEvents; /** @hide */ - public ContentCaptureEventsRequest(@NonNull List<ContentCaptureEvent> events) { + public ContentCaptureEventsRequest(@NonNull ParceledListSlice<ContentCaptureEvent> events) { mEvents = events; } @@ -43,7 +44,7 @@ public final class ContentCaptureEventsRequest implements Parcelable { */ @NonNull public List<ContentCaptureEvent> getEvents() { - return mEvents; + return mEvents.getList(); } @Override @@ -53,7 +54,7 @@ public final class ContentCaptureEventsRequest implements Parcelable { @Override public void writeToParcel(Parcel parcel, int flags) { - parcel.writeTypedList(mEvents, flags); + parcel.writeParcelable(mEvents, flags); } public static final Parcelable.Creator<ContentCaptureEventsRequest> CREATOR = @@ -61,8 +62,7 @@ public final class ContentCaptureEventsRequest implements Parcelable { @Override public ContentCaptureEventsRequest createFromParcel(Parcel parcel) { - return new ContentCaptureEventsRequest(parcel - .createTypedArrayList(ContentCaptureEvent.CREATOR)); + return new ContentCaptureEventsRequest(parcel.readParcelable(null)); } @Override diff --git a/core/java/android/service/contentcapture/ContentCaptureService.java b/core/java/android/service/contentcapture/ContentCaptureService.java index 58848fce43d6..5e87c4092236 100644 --- a/core/java/android/service/contentcapture/ContentCaptureService.java +++ b/core/java/android/service/contentcapture/ContentCaptureService.java @@ -24,15 +24,28 @@ import android.annotation.SystemApi; import android.app.Service; import android.content.ComponentName; import android.content.Intent; +import android.content.pm.ParceledListSlice; +import android.os.Binder; +import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.RemoteException; +import android.util.ArrayMap; import android.util.Log; +import android.util.Slog; +import android.view.contentcapture.ActivityContentCaptureSession; import android.view.contentcapture.ContentCaptureContext; import android.view.contentcapture.ContentCaptureEvent; +import android.view.contentcapture.ContentCaptureManager; +import android.view.contentcapture.ContentCaptureSession; import android.view.contentcapture.ContentCaptureSessionId; +import android.view.contentcapture.IContentCaptureDirectManager; +import com.android.internal.os.IResultReceiver; + +import java.io.FileDescriptor; +import java.io.PrintWriter; import java.util.List; import java.util.Set; @@ -63,39 +76,54 @@ public abstract class ContentCaptureService extends Service { private Handler mHandler; - private final IContentCaptureService mInterface = new IContentCaptureService.Stub() { + /** + * Binder that receives calls from the system server. + */ + private final IContentCaptureService mServerInterface = new IContentCaptureService.Stub() { @Override - public void onSessionLifecycle(ContentCaptureContext context, String sessionId) - throws RemoteException { - if (context != null) { - mHandler.sendMessage( - obtainMessage(ContentCaptureService::handleOnCreateSession, - ContentCaptureService.this, context, sessionId)); - } else { - mHandler.sendMessage( - obtainMessage(ContentCaptureService::handleOnDestroySession, - ContentCaptureService.this, sessionId)); - } + public void onSessionStarted(ContentCaptureContext context, String sessionId, int uid, + IResultReceiver clientReceiver) { + mHandler.sendMessage(obtainMessage(ContentCaptureService::handleOnCreateSession, + ContentCaptureService.this, context, sessionId, uid, clientReceiver)); } @Override - public void onContentCaptureEventsRequest(String sessionId, - ContentCaptureEventsRequest request) { + public void onActivitySnapshot(String sessionId, SnapshotData snapshotData) { mHandler.sendMessage( - obtainMessage(ContentCaptureService::handleOnContentCaptureEventsRequest, - ContentCaptureService.this, sessionId, request)); + obtainMessage(ContentCaptureService::handleOnActivitySnapshot, + ContentCaptureService.this, sessionId, snapshotData)); + } + @Override + public void onSessionFinished(String sessionId) { + mHandler.sendMessage(obtainMessage(ContentCaptureService::handleFinishSession, + ContentCaptureService.this, sessionId)); } + }; + + /** + * Binder that receives calls from the app. + */ + private final IContentCaptureDirectManager mClientInterface = + new IContentCaptureDirectManager.Stub() { @Override - public void onActivitySnapshot(String sessionId, SnapshotData snapshotData) { - mHandler.sendMessage( - obtainMessage(ContentCaptureService::handleOnActivitySnapshot, - ContentCaptureService.this, sessionId, snapshotData)); + public void sendEvents(String sessionId, + @SuppressWarnings("rawtypes") ParceledListSlice events) { + mHandler.sendMessage(obtainMessage(ContentCaptureService::handleSendEvents, + ContentCaptureService.this, sessionId, Binder.getCallingUid(), events)); } }; + /** + * List of sessions per UID. + * + * <p>This map is populated when an session is started, which is called by the system server + * and can be trusted. Then subsequent calls made by the app are verified against this map. + */ + private final ArrayMap<String, Integer> mSessionsByUid = new ArrayMap<>(); + @CallSuper @Override public void onCreate() { @@ -107,7 +135,7 @@ public abstract class ContentCaptureService extends Service { @Override public final IBinder onBind(Intent intent) { if (SERVICE_INTERFACE.equals(intent.getAction())) { - return mInterface.asBinder(); + return mServerInterface.asBinder(); } Log.w(TAG, "Tried to bind to wrong intent (should be " + SERVICE_INTERFACE + ": " + intent); return null; @@ -196,8 +224,10 @@ public abstract class ContentCaptureService extends Service { * @param sessionId the session's Id * @param request the events */ - public abstract void onContentCaptureEventsRequest(@NonNull ContentCaptureSessionId sessionId, - @NonNull ContentCaptureEventsRequest request); + public void onContentCaptureEventsRequest(@NonNull ContentCaptureSessionId sessionId, + @NonNull ContentCaptureEventsRequest request) { + if (VERBOSE) Log.v(TAG, "onContentCaptureEventsRequest(id=" + sessionId + ")"); + } /** * Notifies the service of {@link SnapshotData snapshot data} associated with a session. @@ -212,10 +242,22 @@ public abstract class ContentCaptureService extends Service { * Destroys the content capture session. * * @param sessionId the id of the session to destroy - */ + * */ public void onDestroyContentCaptureSession(@NonNull ContentCaptureSessionId sessionId) { - if (VERBOSE) { - Log.v(TAG, "onDestroyContentCaptureSession(id=" + sessionId + ")"); + if (VERBOSE) Log.v(TAG, "onDestroyContentCaptureSession(id=" + sessionId + ")"); + } + + @Override + @CallSuper + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + final int size = mSessionsByUid.size(); + pw.print("Number sessions: "); pw.println(size); + if (size > 0) { + final String prefix = " "; + for (int i = 0; i < size; i++) { + pw.print(prefix); pw.print(mSessionsByUid.keyAt(i)); + pw.print(": uid="); pw.println(mSessionsByUid.valueAt(i)); + } } } @@ -223,13 +265,19 @@ public abstract class ContentCaptureService extends Service { // so we don't need to create a temporary InteractionSessionId for each event. private void handleOnCreateSession(@NonNull ContentCaptureContext context, - @NonNull String sessionId) { + @NonNull String sessionId, int uid, IResultReceiver clientReceiver) { + mSessionsByUid.put(sessionId, uid); onCreateContentCaptureSession(context, new ContentCaptureSessionId(sessionId)); + setClientState(clientReceiver, ContentCaptureSession.STATE_ACTIVE, + mClientInterface.asBinder()); } - private void handleOnContentCaptureEventsRequest(@NonNull String sessionId, - @NonNull ContentCaptureEventsRequest request) { - onContentCaptureEventsRequest(new ContentCaptureSessionId(sessionId), request); + private void handleSendEvents(@NonNull String sessionId, int uid, + @NonNull ParceledListSlice<ContentCaptureEvent> events) { + if (handleIsRightCallerFor(sessionId, uid)) { + onContentCaptureEventsRequest(new ContentCaptureSessionId(sessionId), + new ContentCaptureEventsRequest(events)); + } } private void handleOnActivitySnapshot(@NonNull String sessionId, @@ -237,7 +285,52 @@ public abstract class ContentCaptureService extends Service { onActivitySnapshot(new ContentCaptureSessionId(sessionId), snapshotData); } - private void handleOnDestroySession(@NonNull String sessionId) { + private void handleFinishSession(@NonNull String sessionId) { + mSessionsByUid.remove(sessionId); onDestroyContentCaptureSession(new ContentCaptureSessionId(sessionId)); } + + /** + * Checks if the given {@code uid} owns the session. + */ + private boolean handleIsRightCallerFor(@NonNull String sessionId, int uid) { + final Integer rightUid = mSessionsByUid.get(sessionId); + if (rightUid == null) { + if (VERBOSE) Log.v(TAG, "No session for " + sessionId); + // Just ignore, as the session could have finished + return false; + } + if (rightUid != uid) { + Log.e(TAG, "invalid call from UID " + uid + ": session " + sessionId + " belongs to " + + rightUid); + //TODO(b/111276913): log metrics as this could be a malicious app forging a sessionId + return false; + } + return true; + + } + + /** + * Sends the state of the {@link ContentCaptureManager} in the cleint app. + * + * @param clientReceiver receiver in the client app. + * @param binder handle to the {@code IContentCaptureDirectManager} object that resides in the + * service. + * @hide + */ + public static void setClientState(@NonNull IResultReceiver clientReceiver, + int sessionStatus, @Nullable IBinder binder) { + try { + final Bundle extras; + if (binder != null) { + extras = new Bundle(); + extras.putBinder(ActivityContentCaptureSession.EXTRA_BINDER, binder); + } else { + extras = null; + } + clientReceiver.send(sessionStatus, extras); + } catch (RemoteException e) { + Slog.w(TAG, "Error async reporting result to client: " + e); + } + } } diff --git a/core/java/android/service/contentcapture/IContentCaptureService.aidl b/core/java/android/service/contentcapture/IContentCaptureService.aidl index 8167be9e6838..20e8e99d4ce1 100644 --- a/core/java/android/service/contentcapture/IContentCaptureService.aidl +++ b/core/java/android/service/contentcapture/IContentCaptureService.aidl @@ -16,10 +16,11 @@ package android.service.contentcapture; -import android.service.contentcapture.ContentCaptureEventsRequest; import android.service.contentcapture.SnapshotData; import android.view.contentcapture.ContentCaptureContext; +import com.android.internal.os.IResultReceiver; + import java.util.List; /** @@ -28,11 +29,8 @@ import java.util.List; * @hide */ oneway interface IContentCaptureService { - - // Called when session is created (context not null) or destroyed (context null) - void onSessionLifecycle(in ContentCaptureContext context, String sessionId); - - void onContentCaptureEventsRequest(String sessionId, in ContentCaptureEventsRequest request); - + void onSessionStarted(in ContentCaptureContext context, String sessionId, int uid, + in IResultReceiver clientReceiver); + void onSessionFinished(String sessionId); void onActivitySnapshot(String sessionId, in SnapshotData snapshotData); } diff --git a/core/java/android/service/quicksettings/Tile.java b/core/java/android/service/quicksettings/Tile.java index 4b81a7260852..6b569cfa9722 100644 --- a/core/java/android/service/quicksettings/Tile.java +++ b/core/java/android/service/quicksettings/Tile.java @@ -15,6 +15,7 @@ */ package android.service.quicksettings; +import android.annotation.Nullable; import android.graphics.drawable.Icon; import android.os.IBinder; import android.os.Parcel; @@ -62,6 +63,7 @@ public final class Tile implements Parcelable { private IBinder mToken; private Icon mIcon; private CharSequence mLabel; + private CharSequence mSubtitle; private CharSequence mContentDescription; // Default to active until clients of the new API can update. private int mState = STATE_ACTIVE; @@ -152,6 +154,22 @@ public final class Tile implements Parcelable { } /** + * Gets the current subtitle for the tile. + */ + @Nullable + public CharSequence getSubtitle() { + return mSubtitle; + } + + /** + * Set the subtitle for the tile. Will be displayed as the secondary label. + * @param subtitle the subtitle to show. + */ + public void setSubtitle(@Nullable CharSequence subtitle) { + this.mSubtitle = subtitle; + } + + /** * Gets the current content description for the tile. */ public CharSequence getContentDescription() { @@ -195,6 +213,7 @@ public final class Tile implements Parcelable { } dest.writeInt(mState); TextUtils.writeToParcel(mLabel, dest, flags); + TextUtils.writeToParcel(mSubtitle, dest, flags); TextUtils.writeToParcel(mContentDescription, dest, flags); } @@ -206,6 +225,7 @@ public final class Tile implements Parcelable { } mState = source.readInt(); mLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); + mSubtitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); mContentDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); } diff --git a/core/java/android/view/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java index ada78532d63f..60eeeeaa77ff 100644 --- a/core/java/android/view/NotificationHeaderView.java +++ b/core/java/android/view/NotificationHeaderView.java @@ -201,22 +201,16 @@ public class NotificationHeaderView extends ViewGroup { int bottom = top + childHeight; int layoutLeft = left; int layoutRight = right; - if (child == mExpandButton && mShowExpandButtonAtEnd) { - layoutRight = end - mContentEndMargin; - end = layoutLeft = layoutRight - child.getMeasuredWidth(); - } - if (child == mProfileBadge) { - int paddingEnd = getPaddingEnd(); - if (mShowWorkBadgeAtEnd) { - paddingEnd = mContentEndMargin; + if ((child == mExpandButton && mShowExpandButtonAtEnd) + || child == mProfileBadge + || child == mAppOps) { + if (end == getMeasuredWidth()) { + layoutRight = end - mContentEndMargin; + } else { + layoutRight = end - params.getMarginEnd(); } - layoutRight = end - paddingEnd; - end = layoutLeft = layoutRight - child.getMeasuredWidth(); - } - if (child == mAppOps) { - int paddingEnd = mContentEndMargin; - layoutRight = end - paddingEnd; - end = layoutLeft = layoutRight - child.getMeasuredWidth(); + layoutLeft = layoutRight - child.getMeasuredWidth(); + end = layoutLeft - params.getMarginStart(); } if (getLayoutDirection() == LAYOUT_DIRECTION_RTL) { int ltrLeft = layoutLeft; diff --git a/core/java/android/view/TouchDelegate.java b/core/java/android/view/TouchDelegate.java index bef9f07afb5f..2ea95e90a8bf 100644 --- a/core/java/android/view/TouchDelegate.java +++ b/core/java/android/view/TouchDelegate.java @@ -157,6 +157,11 @@ public class TouchDelegate { * Forward hover events to the delegate view if the event is within the bounds * specified in the constructor and touch exploration is enabled. * + * <p>This method is provided for accessibility purposes so touch exploration, which is + * commonly used by screen readers, can properly place accessibility focus on views that + * use touch delegates. Therefore, touch exploration must be enabled for hover events + * to be dispatched through the delegate.</p> + * * @param event The hover event to forward * @return True if the event was consumed by the delegate, false otherwise. * diff --git a/core/java/android/view/contentcapture/ActivityContentCaptureSession.java b/core/java/android/view/contentcapture/ActivityContentCaptureSession.java new file mode 100644 index 000000000000..7886518b9afc --- /dev/null +++ b/core/java/android/view/contentcapture/ActivityContentCaptureSession.java @@ -0,0 +1,471 @@ +/* + * 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.view.contentcapture; + +import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_APPEARED; +import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_DISAPPEARED; +import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED; +import static android.view.contentcapture.ContentCaptureManager.DEBUG; +import static android.view.contentcapture.ContentCaptureManager.VERBOSE; + +import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.ParceledListSlice; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.IBinder.DeathRecipient; +import android.os.RemoteException; +import android.os.SystemClock; +import android.util.Log; +import android.util.TimeUtils; +import android.view.autofill.AutofillId; +import android.view.contentcapture.ViewNode.ViewStructureImpl; + +import com.android.internal.os.IResultReceiver; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Main session associated with a context. + * + * <p>This session is created when the activity starts and finished when it stops; clients can use + * it to create children activities. + * + * <p><b>NOTE: all methods in this class should return right away, or do the real work in a handler + * thread. Hence, the only field that must be thread-safe is {@code mEnabled}, which is called at + * the beginning of every method. + * + * @hide + */ +public final class ActivityContentCaptureSession extends ContentCaptureSession { + + /** + * Handler message used to flush the buffer. + */ + private static final int MSG_FLUSH = 1; + + /** + * Maximum number of events that are buffered before sent to the app. + */ + // TODO(b/121044064): use settings + private static final int MAX_BUFFER_SIZE = 100; + + /** + * Frequency the buffer is flushed if stale. + */ + // TODO(b/121044064): use settings + private static final int FLUSHING_FREQUENCY_MS = 5_000; + + /** + * Name of the {@link IResultReceiver} extra used to pass the binder interface to the service. + * @hide + */ + public static final String EXTRA_BINDER = "binder"; + + @NonNull + private final AtomicBoolean mDisabled; + + @NonNull + private final Context mContext; + + @NonNull + private final Handler mHandler; + + /** + * Interface to the system_server binder object - it's only used to start the session (and + * notify when the session is finished). + */ + @Nullable + private final IContentCaptureManager mSystemServerInterface; + + /** + * Direct interface to the service binder object - it's used to send the events, including the + * last ones (when the session is finished) + */ + @Nullable + private IContentCaptureDirectManager mDirectServiceInterface; + @Nullable + private DeathRecipient mDirectServiceVulture; + + private int mState = STATE_UNKNOWN; + + @Nullable + private IBinder mApplicationToken; + + @Nullable + private ComponentName mComponentName; + + /** + * List of events held to be sent as a batch. + */ + @Nullable + private ArrayList<ContentCaptureEvent> mEvents; + + // Used just for debugging purposes (on dump) + private long mNextFlush; + + // Lazily created on demand. + private ContentCaptureSessionId mContentCaptureSessionId; + + /** + * @hide */ + protected ActivityContentCaptureSession(@NonNull Context context, @NonNull Handler handler, + @Nullable IContentCaptureManager systemServerInterface, @NonNull AtomicBoolean disabled, + @Nullable ContentCaptureContext clientContext) { + super(clientContext); + mContext = context; + mHandler = handler; + mSystemServerInterface = systemServerInterface; + mDisabled = disabled; + } + + /** + * Starts this session. + * + * @hide + */ + void start(@NonNull IBinder applicationToken, @NonNull ComponentName activityComponent) { + if (!isContentCaptureEnabled()) return; + + if (VERBOSE) { + Log.v(mTag, "start(): token=" + applicationToken + ", comp=" + + ComponentName.flattenToShortString(activityComponent)); + } + + mHandler.sendMessage(obtainMessage(ActivityContentCaptureSession::handleStartSession, this, + applicationToken, activityComponent)); + } + + @Override + void flush() { + mHandler.sendMessage(obtainMessage(ActivityContentCaptureSession::handleForceFlush, this)); + } + + @Override + void onDestroy() { + mHandler.sendMessage( + obtainMessage(ActivityContentCaptureSession::handleDestroySession, this)); + } + + private void handleStartSession(@NonNull IBinder token, @NonNull ComponentName componentName) { + if (mState != STATE_UNKNOWN) { + // TODO(b/111276913): revisit this scenario + Log.w(mTag, "ignoring handleStartSession(" + token + ") while on state " + + getStateAsString(mState)); + return; + } + mState = STATE_WAITING_FOR_SERVER; + mApplicationToken = token; + mComponentName = componentName; + + if (VERBOSE) { + Log.v(mTag, "handleStartSession(): token=" + token + ", act=" + + getActivityDebugName() + ", id=" + mId); + } + final int flags = 0; // TODO(b/111276913): get proper flags + + try { + mSystemServerInterface.startSession(mContext.getUserId(), mApplicationToken, + componentName, mId, mClientContext, flags, new IResultReceiver.Stub() { + @Override + public void send(int resultCode, Bundle resultData) { + IBinder binder = null; + if (resultData != null) { + binder = resultData.getBinder(EXTRA_BINDER); + if (binder == null) { + Log.wtf(mTag, "No " + EXTRA_BINDER + " extra result"); + handleResetState(); + return; + } + } + handleSessionStarted(resultCode, binder); + } + }); + } catch (RemoteException e) { + Log.w(mTag, "Error starting session for " + componentName.flattenToShortString() + ": " + + e); + } + } + + /** + * Callback from {@code system_server} after call to + * {@link IContentCaptureManager#startSession(int, IBinder, ComponentName, String, + * ContentCaptureContext, int, IResultReceiver)}. + * + * @param resultCode session state + * @param binder handle to {@code IContentCaptureDirectManager} + */ + private void handleSessionStarted(int resultCode, @Nullable IBinder binder) { + mState = resultCode; + if (binder != null) { + mDirectServiceInterface = IContentCaptureDirectManager.Stub.asInterface(binder); + mDirectServiceVulture = () -> { + Log.w(mTag, "Destroying session " + mId + " because service died"); + destroy(); + }; + try { + binder.linkToDeath(mDirectServiceVulture, 0); + } catch (RemoteException e) { + Log.w(mTag, "Failed to link to death on " + binder + ": " + e); + } + } + if (resultCode == STATE_DISABLED || resultCode == STATE_DISABLED_DUPLICATED_ID) { + mDisabled.set(true); + handleResetSession(/* resetState= */ false); + } else { + mDisabled.set(false); + } + if (VERBOSE) { + Log.v(mTag, "handleSessionStarted() result: code=" + resultCode + ", id=" + mId + + ", state=" + getStateAsString(mState) + ", disabled=" + mDisabled.get() + + ", binder=" + binder); + } + } + + private void handleSendEvent(@NonNull ContentCaptureEvent event, boolean forceFlush) { + if (mEvents == null) { + if (VERBOSE) { + Log.v(mTag, "Creating buffer for " + MAX_BUFFER_SIZE + " events"); + } + mEvents = new ArrayList<>(MAX_BUFFER_SIZE); + } + mEvents.add(event); + + final int numberEvents = mEvents.size(); + + // TODO(b/120784831): need to optimize it so we buffer changes until a number of X are + // buffered (either total or per autofillid). For + // example, if the user typed "a", "b", "c" and the threshold is 3, we should buffer + // "a" and "b" then send "abc". + final boolean bufferEvent = numberEvents < MAX_BUFFER_SIZE; + + if (bufferEvent && !forceFlush) { + handleScheduleFlush(/* checkExisting= */ true); + return; + } + + if (mState != STATE_ACTIVE) { + // Callback from startSession hasn't been called yet - typically happens on system + // apps that are started before the system service + // TODO(b/111276913): try to ignore session while system is not ready / boot + // not complete instead. Similarly, the manager service should return right away + // when the user does not have a service set + if (VERBOSE) { + Log.v(mTag, "Closing session for " + getActivityDebugName() + + " after " + numberEvents + " delayed events and state " + + getStateAsString(mState)); + } + handleResetState(); + // TODO(b/111276913): blacklist activity / use special flag to indicate that + // when it's launched again + return; + } + + handleForceFlush(); + } + + private void handleScheduleFlush(boolean checkExisting) { + if (checkExisting && mHandler.hasMessages(MSG_FLUSH)) { + // "Renew" the flush message by removing the previous one + mHandler.removeMessages(MSG_FLUSH); + } + mNextFlush = SystemClock.elapsedRealtime() + FLUSHING_FREQUENCY_MS; + if (VERBOSE) { + Log.v(mTag, "Scheduled to flush in " + FLUSHING_FREQUENCY_MS + "ms: " + mNextFlush); + } + mHandler.sendMessageDelayed( + obtainMessage(ActivityContentCaptureSession::handleFlushIfNeeded, this) + .setWhat(MSG_FLUSH), FLUSHING_FREQUENCY_MS); + } + + private void handleFlushIfNeeded() { + if (mEvents.isEmpty()) { + if (VERBOSE) Log.v(mTag, "Nothing to flush"); + return; + } + handleForceFlush(); + } + + private void handleForceFlush() { + if (mEvents == null) return; + + if (mDirectServiceInterface == null) { + Log.w(mTag, "handleForceFlush(): client not available yet"); + if (!mHandler.hasMessages(MSG_FLUSH)) { + handleScheduleFlush(/* checkExisting= */ false); + } + return; + } + + final int numberEvents = mEvents.size(); + try { + if (DEBUG) { + Log.d(mTag, "Flushing " + numberEvents + " event(s) for " + getActivityDebugName()); + } + mHandler.removeMessages(MSG_FLUSH); + + final ParceledListSlice<ContentCaptureEvent> events = handleClearEvents(); + mDirectServiceInterface.sendEvents(mId, events); + } catch (RemoteException e) { + Log.w(mTag, "Error sending " + numberEvents + " for " + getActivityDebugName() + + ": " + e); + } + } + + /** + * Resets the buffer and return a {@link ParceledListSlice} with the previous events. + */ + @NonNull + private ParceledListSlice<ContentCaptureEvent> handleClearEvents() { + // NOTE: we must save a reference to the current mEvents and then set it to to null, + // otherwise clearing it would clear it in the receiving side if the service is also local. + final List<ContentCaptureEvent> events = mEvents == null + ? Collections.emptyList() + : mEvents; + mEvents = null; + return new ParceledListSlice<>(events); + } + + private void handleDestroySession() { + if (DEBUG) { + Log.d(mTag, "Destroying session (ctx=" + mContext + ", id=" + mId + ") with " + + (mEvents == null ? 0 : mEvents.size()) + " event(s) for " + + getActivityDebugName()); + } + + try { + mSystemServerInterface.finishSession(mContext.getUserId(), mId); + } catch (RemoteException e) { + Log.e(mTag, "Error destroying system-service session " + mId + " for " + + getActivityDebugName() + ": " + e); + } + } + + private void handleResetState() { + handleResetSession(/* resetState= */ true); + } + + // TODO(b/121042846): once we support multiple sessions, we might need to move some of these + // clearings out. + private void handleResetSession(boolean resetState) { + if (resetState) { + mState = STATE_UNKNOWN; + } + mContentCaptureSessionId = null; + mApplicationToken = null; + mComponentName = null; + mEvents = null; + if (mDirectServiceInterface != null) { + mDirectServiceInterface.asBinder().unlinkToDeath(mDirectServiceVulture, 0); + } + mDirectServiceInterface = null; + mHandler.removeMessages(MSG_FLUSH); + } + + @Override + void internalNotifyViewAppeared(@NonNull ViewStructureImpl node) { + mHandler.sendMessage(obtainMessage(ActivityContentCaptureSession::handleSendEvent, this, + new ContentCaptureEvent(TYPE_VIEW_APPEARED) + .setViewNode(node.mNode), /* forceFlush= */ false)); + } + + @Override + void internalNotifyViewDisappeared(@NonNull AutofillId id) { + mHandler.sendMessage(obtainMessage(ActivityContentCaptureSession::handleSendEvent, this, + new ContentCaptureEvent(TYPE_VIEW_DISAPPEARED).setAutofillId(id), + /* forceFlush= */ false)); + } + + @Override + void internalNotifyViewTextChanged(@NonNull AutofillId id, @Nullable CharSequence text, + int flags) { + mHandler.sendMessage(obtainMessage(ActivityContentCaptureSession::handleSendEvent, this, + new ContentCaptureEvent(TYPE_VIEW_TEXT_CHANGED, flags).setAutofillId(id) + .setText(text), /* forceFlush= */ false)); + } + + @Override + boolean isContentCaptureEnabled() { + return mSystemServerInterface != null && !mDisabled.get(); + } + + @Override + void dump(@NonNull String prefix, @NonNull PrintWriter pw) { + pw.print(prefix); pw.print("id: "); pw.println(mId); + pw.print(prefix); pw.print("mContext: "); pw.println(mContext); + pw.print(prefix); pw.print("user: "); pw.println(mContext.getUserId()); + if (mSystemServerInterface != null) { + pw.print(prefix); pw.print("mSystemServerInterface: "); + pw.println(mSystemServerInterface); + } + if (mDirectServiceInterface != null) { + pw.print(prefix); pw.print("mDirectServiceInterface: "); + pw.println(mDirectServiceInterface); + } + if (mClientContext != null) { + // NOTE: we don't dump clientContent because it could have PII + pw.print(prefix); pw.println("hasClientContext"); + + } + pw.print(prefix); pw.print("mDisabled: "); pw.println(mDisabled.get()); + pw.print(prefix); pw.print("isEnabled(): "); pw.println(isContentCaptureEnabled()); + if (mContentCaptureSessionId != null) { + pw.print(prefix); pw.print("public id: "); pw.println(mContentCaptureSessionId); + } + pw.print(prefix); pw.print("state: "); pw.print(mState); pw.print(" ("); + pw.print(getStateAsString(mState)); pw.println(")"); + if (mApplicationToken != null) { + pw.print(prefix); pw.print("app token: "); pw.println(mApplicationToken); + } + if (mComponentName != null) { + pw.print(prefix); pw.print("component name: "); + pw.println(mComponentName.flattenToShortString()); + } + if (mEvents != null && !mEvents.isEmpty()) { + final int numberEvents = mEvents.size(); + pw.print(prefix); pw.print("buffered events: "); pw.print(numberEvents); + pw.print('/'); pw.println(MAX_BUFFER_SIZE); + if (VERBOSE && numberEvents > 0) { + final String prefix3 = prefix + " "; + for (int i = 0; i < numberEvents; i++) { + final ContentCaptureEvent event = mEvents.get(i); + pw.print(prefix3); pw.print(i); pw.print(": "); event.dump(pw); + pw.println(); + } + } + pw.print(prefix); pw.print("flush frequency: "); pw.println(FLUSHING_FREQUENCY_MS); + pw.print(prefix); pw.print("next flush: "); + TimeUtils.formatDuration(mNextFlush - SystemClock.elapsedRealtime(), pw); pw.println(); + } + } + + /** + * Gets a string that can be used to identify the activity on logging statements. + */ + private String getActivityDebugName() { + return mComponentName == null ? mContext.getPackageName() + : mComponentName.flattenToShortString(); + } +} diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java index 7fbbfb775397..fca2857d6e06 100644 --- a/core/java/android/view/contentcapture/ContentCaptureManager.java +++ b/core/java/android/view/contentcapture/ContentCaptureManager.java @@ -66,7 +66,7 @@ public final class ContentCaptureManager { @NonNull private final Handler mHandler; - private ContentCaptureSession mMainSession; + private ActivityContentCaptureSession mMainSession; /** @hide */ public ContentCaptureManager(@NonNull Context context, @@ -110,7 +110,7 @@ public final class ContentCaptureManager { // 4.Close (and delete) these sessions when onActivityStopped() is called. // 5.Figure out whether each session will have its own mDisabled AtomicBoolean. if (mMainSession == null) { - mMainSession = new ContentCaptureSession(mContext, mHandler, mService, + mMainSession = new ActivityContentCaptureSession(mContext, mHandler, mService, mDisabled, Preconditions.checkNotNull(context)); } else { throw new IllegalStateException("Manager already has a session: " + mMainSession); @@ -127,12 +127,12 @@ public final class ContentCaptureManager { * @hide */ @NonNull - public ContentCaptureSession getMainContentCaptureSession() { + public ActivityContentCaptureSession getMainContentCaptureSession() { // TODO(b/121033016): figure out how to manage the "default" session when it support // multiple sessions (can't just be the first one, as it could be closed). if (mMainSession == null) { - mMainSession = new ContentCaptureSession(mContext, mHandler, mService, mDisabled, - /* contentCaptureContext= */ null); + mMainSession = new ActivityContentCaptureSession(mContext, mHandler, mService, + mDisabled, /* clientContext= */ null); if (VERBOSE) { Log.v(TAG, "getDefaultContentCaptureSession(): created " + mMainSession); } diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java index 632955d335cf..aedb7a94ff5d 100644 --- a/core/java/android/view/contentcapture/ContentCaptureSession.java +++ b/core/java/android/view/contentcapture/ContentCaptureSession.java @@ -15,58 +15,32 @@ */ package android.view.contentcapture; -import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_APPEARED; -import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_DISAPPEARED; -import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED; -import static android.view.contentcapture.ContentCaptureManager.DEBUG; import static android.view.contentcapture.ContentCaptureManager.VERBOSE; -import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; - import android.annotation.NonNull; import android.annotation.Nullable; -import android.content.ComponentName; -import android.content.Context; -import android.os.Bundle; -import android.os.Handler; -import android.os.IBinder; -import android.os.RemoteException; -import android.os.SystemClock; import android.util.Log; -import android.util.TimeUtils; import android.view.View; import android.view.ViewStructure; import android.view.autofill.AutofillId; +import android.view.contentcapture.ViewNode.ViewStructureImpl; -import com.android.internal.os.IResultReceiver; import com.android.internal.util.Preconditions; import dalvik.system.CloseGuard; import java.io.PrintWriter; -import java.util.ArrayList; import java.util.UUID; -import java.util.concurrent.atomic.AtomicBoolean; /** * Session used to notify a system-provided Content Capture service about events associated with * views. */ -public final class ContentCaptureSession implements AutoCloseable { - - /* - * IMPLEMENTATION NOTICE: - * - * All methods in this class should return right away, or do the real work in a handler thread. - * - * Hence, the only field that must be thread-safe is mEnabled, which is called at the - * beginning of every method. - */ - - private static final String TAG = ContentCaptureSession.class.getSimpleName(); +public abstract class ContentCaptureSession implements AutoCloseable { /** * Used on {@link #notifyViewTextChanged(AutofillId, CharSequence, int)} to indicate that the + * * thext change was caused by user input (for example, through IME). */ public static final int FLAG_USER_INPUT = 0x1; @@ -100,58 +74,23 @@ public final class ContentCaptureSession implements AutoCloseable { public static final int STATE_DISABLED = 3; /** - * Handler message used to flush the buffer. - */ - private static final int MSG_FLUSH = 1; - - /** - * Maximum number of events that are buffered before sent to the app. + * Session is disabled because its id already existed on server. + * + * @hide */ - // TODO(b/121044064): use settings - private static final int MAX_BUFFER_SIZE = 100; + public static final int STATE_DISABLED_DUPLICATED_ID = 4; - /** - * Frequency the buffer is flushed if stale. - */ - // TODO(b/121044064): use settings - private static final int FLUSHING_FREQUENCY_MS = 5_000; + /** @hide */ + protected final String mTag = getClass().getSimpleName(); private final CloseGuard mCloseGuard = CloseGuard.get(); - @NonNull - private final AtomicBoolean mDisabled; - - @NonNull - private final Context mContext; - - @NonNull - private final Handler mHandler; - - @Nullable - private final IContentCaptureManager mService; - + /** @hide */ @Nullable - private final String mId = UUID.randomUUID().toString(); + protected final String mId = UUID.randomUUID().toString(); private int mState = STATE_UNKNOWN; - @Nullable - private IBinder mApplicationToken; - - @Nullable - private ComponentName mComponentName; - - /** - * List of events held to be sent as a batch. - */ - // TODO(b/111276913): once we support multiple sessions, we need to move the buffer of events - // to its own class so it's shared by all sessions - @Nullable - private ArrayList<ContentCaptureEvent> mEvents; - - // Used just for debugging purposes (on dump) - private long mNextFlush; - // Lazily created on demand. private ContentCaptureSessionId mContentCaptureSessionId; @@ -159,18 +98,15 @@ public final class ContentCaptureSession implements AutoCloseable { * {@link ContentCaptureContext} set by client, or {@code null} when it's the * {@link ContentCaptureManager#getMainContentCaptureSession() default session} for the * context. + * + * @hide */ @Nullable - private final ContentCaptureContext mClientContext; + // TODO(b/121042846): move to ChildContentCaptureSession.java + protected final ContentCaptureContext mClientContext; /** @hide */ - protected ContentCaptureSession(@NonNull Context context, @NonNull Handler handler, - @Nullable IContentCaptureManager service, @NonNull AtomicBoolean disabled, - @Nullable ContentCaptureContext clientContext) { - mContext = context; - mHandler = handler; - mService = service; - mDisabled = disabled; + protected ContentCaptureSession(@Nullable ContentCaptureContext clientContext) { mClientContext = clientContext; mCloseGuard.open("destroy"); } @@ -178,7 +114,7 @@ public final class ContentCaptureSession implements AutoCloseable { /** * Gets the id used to identify this session. */ - public ContentCaptureSessionId getContentCaptureSessionId() { + public final ContentCaptureSessionId getContentCaptureSessionId() { if (mContentCaptureSessionId == null) { mContentCaptureSessionId = new ContentCaptureSessionId(mId); } @@ -186,37 +122,16 @@ public final class ContentCaptureSession implements AutoCloseable { } /** - * Starts this session. - * - * @hide - */ - void start(@NonNull IBinder applicationToken, @NonNull ComponentName activityComponent) { - if (!isContentCaptureEnabled()) return; - - if (VERBOSE) { - Log.v(TAG, "start(): token=" + applicationToken + ", comp=" - + ComponentName.flattenToShortString(activityComponent)); - } - - mHandler.sendMessage(obtainMessage(ContentCaptureSession::handleStartSession, this, - applicationToken, activityComponent)); - } - - /** * Flushes the buffered events to the service. - * - * @hide */ - void flush() { - mHandler.sendMessage(obtainMessage(ContentCaptureSession::handleForceFlush, this)); - } + abstract void flush(); /** * Destroys this session, flushing out all pending notifications to the service. * * <p>Once destroyed, any new notification will be dropped. */ - public void destroy() { + public final void destroy() { //TODO(b/111276913): mark it as destroyed so other methods are ignored (and test on CTS) if (!isContentCaptureEnabled()) return; @@ -224,13 +139,19 @@ public final class ContentCaptureSession implements AutoCloseable { //TODO(b/111276913): check state (for example, how to handle if it's waiting for remote // id) and send it to the cache of batched commands if (VERBOSE) { - Log.v(TAG, "destroy(): state=" + getStateAsString(mState) + ", mId=" + mId); + Log.v(mTag, "destroy(): state=" + getStateAsString(mState) + ", mId=" + mId); } - mHandler.sendMessage(obtainMessage(ContentCaptureSession::handleDestroySession, this)); + flush(); + + onDestroy(); + mCloseGuard.close(); } + abstract void onDestroy(); + + /** @hide */ @Override public void close() { @@ -249,162 +170,6 @@ public final class ContentCaptureSession implements AutoCloseable { } } - private void handleStartSession(@NonNull IBinder token, @NonNull ComponentName componentName) { - if (mState != STATE_UNKNOWN) { - // TODO(b/111276913): revisit this scenario - Log.w(TAG, "ignoring handleStartSession(" + token + ") while on state " - + getStateAsString(mState)); - return; - } - mState = STATE_WAITING_FOR_SERVER; - mApplicationToken = token; - mComponentName = componentName; - - if (VERBOSE) { - Log.v(TAG, "handleStartSession(): token=" + token + ", act=" - + getActivityDebugName() + ", id=" + mId); - } - final int flags = 0; // TODO(b/111276913): get proper flags - - try { - mService.startSession(mContext.getUserId(), mApplicationToken, componentName, - mId, mClientContext, flags, new IResultReceiver.Stub() { - @Override - public void send(int resultCode, Bundle resultData) { - handleSessionStarted(resultCode); - } - }); - } catch (RemoteException e) { - Log.w(TAG, "Error starting session for " + componentName.flattenToShortString() + ": " - + e); - } - } - - private void handleSessionStarted(int resultCode) { - mState = resultCode; - mDisabled.set(mState == STATE_DISABLED); - if (VERBOSE) { - Log.v(TAG, "handleSessionStarted() result: code=" + resultCode + ", id=" + mId - + ", state=" + getStateAsString(mState) + ", disabled=" + mDisabled.get()); - } - } - - private void handleSendEvent(@NonNull ContentCaptureEvent event, boolean forceFlush) { - if (mEvents == null) { - if (VERBOSE) { - Log.v(TAG, "Creating buffer for " + MAX_BUFFER_SIZE + " events"); - } - mEvents = new ArrayList<>(MAX_BUFFER_SIZE); - } - mEvents.add(event); - - final int numberEvents = mEvents.size(); - - // TODO(b/120784831): need to optimize it so we buffer changes until a number of X are - // buffered (either total or per autofillid). For - // example, if the user typed "a", "b", "c" and the threshold is 3, we should buffer - // "a" and "b" then send "abc". - final boolean bufferEvent = numberEvents < MAX_BUFFER_SIZE; - - if (bufferEvent && !forceFlush) { - handleScheduleFlush(); - return; - } - - if (mState != STATE_ACTIVE) { - // Callback from startSession hasn't been called yet - typically happens on system - // apps that are started before the system service - // TODO(b/111276913): try to ignore session while system is not ready / boot - // not complete instead. Similarly, the manager service should return right away - // when the user does not have a service set - if (VERBOSE) { - Log.v(TAG, "Closing session for " + getActivityDebugName() - + " after " + numberEvents + " delayed events and state " - + getStateAsString(mState)); - } - handleResetState(); - // TODO(b/111276913): blacklist activity / use special flag to indicate that - // when it's launched again - return; - } - - handleForceFlush(); - } - - private void handleScheduleFlush() { - if (mHandler.hasMessages(MSG_FLUSH)) { - // "Renew" the flush message by removing the previous one - mHandler.removeMessages(MSG_FLUSH); - } - mNextFlush = SystemClock.elapsedRealtime() + FLUSHING_FREQUENCY_MS; - if (VERBOSE) { - Log.v(TAG, "Scheduled to flush in " + FLUSHING_FREQUENCY_MS + "ms: " + mNextFlush); - } - mHandler.sendMessageDelayed( - obtainMessage(ContentCaptureSession::handleFlushIfNeeded, this).setWhat(MSG_FLUSH), - FLUSHING_FREQUENCY_MS); - } - - private void handleFlushIfNeeded() { - if (mEvents.isEmpty()) { - if (VERBOSE) Log.v(TAG, "Nothing to flush"); - return; - } - handleForceFlush(); - } - - private void handleForceFlush() { - if (mEvents == null) return; - - final int numberEvents = mEvents.size(); - try { - if (DEBUG) { - Log.d(TAG, "Flushing " + numberEvents + " event(s) for " + getActivityDebugName()); - } - mHandler.removeMessages(MSG_FLUSH); - mService.sendEvents(mContext.getUserId(), mId, mEvents); - // TODO(b/111276913): decide whether we should clear or set it to null, as each has - // its own advantages: clearing will save extra allocations while the session is - // active, while setting to null would save memory if there's no more event coming. - mEvents.clear(); - } catch (RemoteException e) { - Log.w(TAG, "Error sending " + numberEvents + " for " + getActivityDebugName() - + ": " + e); - } - } - - private void handleDestroySession() { - //TODO(b/111276913): right now both the ContentEvents and lifecycle sessions are sent - // to system_server, so it's ok to call both in sequence here. But once we split - // them so the events are sent directly to the service, we need to make sure they're - // sent in order. - try { - if (DEBUG) { - Log.d(TAG, "Destroying session (ctx=" + mContext + ", id=" + mId + ") with " - + (mEvents == null ? 0 : mEvents.size()) + " event(s) for " - + getActivityDebugName()); - } - - mService.finishSession(mContext.getUserId(), mId, mEvents); - } catch (RemoteException e) { - Log.e(TAG, "Error destroying session " + mId + " for " + getActivityDebugName() - + ": " + e); - } finally { - handleResetState(); - } - } - - // TODO(b/111276913): once we support multiple sessions, we might need to move some of these - // clearings out. - private void handleResetState() { - mState = STATE_UNKNOWN; - mContentCaptureSessionId = null; - mApplicationToken = null; - mComponentName = null; - mEvents = null; - mHandler.removeMessages(MSG_FLUSH); - } - /** * Notifies the Content Capture Service that a node has been added to the view structure. * @@ -414,7 +179,7 @@ public final class ContentCaptureSession implements AutoCloseable { * * @param node node that has been added. */ - public void notifyViewAppeared(@NonNull ViewStructure node) { + public final void notifyViewAppeared(@NonNull ViewStructure node) { Preconditions.checkNotNull(node); if (!isContentCaptureEnabled()) return; @@ -422,12 +187,11 @@ public final class ContentCaptureSession implements AutoCloseable { throw new IllegalArgumentException("Invalid node class: " + node.getClass()); } - mHandler.sendMessage(obtainMessage(ContentCaptureSession::handleSendEvent, this, - new ContentCaptureEvent(TYPE_VIEW_APPEARED) - .setViewNode(((ViewNode.ViewStructureImpl) node).mNode), - /* forceFlush= */ false)); + internalNotifyViewAppeared((ViewStructureImpl) node); } + abstract void internalNotifyViewAppeared(@NonNull ViewNode.ViewStructureImpl node); + /** * Notifies the Content Capture Service that a node has been removed from the view structure. * @@ -436,15 +200,15 @@ public final class ContentCaptureSession implements AutoCloseable { * * @param id id of the node that has been removed. */ - public void notifyViewDisappeared(@NonNull AutofillId id) { + public final void notifyViewDisappeared(@NonNull AutofillId id) { Preconditions.checkNotNull(id); if (!isContentCaptureEnabled()) return; - mHandler.sendMessage(obtainMessage(ContentCaptureSession::handleSendEvent, this, - new ContentCaptureEvent(TYPE_VIEW_DISAPPEARED).setAutofillId(id), - /* forceFlush= */ false)); + internalNotifyViewDisappeared(id); } + abstract void internalNotifyViewDisappeared(@NonNull AutofillId id); + /** * Notifies the Intelligence Service that the value of a text node has been changed. * @@ -453,24 +217,25 @@ public final class ContentCaptureSession implements AutoCloseable { * @param flags either {@code 0} or {@link #FLAG_USER_INPUT} when the value was explicitly * changed by the user (for example, through the keyboard). */ - public void notifyViewTextChanged(@NonNull AutofillId id, @Nullable CharSequence text, + public final void notifyViewTextChanged(@NonNull AutofillId id, @Nullable CharSequence text, int flags) { Preconditions.checkNotNull(id); if (!isContentCaptureEnabled()) return; - mHandler.sendMessage(obtainMessage(ContentCaptureSession::handleSendEvent, this, - new ContentCaptureEvent(TYPE_VIEW_TEXT_CHANGED, flags).setAutofillId(id) - .setText(text), /* forceFlush= */ false)); + internalNotifyViewTextChanged(id, text, flags); } + abstract void internalNotifyViewTextChanged(@NonNull AutofillId id, @Nullable CharSequence text, + int flags); + /** * Creates a {@link ViewStructure} for a "standard" view. * * @hide */ @NonNull - public ViewStructure newViewStructure(@NonNull View view) { + public final ViewStructure newViewStructure(@NonNull View view) { return new ViewNode.ViewStructureImpl(view); } @@ -487,73 +252,25 @@ public final class ContentCaptureSession implements AutoCloseable { * @hide */ @NonNull - public ViewStructure newVirtualViewStructure(@NonNull AutofillId parentId, int virtualId) { + public final ViewStructure newVirtualViewStructure(@NonNull AutofillId parentId, + int virtualId) { return new ViewNode.ViewStructureImpl(parentId, virtualId); } - private boolean isContentCaptureEnabled() { - return mService != null && !mDisabled.get(); - } - - void dump(@NonNull String prefix, @NonNull PrintWriter pw) { - pw.print(prefix); pw.print("id: "); pw.println(mId); - pw.print(prefix); pw.print("mContext: "); pw.println(mContext); - pw.print(prefix); pw.print("user: "); pw.println(mContext.getUserId()); - if (mService != null) { - pw.print(prefix); pw.print("mService: "); pw.println(mService); - } - if (mClientContext != null) { - // NOTE: we don't dump clientContent because it could have PII - pw.print(prefix); pw.println("hasClientContext"); + abstract boolean isContentCaptureEnabled(); - } - pw.print(prefix); pw.print("mDisabled: "); pw.println(mDisabled.get()); - pw.print(prefix); pw.print("isEnabled(): "); pw.println(isContentCaptureEnabled()); - if (mContentCaptureSessionId != null) { - pw.print(prefix); pw.print("public id: "); pw.println(mContentCaptureSessionId); - } - pw.print(prefix); pw.print("state: "); pw.print(mState); pw.print(" ("); - pw.print(getStateAsString(mState)); pw.println(")"); - if (mApplicationToken != null) { - pw.print(prefix); pw.print("app token: "); pw.println(mApplicationToken); - } - if (mComponentName != null) { - pw.print(prefix); pw.print("component name: "); - pw.println(mComponentName.flattenToShortString()); - } - if (mEvents != null && !mEvents.isEmpty()) { - final int numberEvents = mEvents.size(); - pw.print(prefix); pw.print("buffered events: "); pw.print(numberEvents); - pw.print('/'); pw.println(MAX_BUFFER_SIZE); - if (VERBOSE && numberEvents > 0) { - final String prefix3 = prefix + " "; - for (int i = 0; i < numberEvents; i++) { - final ContentCaptureEvent event = mEvents.get(i); - pw.print(prefix3); pw.print(i); pw.print(": "); event.dump(pw); - pw.println(); - } - } - pw.print(prefix); pw.print("flush frequency: "); pw.println(FLUSHING_FREQUENCY_MS); - pw.print(prefix); pw.print("next flush: "); - TimeUtils.formatDuration(mNextFlush - SystemClock.elapsedRealtime(), pw); pw.println(); - } - } - - /** - * Gets a string that can be used to identify the activity on logging statements. - */ - private String getActivityDebugName() { - return mComponentName == null ? mContext.getPackageName() - : mComponentName.flattenToShortString(); - } + abstract void dump(@NonNull String prefix, @NonNull PrintWriter pw); @Override public String toString() { return mId; } + /** + * @hide + */ @NonNull - private static String getStateAsString(int state) { + protected static String getStateAsString(int state) { switch (state) { case STATE_UNKNOWN: return "UNKNOWN"; @@ -563,6 +280,8 @@ public final class ContentCaptureSession implements AutoCloseable { return "ACTIVE"; case STATE_DISABLED: return "DISABLED"; + case STATE_DISABLED_DUPLICATED_ID: + return "DISABLED_DUPLICATED_ID"; default: return "INVALID:" + state; } diff --git a/core/java/android/view/contentcapture/IContentCaptureDirectManager.aidl b/core/java/android/view/contentcapture/IContentCaptureDirectManager.aidl new file mode 100644 index 000000000000..145fc16707a1 --- /dev/null +++ b/core/java/android/view/contentcapture/IContentCaptureDirectManager.aidl @@ -0,0 +1,30 @@ +/* + * 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.view.contentcapture; + +import android.content.pm.ParceledListSlice; +import android.view.contentcapture.ContentCaptureEvent; + +/** + * Interface between an app (ContentCaptureManager / ContentCaptureSession) and the app providing + * the ContentCaptureService implementation. + * + * @hide + */ +oneway interface IContentCaptureDirectManager { + void sendEvents(in String sessionId, in ParceledListSlice events); +} diff --git a/core/java/android/view/contentcapture/IContentCaptureManager.aidl b/core/java/android/view/contentcapture/IContentCaptureManager.aidl index 2002c5c1fc9a..cbd37017038e 100644 --- a/core/java/android/view/contentcapture/IContentCaptureManager.aidl +++ b/core/java/android/view/contentcapture/IContentCaptureManager.aidl @@ -26,12 +26,14 @@ import com.android.internal.os.IResultReceiver; import java.util.List; /** - * {@hide} - */ + * Interface between an app (ContentCaptureManager / ContentCaptureSession) and the system-server + * implementation service (ContentCaptureManagerService). + * + * @hide + */ oneway interface IContentCaptureManager { void startSession(int userId, IBinder activityToken, in ComponentName componentName, String sessionId, in ContentCaptureContext clientContext, int flags, in IResultReceiver result); - void finishSession(int userId, String sessionId, in List<ContentCaptureEvent> events); - void sendEvents(int userId, in String sessionId, in List<ContentCaptureEvent> events); + void finishSession(int userId, String sessionId); } diff --git a/core/java/com/android/internal/app/IAppOpsNotedCallback.aidl b/core/java/com/android/internal/app/IAppOpsNotedCallback.aidl new file mode 100644 index 000000000000..fa5c30a03e78 --- /dev/null +++ b/core/java/com/android/internal/app/IAppOpsNotedCallback.aidl @@ -0,0 +1,22 @@ +/* + * 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.internal.app; + +// Iterface to observe op note/checks of ops +oneway interface IAppOpsNotedCallback { + void opNoted(int op, int uid, String packageName, int mode); +} diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl index 049103bfebb2..e571656b9135 100644 --- a/core/java/com/android/internal/app/IAppOpsService.aidl +++ b/core/java/com/android/internal/app/IAppOpsService.aidl @@ -21,6 +21,7 @@ import android.content.pm.ParceledListSlice; import android.os.Bundle; import com.android.internal.app.IAppOpsCallback; 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 @@ -61,4 +62,7 @@ interface IAppOpsService { boolean isOperationActive(int code, int uid, String packageName); void startWatchingModeWithFlags(int op, String packageName, int flags, IAppOpsCallback callback); + + void startWatchingNoted(in int[] ops, IAppOpsNotedCallback callback); + void stopWatchingNoted(IAppOpsNotedCallback callback); } diff --git a/core/java/com/android/internal/app/procstats/AssociationState.java b/core/java/com/android/internal/app/procstats/AssociationState.java index 4da339165655..2df515835026 100644 --- a/core/java/com/android/internal/app/procstats/AssociationState.java +++ b/core/java/com/android/internal/app/procstats/AssociationState.java @@ -17,6 +17,7 @@ package com.android.internal.app.procstats; +import android.annotation.Nullable; import android.os.Parcel; import android.os.SystemClock; import android.os.UserHandle; @@ -192,9 +193,16 @@ public final class AssociationState { */ String mProcess; - SourceKey(int uid, String process) { + /** + * Optional package name, or null; consider this final. Not final just to avoid a + * temporary object during lookup. + */ + @Nullable String mPackage; + + SourceKey(int uid, String process, String pkg) { mUid = uid; mProcess = process; + mPackage = pkg; } public boolean equals(Object o) { @@ -202,12 +210,14 @@ public final class AssociationState { return false; } SourceKey s = (SourceKey) o; - return s.mUid == mUid && Objects.equals(s.mProcess, mProcess); + return s.mUid == mUid && Objects.equals(s.mProcess, mProcess) + && Objects.equals(s.mPackage, mPackage); } @Override public int hashCode() { - return Integer.hashCode(mUid) ^ (mProcess == null ? 0 : mProcess.hashCode()); + return Integer.hashCode(mUid) ^ (mProcess == null ? 0 : mProcess.hashCode()) + ^ (mPackage == null ? 0 : (mPackage.hashCode() * 33)); } @Override @@ -217,6 +227,8 @@ public final class AssociationState { UserHandle.formatUid(sb, mUid); sb.append(' '); sb.append(mProcess); + sb.append(' '); + sb.append(mPackage); sb.append('}'); return sb.toString(); } @@ -227,7 +239,7 @@ public final class AssociationState { */ private final ArrayMap<SourceKey, SourceState> mSources = new ArrayMap<>(); - private final SourceKey mTmpSourceKey = new SourceKey(0, null); + private final SourceKey mTmpSourceKey = new SourceKey(0, null, null); private ProcessState mProc; @@ -266,12 +278,13 @@ public final class AssociationState { mProc = proc; } - public SourceState startSource(int uid, String processName) { + public SourceState startSource(int uid, String processName, String packageName) { mTmpSourceKey.mUid = uid; mTmpSourceKey.mProcess = processName; + mTmpSourceKey.mPackage = packageName; SourceState src = mSources.get(mTmpSourceKey); if (src == null) { - SourceKey key = new SourceKey(uid, processName); + SourceKey key = new SourceKey(uid, processName, packageName); src = new SourceState(key); mSources.put(key, src); } @@ -379,6 +392,7 @@ public final class AssociationState { final SourceState src = mSources.valueAt(isrc); out.writeInt(key.mUid); stats.writeCommonString(out, key.mProcess); + stats.writeCommonString(out, key.mPackage); out.writeInt(src.mCount); out.writeLong(src.mDuration); out.writeInt(src.mActiveCount); @@ -405,7 +419,8 @@ public final class AssociationState { for (int isrc = 0; isrc < NSRC; isrc++) { final int uid = in.readInt(); final String procName = stats.readCommonString(in, parcelVersion); - final SourceKey key = new SourceKey(uid, procName); + final String pkgName = stats.readCommonString(in, parcelVersion); + final SourceKey key = new SourceKey(uid, procName, pkgName); final SourceState src = new SourceState(key); src.mCount = in.readInt(); src.mDuration = in.readLong(); @@ -445,10 +460,11 @@ public final class AssociationState { } } - public boolean hasProcess(String procName) { + public boolean hasProcessOrPackage(String procName) { final int NSRC = mSources.size(); for (int isrc = 0; isrc < NSRC; isrc++) { - if (mSources.keyAt(isrc).mProcess.equals(procName)) { + final SourceKey key = mSources.keyAt(isrc); + if (procName.equals(key.mProcess) || procName.equals(key.mPackage)) { return true; } } @@ -466,14 +482,20 @@ public final class AssociationState { for (int isrc = 0; isrc < NSRC; isrc++) { final SourceKey key = mSources.keyAt(isrc); final SourceState src = mSources.valueAt(isrc); - if (reqPackage != null && !reqPackage.equals(key.mProcess)) { + if (reqPackage != null && !reqPackage.equals(key.mProcess) + && !reqPackage.equals(key.mPackage)) { continue; } pw.print(prefixInner); pw.print("<- "); pw.print(key.mProcess); - pw.print(" / "); + pw.print("/"); UserHandle.formatUid(pw, key.mUid); + if (key.mPackage != null) { + pw.print(" ("); + pw.print(key.mPackage); + pw.print(")"); + } pw.println(":"); pw.print(prefixInner); pw.print(" Total count "); @@ -683,6 +705,7 @@ public final class AssociationState { final SourceState src = mSources.valueAt(isrc); final long sourceToken = proto.start(PackageAssociationProcessStatsProto.SOURCES); proto.write(PackageAssociationSourceProcessStatsProto.PROCESS_NAME, key.mProcess); + proto.write(PackageAssociationSourceProcessStatsProto.PACKAGE_NAME, key.mPackage); proto.write(PackageAssociationSourceProcessStatsProto.PROCESS_UID, key.mUid); proto.write(PackageAssociationSourceProcessStatsProto.TOTAL_COUNT, src.mCount); long duration = src.mDuration; diff --git a/core/java/com/android/internal/app/procstats/ProcessStats.java b/core/java/com/android/internal/app/procstats/ProcessStats.java index 9ee583a97b8d..9b9b77196a53 100644 --- a/core/java/com/android/internal/app/procstats/ProcessStats.java +++ b/core/java/com/android/internal/app/procstats/ProcessStats.java @@ -178,7 +178,7 @@ public final class ProcessStats implements Parcelable { {"proc", "pkg-proc", "pkg-svc", "pkg-asc", "pkg-all", "all"}; // Current version of the parcel format. - private static final int PARCEL_VERSION = 34; + private static final int PARCEL_VERSION = 35; // In-memory Parcel magic number, used to detect attempts to unmarshall bad data private static final int MAGIC = 0x50535454; @@ -1490,7 +1490,7 @@ public final class ProcessStats implements Parcelable { // package, so that if so we print those. for (int iasc = 0; iasc < NASCS; iasc++) { AssociationState asc = pkgState.mAssociations.valueAt(iasc); - if (asc.hasProcess(reqPackage)) { + if (asc.hasProcessOrPackage(reqPackage)) { onlyAssociations = true; break; } @@ -1591,7 +1591,7 @@ public final class ProcessStats implements Parcelable { for (int iasc = 0; iasc < NASCS; iasc++) { AssociationState asc = pkgState.mAssociations.valueAt(iasc); if (!pkgMatch && !reqPackage.equals(asc.getProcessName())) { - if (!onlyAssociations || !asc.hasProcess(reqPackage)) { + if (!onlyAssociations || !asc.hasProcessOrPackage(reqPackage)) { continue; } } diff --git a/services/core/java/com/android/server/infra/AbstractMultiplePendingRequestsRemoteService.java b/core/java/com/android/internal/infra/AbstractMultiplePendingRequestsRemoteService.java index aaea45e4adbf..26cf1809313e 100644 --- a/services/core/java/com/android/server/infra/AbstractMultiplePendingRequestsRemoteService.java +++ b/core/java/com/android/internal/infra/AbstractMultiplePendingRequestsRemoteService.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.infra; +package com.android.internal.infra; import android.annotation.NonNull; import android.content.ComponentName; diff --git a/services/core/java/com/android/server/infra/AbstractRemoteService.java b/core/java/com/android/internal/infra/AbstractRemoteService.java index 41dcf89a0b04..5ab258283785 100644 --- a/services/core/java/com/android/server/infra/AbstractRemoteService.java +++ b/core/java/com/android/internal/infra/AbstractRemoteService.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.infra; +package com.android.internal.infra; import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; @@ -27,13 +27,13 @@ import android.os.Handler; import android.os.IBinder; import android.os.IBinder.DeathRecipient; import android.os.IInterface; +import android.os.Looper; import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; import android.util.Slog; import com.android.internal.annotations.GuardedBy; -import com.android.server.FgThread; import java.io.PrintWriter; import java.lang.ref.WeakReference; @@ -109,7 +109,7 @@ public abstract class AbstractRemoteService<S extends AbstractRemoteService<S, I mComponentName = componentName; mIntent = new Intent(serviceInterface).setComponent(mComponentName); mUserId = userId; - mHandler = new Handler(FgThread.getHandler().getLooper()); + mHandler = new Handler(Looper.getMainLooper()); mBindInstantServiceAllowed = bindInstantServiceAllowed; } diff --git a/services/core/java/com/android/server/infra/AbstractSinglePendingRequestRemoteService.java b/core/java/com/android/internal/infra/AbstractSinglePendingRequestRemoteService.java index d32f13b5d71b..f0c223388137 100644 --- a/services/core/java/com/android/server/infra/AbstractSinglePendingRequestRemoteService.java +++ b/core/java/com/android/internal/infra/AbstractSinglePendingRequestRemoteService.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.infra; +package com.android.internal.infra; import android.annotation.NonNull; import android.content.ComponentName; diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index 8bdb000aad0e..c2c6ae6712ab 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -536,9 +536,11 @@ public class ZygoteInit { static ClassLoader createPathClassLoader(String classPath, int targetSdkVersion) { String libraryPath = System.getProperty("java.library.path"); + // We use the boot class loader, that's what the runtime expects at AOT. + ClassLoader parent = ClassLoader.getSystemClassLoader().getParent(); + return ClassLoaderFactory.createClassLoader(classPath, libraryPath, libraryPath, - ClassLoader.getSystemClassLoader(), targetSdkVersion, true /* isNamespaceShared */, - null /* classLoaderName */); + parent, targetSdkVersion, true /* isNamespaceShared */, null /* classLoaderName */); } /** diff --git a/core/java/com/android/internal/usb/DumpUtils.java b/core/java/com/android/internal/usb/DumpUtils.java index cac22652ebd7..240c2e757faf 100644 --- a/core/java/com/android/internal/usb/DumpUtils.java +++ b/core/java/com/android/internal/usb/DumpUtils.java @@ -16,12 +16,12 @@ package com.android.internal.usb; -import static android.hardware.usb.UsbPort.MODE_AUDIO_ACCESSORY; -import static android.hardware.usb.UsbPort.MODE_DEBUG_ACCESSORY; -import static android.hardware.usb.UsbPort.MODE_DFP; -import static android.hardware.usb.UsbPort.MODE_DUAL; -import static android.hardware.usb.UsbPort.MODE_NONE; -import static android.hardware.usb.UsbPort.MODE_UFP; +import static android.hardware.usb.UsbPortStatus.MODE_AUDIO_ACCESSORY; +import static android.hardware.usb.UsbPortStatus.MODE_DEBUG_ACCESSORY; +import static android.hardware.usb.UsbPortStatus.MODE_DFP; +import static android.hardware.usb.UsbPortStatus.MODE_DUAL; +import static android.hardware.usb.UsbPortStatus.MODE_NONE; +import static android.hardware.usb.UsbPortStatus.MODE_UFP; import static com.android.internal.util.dump.DumpUtils.writeStringIfNotNull; diff --git a/core/jni/Android.bp b/core/jni/Android.bp index 43f8d00bb5f1..21fa75eb35e0 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -274,6 +274,7 @@ cc_library_shared { "libicuuc", "libmedia", "libmediametrics", + "libmeminfo", "libaudioclient", "libjpeg", "libusbhost", diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp index c32de0a5737e..12ca78a7ce92 100755 --- a/core/jni/android/graphics/Bitmap.cpp +++ b/core/jni/android/graphics/Bitmap.cpp @@ -725,9 +725,10 @@ static jobject Bitmap_createFromParcel(JNIEnv* env, jobject, jobject parcel) { return NULL; } - // Map the pixels in place and take ownership of the ashmem region. - nativeBitmap = sk_sp<Bitmap>(GraphicsJNI::mapAshmemBitmap(env, bitmap.get(), - dupFd, const_cast<void*>(blob.data()), size, !isMutable)); + // Map the pixels in place and take ownership of the ashmem region. We must also respect the + // rowBytes value already set on the bitmap instead of attempting to compute our own. + nativeBitmap = Bitmap::createFrom(bitmap->info(), bitmap->rowBytes(), dupFd, + const_cast<void*>(blob.data()), size, !isMutable); if (!nativeBitmap) { close(dupFd); blob.release(); @@ -1097,21 +1098,20 @@ static jobject Bitmap_copyPreserveInternalConfig(JNIEnv* env, jobject, jlong bit SkBitmap src; hwuiBitmap.getSkBitmap(&src); - SkBitmap result; - HeapAllocator allocator; - if (!bitmapCopyTo(&result, hwuiBitmap.info().colorType(), src, &allocator)) { + if (src.pixelRef() == nullptr) { doThrowRE(env, "Could not copy a hardware bitmap."); return NULL; } - return createBitmap(env, allocator.getStorageObjAndReset(), getPremulBitmapCreateFlags(false)); + + sk_sp<Bitmap> bitmap = Bitmap::createFrom(src.info(), *src.pixelRef()); + return createBitmap(env, bitmap.release(), getPremulBitmapCreateFlags(false)); } static jobject Bitmap_createHardwareBitmap(JNIEnv* env, jobject, jobject graphicBuffer) { sp<GraphicBuffer> buffer(graphicBufferForJavaObject(env, graphicBuffer)); - // Bitmap::createFrom currently assumes SRGB color space for RGBA images. // To support any color space, we need to pass an additional ColorSpace argument to // java Bitmap.createHardwareBitmap. - sk_sp<Bitmap> bitmap = Bitmap::createFrom(buffer); + sk_sp<Bitmap> bitmap = Bitmap::createFrom(buffer, SkColorSpace::MakeSRGB()); if (!bitmap.get()) { ALOGW("failed to create hardware bitmap from graphic buffer"); return NULL; diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp index 26af15e79e2d..67d0c8aced61 100644 --- a/core/jni/android/graphics/Graphics.cpp +++ b/core/jni/android/graphics/Graphics.cpp @@ -424,36 +424,6 @@ jobject GraphicsJNI::createRegion(JNIEnv* env, SkRegion* region) /////////////////////////////////////////////////////////////////////////////// -android::Bitmap* GraphicsJNI::mapAshmemBitmap(JNIEnv* env, SkBitmap* bitmap, - int fd, void* addr, size_t size, bool readOnly) { - const SkImageInfo& info = bitmap->info(); - if (info.colorType() == kUnknown_SkColorType) { - doThrowIAE(env, "unknown bitmap configuration"); - return nullptr; - } - - if (!addr) { - // Map existing ashmem region if not already mapped. - int flags = readOnly ? (PROT_READ) : (PROT_READ | PROT_WRITE); - size = ashmem_get_size_region(fd); - addr = mmap(NULL, size, flags, MAP_SHARED, fd, 0); - if (addr == MAP_FAILED) { - return nullptr; - } - } - - // we must respect the rowBytes value already set on the bitmap instead of - // attempting to compute our own. - const size_t rowBytes = bitmap->rowBytes(); - - auto wrapper = new android::Bitmap(addr, fd, size, info, rowBytes); - wrapper->getSkBitmap(bitmap); - if (readOnly) { - bitmap->pixelRef()->setImmutable(); - } - return wrapper; -} - SkColorSpaceTransferFn GraphicsJNI::getNativeTransferParameters(JNIEnv* env, jobject transferParams) { SkColorSpaceTransferFn p; p.fA = (float) env->GetDoubleField(transferParams, gTransferParams_aFieldID); diff --git a/core/jni/android/graphics/GraphicsJNI.h b/core/jni/android/graphics/GraphicsJNI.h index cee3c46dd67f..b0bd68336e08 100644 --- a/core/jni/android/graphics/GraphicsJNI.h +++ b/core/jni/android/graphics/GraphicsJNI.h @@ -85,9 +85,6 @@ public: static jobject createBitmapRegionDecoder(JNIEnv* env, SkBitmapRegionDecoder* bitmap); - static android::Bitmap* mapAshmemBitmap(JNIEnv* env, SkBitmap* bitmap, - int fd, void* addr, size_t size, bool readOnly); - /** * Given a bitmap we natively allocate a memory block to store the contents * of that bitmap. The memory is then attached to the bitmap via an diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp index fa1da4bfbf3a..888dab19c247 100644 --- a/core/jni/android_os_Debug.cpp +++ b/core/jni/android_os_Debug.cpp @@ -32,6 +32,7 @@ #include <atomic> #include <iomanip> #include <string> +#include <vector> #include <debuggerd/client.h> #include <log/log.h> @@ -41,6 +42,7 @@ #include <nativehelper/JNIHelp.h> #include <nativehelper/ScopedUtfChars.h> #include "jni.h" +#include <meminfo/sysmeminfo.h> #include <memtrack/memtrack.h> #include <memunreachable/memunreachable.h> #include "android_os_Debug.h" @@ -712,6 +714,8 @@ static long get_allocated_vmalloc_memory() { return vmalloc_allocated_size; } +// The 1:1 mapping of MEMINFO_* enums here must match with the constants from +// Debug.java. enum { MEMINFO_TOTAL, MEMINFO_FREE, @@ -731,138 +735,43 @@ enum { MEMINFO_COUNT }; -static long long get_zram_mem_used() -{ -#define ZRAM_SYSFS "/sys/block/zram0/" - UniqueFile mm_stat_file = MakeUniqueFile(ZRAM_SYSFS "mm_stat", "re"); - if (mm_stat_file) { - long long mem_used_total = 0; - - int matched = fscanf(mm_stat_file.get(), "%*d %*d %lld %*d %*d %*d %*d", &mem_used_total); - if (matched != 1) - ALOGW("failed to parse " ZRAM_SYSFS "mm_stat"); - - return mem_used_total; - } - - UniqueFile mem_used_total_file = MakeUniqueFile(ZRAM_SYSFS "mem_used_total", "re"); - if (mem_used_total_file) { - long long mem_used_total = 0; - - int matched = fscanf(mem_used_total_file.get(), "%lld", &mem_used_total); - if (matched != 1) - ALOGW("failed to parse " ZRAM_SYSFS "mem_used_total"); - - return mem_used_total; - } - - return 0; -} - static void android_os_Debug_getMemInfo(JNIEnv *env, jobject clazz, jlongArray out) { - char buffer[4096]; - size_t numFound = 0; - if (out == NULL) { jniThrowNullPointerException(env, "out == null"); return; } - int fd = open("/proc/meminfo", O_RDONLY | O_CLOEXEC); - - if (fd < 0) { - ALOGW("Unable to open /proc/meminfo: %s\n", strerror(errno)); + int outLen = env->GetArrayLength(out); + if (outLen < MEMINFO_COUNT) { + jniThrowRuntimeException(env, "outLen < MEMINFO_COUNT"); return; } - int len = read(fd, buffer, sizeof(buffer)-1); - close(fd); - - if (len < 0) { - ALOGW("Empty /proc/meminfo"); + // Read system memory info including ZRAM. The values are stored in the vector + // in the same order as MEMINFO_* enum + std::vector<uint64_t> mem(MEMINFO_COUNT); + std::vector<std::string> tags(::android::meminfo::SysMemInfo::kDefaultSysMemInfoTags); + tags.insert(tags.begin() + MEMINFO_ZRAM_TOTAL, "Zram:"); + ::android::meminfo::SysMemInfo smi; + if (!smi.ReadMemInfo(tags, &mem)) { + jniThrowRuntimeException(env, "SysMemInfo read failed"); return; } - buffer[len] = 0; - - static const char* const tags[] = { - "MemTotal:", - "MemFree:", - "Buffers:", - "Cached:", - "Shmem:", - "Slab:", - "SReclaimable:", - "SUnreclaim:", - "SwapTotal:", - "SwapFree:", - "ZRam:", - "Mapped:", - "VmallocUsed:", - "PageTables:", - "KernelStack:", - NULL - }; - static const int tagsLen[] = { - 9, - 8, - 8, - 7, - 6, - 5, - 13, - 11, - 10, - 9, - 5, - 7, - 12, - 11, - 12, - 0 - }; - long mem[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; - - char* p = buffer; - while (*p && numFound < (sizeof(tagsLen) / sizeof(tagsLen[0]))) { - int i = 0; - while (tags[i]) { - if (strncmp(p, tags[i], tagsLen[i]) == 0) { - p += tagsLen[i]; - while (*p == ' ') p++; - char* num = p; - while (*p >= '0' && *p <= '9') p++; - if (*p != 0) { - *p = 0; - p++; - } - mem[i] = atoll(num); - numFound++; - break; - } - i++; - } - while (*p && *p != '\n') { - p++; - } - if (*p) p++; - } - - mem[MEMINFO_ZRAM_TOTAL] = get_zram_mem_used() / 1024; - // Recompute Vmalloc Used since the value in meminfo - // doesn't account for I/O remapping which doesn't use RAM. - mem[MEMINFO_VMALLOC_USED] = get_allocated_vmalloc_memory() / 1024; - int maxNum = env->GetArrayLength(out); - if (maxNum > MEMINFO_COUNT) { - maxNum = MEMINFO_COUNT; - } jlong* outArray = env->GetLongArrayElements(out, 0); if (outArray != NULL) { - for (int i=0; i<maxNum; i++) { + outLen = MEMINFO_COUNT; + for (int i = 0; i < outLen; i++) { + // TODO: move get_allocated_vmalloc_memory() to libmeminfo + if (i == MEMINFO_VMALLOC_USED) { + outArray[i] = get_allocated_vmalloc_memory() / 1024; + continue; + } outArray[i] = mem[i]; } } + env->ReleaseLongArrayElements(out, outArray, 0); } diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp index 102a0b7b8957..0c1a8aa18370 100644 --- a/core/jni/android_util_Process.cpp +++ b/core/jni/android_util_Process.cpp @@ -25,8 +25,12 @@ #include <cutils/sched_policy.h> #include <utils/String8.h> #include <utils/Vector.h> +#include <meminfo/sysmeminfo.h> #include <processgroup/processgroup.h> +#include <string> +#include <vector> + #include "core_jni_helpers.h" #include "android_util_Binder.h" @@ -39,9 +43,11 @@ #include <inttypes.h> #include <pwd.h> #include <signal.h> +#include <string.h> #include <sys/errno.h> #include <sys/resource.h> #include <sys/stat.h> +#include <sys/sysinfo.h> #include <sys/types.h> #include <unistd.h> @@ -603,66 +609,34 @@ static int pid_compare(const void* v1, const void* v2) return *((const jint*)v1) - *((const jint*)v2); } -static jlong getFreeMemoryImpl(const char* const sums[], const size_t sumsLen[], size_t num) +static jlong android_os_Process_getFreeMemory(JNIEnv* env, jobject clazz) { - int fd = open("/proc/meminfo", O_RDONLY | O_CLOEXEC); + static const std::vector<std::string> memFreeTags = { + ::android::meminfo::SysMemInfo::kMemFree, + ::android::meminfo::SysMemInfo::kMemCached, + }; + std::vector<uint64_t> mem(memFreeTags.size()); + ::android::meminfo::SysMemInfo smi; - if (fd < 0) { - ALOGW("Unable to open /proc/meminfo"); - return -1; + if (!smi.ReadMemInfo(memFreeTags, &mem)) { + jniThrowRuntimeException(env, "SysMemInfo read failed to get Free Memory"); + return -1L; } - char buffer[2048]; - const int len = read(fd, buffer, sizeof(buffer)-1); - close(fd); - - if (len < 0) { - ALOGW("Unable to read /proc/meminfo"); - return -1; - } - buffer[len] = 0; - - size_t numFound = 0; - jlong mem = 0; - - char* p = buffer; - while (*p && numFound < num) { - int i = 0; - while (sums[i]) { - if (strncmp(p, sums[i], sumsLen[i]) == 0) { - p += sumsLen[i]; - while (*p == ' ') p++; - char* num = p; - while (*p >= '0' && *p <= '9') p++; - if (*p != 0) { - *p = 0; - p++; - if (*p == 0) p--; - } - mem += atoll(num) * 1024; - numFound++; - break; - } - i++; - } - p++; - } - - return numFound > 0 ? mem : -1; -} - -static jlong android_os_Process_getFreeMemory(JNIEnv* env, jobject clazz) -{ - static const char* const sums[] = { "MemFree:", "Cached:", NULL }; - static const size_t sumsLen[] = { strlen("MemFree:"), strlen("Cached:"), 0 }; - return getFreeMemoryImpl(sums, sumsLen, 2); + jlong sum = 0; + std::for_each(mem.begin(), mem.end(), [&](uint64_t val) { sum += val; }); + return sum * 1024; } static jlong android_os_Process_getTotalMemory(JNIEnv* env, jobject clazz) { - static const char* const sums[] = { "MemTotal:", NULL }; - static const size_t sumsLen[] = { strlen("MemTotal:"), 0 }; - return getFreeMemoryImpl(sums, sumsLen, 1); + struct sysinfo si; + if (sysinfo(&si) == -1) { + ALOGE("sysinfo failed: %s", strerror(errno)); + return -1; + } + + return si.totalram; } void android_os_Process_readProcLines(JNIEnv* env, jobject clazz, jstring fileStr, diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp index 702741eb813c..5a8ab3c1bdc4 100644 --- a/core/jni/android_view_ThreadedRenderer.cpp +++ b/core/jni/android_view_ThreadedRenderer.cpp @@ -31,8 +31,6 @@ #include <gui/BufferQueue.h> #include <gui/Surface.h> -#include <EGL/egl.h> -#include <EGL/eglext.h> #include <private/EGL/cache.h> #include <utils/Looper.h> @@ -58,6 +56,7 @@ #include <renderthread/RenderTask.h> #include <renderthread/RenderThread.h> #include <pipeline/skia/ShaderCache.h> +#include <utils/Color.h> namespace android { @@ -1011,10 +1010,9 @@ static jobject android_view_ThreadedRenderer_createHardwareBitmapFromRenderNode( buffer->getWidth(), buffer->getHeight(), width, height); // Continue I guess? } - sk_sp<Bitmap> bitmap = Bitmap::createFrom(buffer); - // Bitmap::createFrom currently can only attach to a GraphicBuffer with PIXEL_FORMAT_RGBA_8888 - // format and SRGB color space. - // To support any color space, we could extract it from BufferItem and pass it to Bitmap. + + sk_sp<SkColorSpace> cs = uirenderer::DataSpaceToColorSpace(bufferItem.mDataSpace); + sk_sp<Bitmap> bitmap = Bitmap::createFrom(buffer, cs); return bitmap::createBitmap(env, bitmap.release(), android::bitmap::kBitmapCreateFlag_Premultiplied); } diff --git a/core/proto/android/service/procstats.proto b/core/proto/android/service/procstats.proto index ce19ce3519b2..71ebcc1e3659 100644 --- a/core/proto/android/service/procstats.proto +++ b/core/proto/android/service/procstats.proto @@ -186,7 +186,7 @@ message PackageServiceStatsProto { repeated PackageServiceOperationStatsProto operation_stats = 2; } -// Next Tag: 7 +// Next Tag: 8 message PackageAssociationSourceProcessStatsProto { option (android.msg_privacy).dest = DEST_AUTOMATIC; @@ -194,6 +194,8 @@ message PackageAssociationSourceProcessStatsProto { optional int32 process_uid = 1; // Process name. optional string process_name = 2; + // Package name. + optional string package_name = 7; // Total count of the times this association appeared. optional int32 total_count = 3; diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index cc8927fdf730..8b6f33fd97e7 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -2003,6 +2003,15 @@ <permission android:name="android.permission.BIND_SCREENING_SERVICE" android:protectionLevel="signature|privileged" /> + <!-- Must be required by a {@link android.telecom.PhoneAccountSuggestionService}, + to ensure that only the system can bind to it. + <p>Protection level: signature|privileged + @SystemApi + @hide + --> + <permission android:name="android.permission.BIND_PHONE_ACCOUNT_SUGGESTION_SERVICE" + android:protectionLevel="signature|privileged" /> + <!-- Must be required by a {@link android.telecom.CallRedirectionService}, to ensure that only the system can bind to it. <p>Protection level: signature|privileged diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml index 5ba1cf259551..0f53549a966c 100644 --- a/core/res/res/layout/notification_template_header.xml +++ b/core/res/res/layout/notification_template_header.xml @@ -111,6 +111,7 @@ android:background="@null" android:layout_width="@dimen/notification_header_expand_icon_size" android:layout_height="@dimen/notification_header_expand_icon_size" + android:layout_marginStart="4dp" android:paddingTop="@dimen/notification_expand_button_padding_top" android:visibility="gone" android:contentDescription="@string/expand_button_content_description_collapsed" diff --git a/core/res/res/values-night/themes_permission_controller.xml b/core/res/res/values-night/themes_permission_controller.xml index 0ad2bdcbb52d..a071927f46b2 100644 --- a/core/res/res/values-night/themes_permission_controller.xml +++ b/core/res/res/values-night/themes_permission_controller.xml @@ -28,8 +28,5 @@ <style name="Theme.DeviceDefault.PermissionGrant" parent="@style/Theme.DeviceDefault.Dialog"> <item name="titleTextStyle">@style/PermissionGrantTitleMessage</item> - <item name="radioButtonStyle">@style/PermissionGrantRadioButton</item> - <item name="checkboxStyle">@style/PermissionGrantCheckbox</item> - <item name="buttonBarStyle">@style/PermissionGrantButtonBar</item> </style> </resources> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index dd0b1ee83e14..9f5eab557026 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2615,6 +2615,11 @@ <!-- Package name for default network scorer app; overridden by product overlays. --> <string name="config_defaultNetworkScorerPackageName"></string> + <!-- Feature flag to enable memory efficient task snapshots that are used in recents optimized + for low memory devices and replace the app transition starting window with the splash + screen. --> + <bool name="config_lowRamTaskSnapshotsAndRecents">false</bool> + <!-- Determines whether recent tasks are provided to the user. Default device has recents property. If this is false, then the following recents config flags are ignored. --> <bool name="config_hasRecents">true</bool> @@ -3492,8 +3497,6 @@ <!-- Name of a font family to use for headlines. If empty, falls back to platform default --> <string name="config_headlineFontFamily" translatable="false"></string> - <!-- Name of a font family to use for headlines. Defaults to sans-serif-light --> - <string name="config_headlineFontFamilyLight" translatable="false">sans-serif-light</string> <!-- Allows setting custom fontFeatureSettings on specific text. --> <string name="config_headlineFontFeatureSettings" translatable="false"></string> @@ -3560,8 +3563,6 @@ <string name="config_headlineFontFamilyMedium" translateable="false">@string/font_family_button_material</string> <!-- Name of a font family to use for body text. --> <string name="config_bodyFontFamily" translatable="false">sans-serif</string> - <!-- Name of a font family to use for light body text. --> - <string name="config_bodyFontFamilyLight" translatable="false">sans-serif-light</string> <!-- Name of a font family to use for medium body text. --> <string name="config_bodyFontFamilyMedium" translatable="false">sans-serif-medium</string> @@ -3646,4 +3647,11 @@ <!-- Component name for the default module metadata provider on this device --> <string name="config_defaultModuleMetadataProvider">com.android.modulemetadata</string> + + <!-- This is the default launcher component to use on secondary displays that support system + decorations. + This launcher activity must support multiple instances and have corresponding launch mode + set in AndroidManifest. + {@see android.view.Display#FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS} --> + <string name="config_secondaryHomeComponent" translatable="false">com.android.launcher3/com.android.launcher3.SecondaryDisplayLauncher</string> </resources> diff --git a/core/res/res/values/styles_permission_controller.xml b/core/res/res/values/styles_permission_controller.xml index e6e0de3a6f30..5a9d3e6c2889 100644 --- a/core/res/res/values/styles_permission_controller.xml +++ b/core/res/res/values/styles_permission_controller.xml @@ -25,15 +25,16 @@ </style> <style name="PermissionGrantTitleIcon"> - <item name="layout_width">36dp</item> - <item name="layout_height">36dp</item> + <item name="layout_width">24dp</item> + <item name="layout_height">24dp</item> + <item name="layout_marginBottom">12dp</item> <item name="tint">?attr/colorAccent</item> <item name="scaleType">fitCenter</item> </style> <style name="PermissionGrantTitleMessage" parent="@style/TextAppearance.DeviceDefault"> - <item name="paddingStart">22dp</item> + <item name="gravity">center</item> <item name="textSize">20sp</item> <item name="textColor">?attr/textColorPrimary</item> </style> @@ -46,56 +47,22 @@ </style> <style name="PermissionGrantDescription"> - <item name="layout_marginTop">20dp</item> <item name="layout_marginStart">24dp</item> - <item name="layout_marginBottom">16dp</item> <item name="layout_marginEnd">24dp</item> </style> <style name="PermissionGrantContent"> - <item name="layout_marginStart">16dp</item> + <item name="layout_marginStart">24dp</item> <item name="layout_marginEnd">24dp</item> </style> - <style name="PermissionGrantRadioGroup"> - <item name="layout_marginStart">8dp</item> - <item name="layout_marginTop">-4dp</item> - </style> - - <style name="PermissionGrantRadioButton" - parent="@style/Widget.DeviceDefault.CompoundButton.RadioButton"> - <item name="paddingStart">16dp</item> - <item name="paddingTop">8dp</item> - <item name="paddingBottom">8dp</item> - <item name="textSize">16sp</item> - </style> - <style name="PermissionGrantDetailMessage" parent="@style/TextAppearance.DeviceDefault"> - <item name="layout_marginStart">8dp</item> - <item name="layout_marginBottom">4dp</item> + <item name="layout_marginTop">18dp</item> <item name="textColor">?attr/textColorPrimary</item> <item name="textSize">16sp</item> </style> - <style name="PermissionGrantDetailMessageSpace"> - <item name="layout_height">8dp</item> - </style> - - <style name="PermissionGrantCheckbox" - parent="@style/Widget.DeviceDefault.CompoundButton.CheckBox"> - <item name="paddingStart">16dp</item> - <item name="textColor">?attr/textColorSecondary</item> - <item name="buttonTint">?attr/textColorSecondary</item> - <item name="textSize">16sp</item> - </style> - - <style name="PermissionGrantButtonBar"> - <item name="layout_marginStart">24dp</item> - <item name="layout_marginEnd">16dp</item> - <item name="layout_marginBottom">4dp</item> - </style> - <!-- styles for the permission review screen. --> <style name="PermissionReviewDescription"> <item name="layout_marginTop">20dp</item> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 87fdc1fb575b..4ed050117541 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1,4 +1,3 @@ -<?xml version="1.0" encoding="utf-8"?> <!-- /* Copyright 2012, The Android Open Source Project ** @@ -330,6 +329,7 @@ <java-symbol type="bool" name="config_enableMultiUserUI"/> <java-symbol type="bool" name="config_enableNewAutoSelectNetworkUI"/> <java-symbol type="bool" name="config_disableUsbPermissionDialogs"/> + <java-symbol type="bool" name="config_lowRamTaskSnapshotsAndRecents" /> <java-symbol type="bool" name="config_hasRecents" /> <java-symbol type="string" name="config_recentsComponentName" /> <java-symbol type="integer" name="config_minNumVisibleRecentTasks_lowRam" /> @@ -3328,7 +3328,6 @@ <java-symbol type="bool" name="config_displayBrightnessBucketsInDoze" /> <java-symbol type="integer" name="config_storageManagerDaystoRetainDefault" /> <java-symbol type="string" name="config_headlineFontFamily" /> - <java-symbol type="string" name="config_headlineFontFamilyLight" /> <java-symbol type="string" name="config_headlineFontFamilyMedium" /> <java-symbol type="drawable" name="stat_sys_vitals" /> @@ -3522,4 +3521,7 @@ <java-symbol type="dimen" name="rounded_corner_radius_bottom" /> <java-symbol type="string" name="config_defaultModuleMetadataProvider" /> + + <!-- For Secondary Launcher --> + <java-symbol type="string" name="config_secondaryHomeComponent" /> </resources> diff --git a/core/res/res/values/themes_permission_controller.xml b/core/res/res/values/themes_permission_controller.xml index 369cee3d98f1..205c4eb2d9a2 100644 --- a/core/res/res/values/themes_permission_controller.xml +++ b/core/res/res/values/themes_permission_controller.xml @@ -28,9 +28,6 @@ <style name="Theme.DeviceDefault.PermissionGrant" parent="@style/Theme.DeviceDefault.Light.Dialog"> <item name="titleTextStyle">@style/PermissionGrantTitleMessage</item> - <item name="radioButtonStyle">@style/PermissionGrantRadioButton</item> - <item name="checkboxStyle">@style/PermissionGrantCheckbox</item> - <item name="buttonBarStyle">@style/PermissionGrantButtonBar</item> </style> <!-- themes for the permission review dialog. --> @@ -39,6 +36,5 @@ <item name="windowActionBar">false</item> <item name="windowNoTitle">true</item> <item name="titleTextStyle">@style/PermissionReviewTitleMessage</item> - <item name="buttonBarStyle">@style/PermissionReviewButtonBar</item> </style> </resources> diff --git a/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk b/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk index 8656781e31e5..c7b2dd1ac8f9 100644 --- a/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk +++ b/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk @@ -20,7 +20,7 @@ LOCAL_SRC_FILES := $(call all-java-files-under,src) LOCAL_PACKAGE_NAME := OverlayHostTests_UpdateOverlay LOCAL_SDK_VERSION := current LOCAL_COMPATIBILITY_SUITE := general-tests -LOCAL_STATIC_JAVA_LIBRARIES := android-support-test +LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.rules LOCAL_USE_AAPT2 := true LOCAL_AAPT_FLAGS := --no-resource-removal include $(BUILD_PACKAGE) diff --git a/core/tests/overlaytests/host/test-apps/UpdateOverlay/AndroidManifest.xml b/core/tests/overlaytests/host/test-apps/UpdateOverlay/AndroidManifest.xml index 06077a79d009..8b5fe99c2e74 100644 --- a/core/tests/overlaytests/host/test-apps/UpdateOverlay/AndroidManifest.xml +++ b/core/tests/overlaytests/host/test-apps/UpdateOverlay/AndroidManifest.xml @@ -21,7 +21,7 @@ <uses-library android:name="android.test.runner" /> </application> - <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner" + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" android:targetPackage="com.android.server.om.hosttest.update_overlay_test" android:label="Update Overlay Test"/> </manifest> diff --git a/core/tests/overlaytests/host/test-apps/UpdateOverlay/src/com/android/server/om/hosttest/update_overlay_test/UpdateOverlayTest.java b/core/tests/overlaytests/host/test-apps/UpdateOverlay/src/com/android/server/om/hosttest/update_overlay_test/UpdateOverlayTest.java index 86a86794004c..fef63208b2d3 100644 --- a/core/tests/overlaytests/host/test-apps/UpdateOverlay/src/com/android/server/om/hosttest/update_overlay_test/UpdateOverlayTest.java +++ b/core/tests/overlaytests/host/test-apps/UpdateOverlay/src/com/android/server/om/hosttest/update_overlay_test/UpdateOverlayTest.java @@ -19,9 +19,10 @@ import static org.junit.Assert.assertEquals; import android.content.res.Configuration; import android.content.res.Resources; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; import org.junit.Before; import org.junit.Test; diff --git a/core/tests/packagemanagertests/Android.mk b/core/tests/packagemanagertests/Android.mk index f95231f1d397..8c00d14ce856 100644 --- a/core/tests/packagemanagertests/Android.mk +++ b/core/tests/packagemanagertests/Android.mk @@ -9,7 +9,7 @@ LOCAL_SRC_FILES := \ $(call all-java-files-under, src) LOCAL_STATIC_JAVA_LIBRARIES := \ - android-support-test \ + androidx.test.rules \ frameworks-base-testutils \ mockito-target-minus-junit4 diff --git a/core/tests/packagemanagertests/AndroidManifest.xml b/core/tests/packagemanagertests/AndroidManifest.xml index 8f490083b16e..ee4a775ac97f 100644 --- a/core/tests/packagemanagertests/AndroidManifest.xml +++ b/core/tests/packagemanagertests/AndroidManifest.xml @@ -24,7 +24,7 @@ </application> <instrumentation - android:name="android.support.test.runner.AndroidJUnitRunner" + android:name="androidx.test.runner.AndroidJUnitRunner" android:targetPackage="com.android.frameworks.coretests.packagemanager" android:label="Frameworks PackageManager Core Tests" /> diff --git a/core/tests/packagemanagertests/src/android/content/pm/KernelPackageMappingTests.java b/core/tests/packagemanagertests/src/android/content/pm/KernelPackageMappingTests.java index 4e0f2a8fe060..c5c9700e2316 100644 --- a/core/tests/packagemanagertests/src/android/content/pm/KernelPackageMappingTests.java +++ b/core/tests/packagemanagertests/src/android/content/pm/KernelPackageMappingTests.java @@ -22,10 +22,11 @@ import android.content.Context; import android.os.FileUtils; import android.os.ServiceManager; import android.os.UserManager; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; import android.util.Log; +import androidx.test.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + import org.junit.After; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/privacytests/Android.mk b/core/tests/privacytests/Android.mk index 3c1526b7dc48..7765977c03e8 100644 --- a/core/tests/privacytests/Android.mk +++ b/core/tests/privacytests/Android.mk @@ -8,7 +8,7 @@ LOCAL_MODULE_TAGS := tests LOCAL_SRC_FILES := \ $(call all-java-files-under, src) -LOCAL_STATIC_JAVA_LIBRARIES := junit rappor-tests android-support-test truth-prebuilt +LOCAL_STATIC_JAVA_LIBRARIES := junit rappor-tests androidx.test.rules truth-prebuilt LOCAL_JAVA_LIBRARIES := android.test.runner LOCAL_PACKAGE_NAME := FrameworksPrivacyLibraryTests diff --git a/core/tests/privacytests/AndroidManifest.xml b/core/tests/privacytests/AndroidManifest.xml index a0e52814bdcd..3c826144aa57 100644 --- a/core/tests/privacytests/AndroidManifest.xml +++ b/core/tests/privacytests/AndroidManifest.xml @@ -22,7 +22,7 @@ </application> <instrumentation - android:name="android.support.test.runner.AndroidJUnitRunner" + android:name="androidx.test.runner.AndroidJUnitRunner" android:targetPackage="com.android.frameworks.coretests.privacy" android:label="Frameworks Privacy Library Tests" /> diff --git a/core/tests/privacytests/src/android/privacy/LongitudinalReportingEncoderTest.java b/core/tests/privacytests/src/android/privacy/LongitudinalReportingEncoderTest.java index c88a722c026b..18928ebd6461 100644 --- a/core/tests/privacytests/src/android/privacy/LongitudinalReportingEncoderTest.java +++ b/core/tests/privacytests/src/android/privacy/LongitudinalReportingEncoderTest.java @@ -17,21 +17,20 @@ package android.privacy; 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 android.privacy.internal.longitudinalreporting.LongitudinalReportingConfig; import android.privacy.internal.longitudinalreporting.LongitudinalReportingEncoder; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; import java.nio.ByteBuffer; -import java.nio.ByteOrder; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; diff --git a/core/tests/privacytests/src/android/privacy/RapporEncoderTest.java b/core/tests/privacytests/src/android/privacy/RapporEncoderTest.java index 71bd8f1c7bf0..4a353505f1da 100644 --- a/core/tests/privacytests/src/android/privacy/RapporEncoderTest.java +++ b/core/tests/privacytests/src/android/privacy/RapporEncoderTest.java @@ -22,8 +22,9 @@ import static org.junit.Assert.assertTrue; import android.privacy.internal.rappor.RapporConfig; import android.privacy.internal.rappor.RapporEncoder; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/utiltests/Android.mk b/core/tests/utiltests/Android.mk index 5c60c8184753..343c07af51df 100644 --- a/core/tests/utiltests/Android.mk +++ b/core/tests/utiltests/Android.mk @@ -15,7 +15,7 @@ LOCAL_SRC_FILES += src/android/util/IRemoteMemoryIntArray.aidl LOCAL_JNI_SHARED_LIBRARIES := libmemoryintarraytest libcutils libc++ LOCAL_STATIC_JAVA_LIBRARIES := \ - android-support-test \ + androidx.test.rules \ frameworks-base-testutils \ mockito-target-minus-junit4 \ diff --git a/core/tests/utiltests/AndroidManifest.xml b/core/tests/utiltests/AndroidManifest.xml index 8db81ca4b3b6..4ef4b1fe9120 100644 --- a/core/tests/utiltests/AndroidManifest.xml +++ b/core/tests/utiltests/AndroidManifest.xml @@ -51,7 +51,7 @@ </application> <instrumentation - android:name="android.support.test.runner.AndroidJUnitRunner" + android:name="androidx.test.runner.AndroidJUnitRunner" android:targetPackage="com.android.frameworks.utiltests" android:label="Frameworks Utility Tests" /> diff --git a/core/tests/utiltests/runtests.sh b/core/tests/utiltests/runtests.sh index 853119f62334..3b46cbbef4e9 100755 --- a/core/tests/utiltests/runtests.sh +++ b/core/tests/utiltests/runtests.sh @@ -21,4 +21,4 @@ adb wait-for-device adb install -r -g "$OUT/data/app/FrameworksUtilTests/FrameworksUtilTests.apk" -adb shell am instrument -w "$@" 'com.android.frameworks.utiltests/android.support.test.runner.AndroidJUnitRunner' +adb shell am instrument -w "$@" 'com.android.frameworks.utiltests/androidx.test.runner.AndroidJUnitRunner' diff --git a/core/tests/utiltests/src/android/util/IntArrayTest.java b/core/tests/utiltests/src/android/util/IntArrayTest.java index a6120a1e8142..a76c640db74a 100644 --- a/core/tests/utiltests/src/android/util/IntArrayTest.java +++ b/core/tests/utiltests/src/android/util/IntArrayTest.java @@ -19,8 +19,9 @@ package android.util; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/utiltests/src/android/util/LongArrayTest.java b/core/tests/utiltests/src/android/util/LongArrayTest.java index a7afcbd81fdf..a9a168b1bf6b 100644 --- a/core/tests/utiltests/src/android/util/LongArrayTest.java +++ b/core/tests/utiltests/src/android/util/LongArrayTest.java @@ -19,8 +19,9 @@ package android.util; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/utiltests/src/android/util/MemoryIntArrayTest.java b/core/tests/utiltests/src/android/util/MemoryIntArrayTest.java index 24b33effdb71..2daefe74eb12 100644 --- a/core/tests/utiltests/src/android/util/MemoryIntArrayTest.java +++ b/core/tests/utiltests/src/android/util/MemoryIntArrayTest.java @@ -24,8 +24,11 @@ import static org.junit.Assert.fail; import android.os.Parcel; import android.os.ParcelFileDescriptor; -import android.support.test.runner.AndroidJUnit4; + +import androidx.test.runner.AndroidJUnit4; + import libcore.io.IoUtils; + import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/utiltests/src/android/util/RemoteIntArray.java b/core/tests/utiltests/src/android/util/RemoteIntArray.java index 11d0888179d1..4a3a01e61b7d 100644 --- a/core/tests/utiltests/src/android/util/RemoteIntArray.java +++ b/core/tests/utiltests/src/android/util/RemoteIntArray.java @@ -24,7 +24,8 @@ import android.os.Build; import android.os.IBinder; import android.os.RemoteException; import android.os.SystemClock; -import android.support.test.InstrumentationRegistry; + +import androidx.test.InstrumentationRegistry; import java.io.Closeable; import java.io.IOException; diff --git a/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java b/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java index b18ee171eb0c..03cf3eb6a2b9 100644 --- a/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java +++ b/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java @@ -29,11 +29,12 @@ import android.content.ContextWrapper; import android.content.pm.UserInfo; import android.os.UserManager; import android.provider.Settings; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; - import android.test.mock.MockContentResolver; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + import com.android.internal.util.test.FakeSettingsProvider; import com.android.internal.widget.ILockSettings; import com.android.internal.widget.LockPatternUtils; diff --git a/data/etc/Android.bp b/data/etc/Android.bp new file mode 100644 index 000000000000..25dabad6d34c --- /dev/null +++ b/data/etc/Android.bp @@ -0,0 +1,64 @@ +// 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. + + +// Sysconfig files + +prebuilt_etc { + name: "framework-sysconfig.xml", + sub_dir: "sysconfig", + src: "framework-sysconfig.xml", +} + +prebuilt_etc { + name: "hiddenapi-package-whitelist.xml", + sub_dir: "sysconfig", + src: "hiddenapi-package-whitelist.xml", +} + +// Privapp permission whitelist files + +prebuilt_etc { + name: "platform.xml", + sub_dir: "permissions", + src: "platform.xml", +} + +prebuilt_etc { + name: "privapp-permissions-platform.xml", + sub_dir: "permissions", + src: "privapp-permissions-platform.xml", +} + +prebuilt_etc { + name: "privapp_whitelist_com.android.settings", + product_specific: true, + sub_dir: "permissions", + src: "com.android.settings.xml", + filename_from_src: true, +} + +prebuilt_etc { + name: "privapp_whitelist_com.android.systemui", + product_specific: true, + sub_dir: "permissions", + src: "com.android.systemui.xml", + filename_from_src: true, +} + +prebuilt_etc { + name: "com.android.timezone.updater.xml", + sub_dir: "permissions", + src: "com.android.timezone.updater.xml", +} diff --git a/data/etc/Android.mk b/data/etc/Android.mk deleted file mode 100644 index ff8c4f12d9d1..000000000000 --- a/data/etc/Android.mk +++ /dev/null @@ -1,78 +0,0 @@ -# -# Copyright (C) 2008 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 := $(my-dir) - -######################## -include $(CLEAR_VARS) -LOCAL_MODULE := framework-sysconfig.xml -LOCAL_MODULE_CLASS := ETC -LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/sysconfig -LOCAL_SRC_FILES := $(LOCAL_MODULE) -include $(BUILD_PREBUILT) - -######################## -include $(CLEAR_VARS) -LOCAL_MODULE := platform.xml -LOCAL_MODULE_CLASS := ETC -LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/permissions -LOCAL_SRC_FILES := $(LOCAL_MODULE) -include $(BUILD_PREBUILT) - -######################## -include $(CLEAR_VARS) -LOCAL_MODULE := privapp-permissions-platform.xml -LOCAL_MODULE_CLASS := ETC -LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/permissions -LOCAL_SRC_FILES := $(LOCAL_MODULE) -include $(BUILD_PREBUILT) - -######################## -include $(CLEAR_VARS) -LOCAL_MODULE := hiddenapi-package-whitelist.xml -LOCAL_MODULE_CLASS := ETC -LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/sysconfig -LOCAL_SRC_FILES := $(LOCAL_MODULE) -include $(BUILD_PREBUILT) - -######################## -include $(CLEAR_VARS) -LOCAL_MODULE := privapp_whitelist_com.android.settings -LOCAL_MODULE_CLASS := ETC -LOCAL_MODULE_RELATIVE_PATH := permissions -LOCAL_MODULE_STEM := com.android.settings.xml -LOCAL_PRODUCT_MODULE := true -LOCAL_SRC_FILES := com.android.settings.xml -include $(BUILD_PREBUILT) - -######################## -include $(CLEAR_VARS) -LOCAL_MODULE := privapp_whitelist_com.android.systemui -LOCAL_MODULE_CLASS := ETC -LOCAL_MODULE_RELATIVE_PATH := permissions -LOCAL_MODULE_STEM := com.android.systemui.xml -LOCAL_PRODUCT_MODULE := true -LOCAL_SRC_FILES := com.android.systemui.xml -include $(BUILD_PREBUILT) - - -######################## -include $(CLEAR_VARS) -LOCAL_MODULE := com.android.timezone.updater.xml -LOCAL_MODULE_CLASS := ETC -LOCAL_MODULE_RELATIVE_PATH := permissions -LOCAL_SRC_FILES := $(LOCAL_MODULE) -include $(BUILD_PREBUILT) diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml index c84c0354a7c4..f7541e0678d5 100644 --- a/data/fonts/fonts.xml +++ b/data/fonts/fonts.xml @@ -365,7 +365,7 @@ <font weight="400" style="normal">NotoSansCarian-Regular.ttf</font> </family> <family lang="und-Cakm"> - <font weight="400" style="normal">NotoSansChakma-Regular.ttf</font> + <font weight="400" style="normal">NotoSansChakma-Regular.otf</font> </family> <family lang="und-Cher"> <font weight="400" style="normal">NotoSansCherokee-Regular.ttf</font> diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp index 98af3eb05391..eeaefc5b157c 100644 --- a/libs/androidfw/Android.bp +++ b/libs/androidfw/Android.bp @@ -167,6 +167,7 @@ cc_test { }, }, data: ["tests/data/**/*.apk"], + test_suites: ["device-tests"], } cc_benchmark { diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp index c20c720eadbb..5a267804ddf1 100644 --- a/libs/androidfw/LoadedArsc.cpp +++ b/libs/androidfw/LoadedArsc.cpp @@ -623,7 +623,7 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk, } // Add the pairing of overlayable properties to resource ids to the package - OverlayableInfo overlayable_info; + OverlayableInfo overlayable_info{}; overlayable_info.policy_flags = policy_header->policy_flags; loaded_package->overlayable_infos_.push_back(std::make_pair(overlayable_info, ids)); break; diff --git a/libs/hwui/HardwareBitmapUploader.cpp b/libs/hwui/HardwareBitmapUploader.cpp index a97c12cad9fd..b9860ada18fc 100644 --- a/libs/hwui/HardwareBitmapUploader.cpp +++ b/libs/hwui/HardwareBitmapUploader.cpp @@ -253,7 +253,8 @@ sk_sp<Bitmap> HardwareBitmapUploader::allocateHardwareBitmap(const SkBitmap& sou eglDestroySyncKHR(display, fence); } - return sk_sp<Bitmap>(new Bitmap(buffer.get(), bitmap.info(), Bitmap::computePalette(bitmap))); + return Bitmap::createFrom(buffer.get(), bitmap.refColorSpace(), bitmap.alphaType(), + Bitmap::computePalette(bitmap)); } } // namespace android::uirenderer diff --git a/libs/hwui/hwui/Bitmap.cpp b/libs/hwui/hwui/Bitmap.cpp index 6c77f9ee1845..6e0258c9ecb2 100644 --- a/libs/hwui/hwui/Bitmap.cpp +++ b/libs/hwui/hwui/Bitmap.cpp @@ -75,20 +75,33 @@ sk_sp<Bitmap> Bitmap::allocateAshmemBitmap(SkBitmap* bitmap) { return allocateBitmap(bitmap, &Bitmap::allocateAshmemBitmap); } -static sk_sp<Bitmap> allocateHeapBitmap(size_t size, const SkImageInfo& info, size_t rowBytes) { - void* addr = calloc(size, 1); - if (!addr) { +sk_sp<Bitmap> Bitmap::allocateAshmemBitmap(size_t size, const SkImageInfo& info, size_t rowBytes) { + // Create new ashmem region with read/write priv + int fd = ashmem_create_region("bitmap", size); + if (fd < 0) { return nullptr; } - return sk_sp<Bitmap>(new Bitmap(addr, size, info, rowBytes)); + + void* addr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (addr == MAP_FAILED) { + close(fd); + return nullptr; + } + + if (ashmem_set_prot_region(fd, PROT_READ) < 0) { + munmap(addr, size); + close(fd); + return nullptr; + } + return sk_sp<Bitmap>(new Bitmap(addr, fd, size, info, rowBytes)); } -sk_sp<Bitmap> Bitmap::allocateHardwareBitmap(SkBitmap& bitmap) { +sk_sp<Bitmap> Bitmap::allocateHardwareBitmap(const SkBitmap& bitmap) { return uirenderer::HardwareBitmapUploader::allocateHardwareBitmap(bitmap); } sk_sp<Bitmap> Bitmap::allocateHeapBitmap(SkBitmap* bitmap) { - return allocateBitmap(bitmap, &android::allocateHeapBitmap); + return allocateBitmap(bitmap, &Bitmap::allocateHeapBitmap); } sk_sp<Bitmap> Bitmap::allocateHeapBitmap(const SkImageInfo& info) { @@ -97,28 +110,15 @@ sk_sp<Bitmap> Bitmap::allocateHeapBitmap(const SkImageInfo& info) { LOG_ALWAYS_FATAL("trying to allocate too large bitmap"); return nullptr; } - return android::allocateHeapBitmap(size, info, info.minRowBytes()); + return allocateHeapBitmap(size, info, info.minRowBytes()); } -sk_sp<Bitmap> Bitmap::allocateAshmemBitmap(size_t size, const SkImageInfo& info, size_t rowBytes) { - // Create new ashmem region with read/write priv - int fd = ashmem_create_region("bitmap", size); - if (fd < 0) { - return nullptr; - } - - void* addr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - if (addr == MAP_FAILED) { - close(fd); - return nullptr; - } - - if (ashmem_set_prot_region(fd, PROT_READ) < 0) { - munmap(addr, size); - close(fd); +sk_sp<Bitmap> Bitmap::allocateHeapBitmap(size_t size, const SkImageInfo& info, size_t rowBytes) { + void* addr = calloc(size, 1); + if (!addr) { return nullptr; } - return sk_sp<Bitmap>(new Bitmap(addr, fd, size, info, rowBytes)); + return sk_sp<Bitmap>(new Bitmap(addr, size, info, rowBytes)); } void FreePixelRef(void* addr, void* context) { @@ -132,17 +132,38 @@ sk_sp<Bitmap> Bitmap::createFrom(const SkImageInfo& info, SkPixelRef& pixelRef) pixelRef.rowBytes())); } -sk_sp<Bitmap> Bitmap::createFrom(sp<GraphicBuffer> graphicBuffer) { - return createFrom(graphicBuffer, SkColorSpace::MakeSRGB()); -} -sk_sp<Bitmap> Bitmap::createFrom(sp<GraphicBuffer> graphicBuffer, sk_sp<SkColorSpace> colorSpace) { +sk_sp<Bitmap> Bitmap::createFrom(sp<GraphicBuffer> graphicBuffer, sk_sp<SkColorSpace> colorSpace, + SkAlphaType alphaType, BitmapPalette palette) { // As we will be effectively texture-sampling the buffer (using either EGL or Vulkan), we can - // view the colorspace as RGBA8888. + // view the format as RGBA8888. SkImageInfo info = SkImageInfo::Make(graphicBuffer->getWidth(), graphicBuffer->getHeight(), - kRGBA_8888_SkColorType, kPremul_SkAlphaType, - colorSpace); - return sk_sp<Bitmap>(new Bitmap(graphicBuffer.get(), info)); + kRGBA_8888_SkColorType, alphaType, colorSpace); + return sk_sp<Bitmap>(new Bitmap(graphicBuffer.get(), info, palette)); +} + +sk_sp<Bitmap> Bitmap::createFrom(const SkImageInfo& info, size_t rowBytes, int fd, void* addr, + size_t size, bool readOnly) { + if (info.colorType() == kUnknown_SkColorType) { + LOG_ALWAYS_FATAL("unknown bitmap configuration"); + return nullptr; + } + + if (!addr) { + // Map existing ashmem region if not already mapped. + int flags = readOnly ? (PROT_READ) : (PROT_READ | PROT_WRITE); + size = ashmem_get_size_region(fd); + addr = mmap(NULL, size, flags, MAP_SHARED, fd, 0); + if (addr == MAP_FAILED) { + return nullptr; + } + } + + sk_sp<Bitmap> bitmap(new Bitmap(addr, fd, size, info, rowBytes)); + if (readOnly) { + bitmap->setImmutable(); + } + return bitmap; } void Bitmap::setColorSpace(sk_sp<SkColorSpace> colorSpace) { diff --git a/libs/hwui/hwui/Bitmap.h b/libs/hwui/hwui/Bitmap.h index d446377ec1d9..2138040d9690 100644 --- a/libs/hwui/hwui/Bitmap.h +++ b/libs/hwui/hwui/Bitmap.h @@ -54,28 +54,31 @@ typedef void (*FreeFunc)(void* addr, void* context); class ANDROID_API Bitmap : public SkPixelRef { public: + /* The allocate factories not only construct the Bitmap object but also allocate the + * backing store whose type is determined by the specific method that is called. + * + * The factories that accept SkBitmap* as a param will modify those params by + * installing the returned bitmap as their SkPixelRef. + * + * The factories that accept const SkBitmap& as a param will copy the contents of the + * provided bitmap into the newly allocated buffer. + */ + static sk_sp<Bitmap> allocateAshmemBitmap(SkBitmap* bitmap); + static sk_sp<Bitmap> allocateHardwareBitmap(const SkBitmap& bitmap); static sk_sp<Bitmap> allocateHeapBitmap(SkBitmap* bitmap); static sk_sp<Bitmap> allocateHeapBitmap(const SkImageInfo& info); - static sk_sp<Bitmap> allocateHardwareBitmap(SkBitmap& bitmap); - - static sk_sp<Bitmap> allocateAshmemBitmap(SkBitmap* bitmap); - static sk_sp<Bitmap> allocateAshmemBitmap(size_t allocSize, const SkImageInfo& info, - size_t rowBytes); - - static sk_sp<Bitmap> createFrom(sp<GraphicBuffer> graphicBuffer); + /* The createFrom factories construct a new Bitmap object by wrapping the already allocated + * memory that is provided as an input param. + */ static sk_sp<Bitmap> createFrom(sp<GraphicBuffer> graphicBuffer, - sk_sp<SkColorSpace> colorSpace); - + sk_sp<SkColorSpace> colorSpace, + SkAlphaType alphaType = kPremul_SkAlphaType, + BitmapPalette palette = BitmapPalette::Unknown); + static sk_sp<Bitmap> createFrom(const SkImageInfo& info, size_t rowBytes, int fd, void* addr, + size_t size, bool readOnly); static sk_sp<Bitmap> createFrom(const SkImageInfo&, SkPixelRef&); - Bitmap(void* address, size_t allocSize, const SkImageInfo& info, size_t rowBytes); - Bitmap(void* address, void* context, FreeFunc freeFunc, const SkImageInfo& info, - size_t rowBytes); - Bitmap(void* address, int fd, size_t mappedSize, const SkImageInfo& info, size_t rowBytes); - Bitmap(GraphicBuffer* buffer, const SkImageInfo& info, - BitmapPalette palette = BitmapPalette::Unknown); - int rowBytesAsPixels() const { return rowBytes() >> mInfo.shiftPerPixel(); } void reconfigure(const SkImageInfo& info, size_t rowBytes); @@ -123,6 +126,15 @@ public: } private: + static sk_sp<Bitmap> allocateAshmemBitmap(size_t size, const SkImageInfo& i, size_t rowBytes); + static sk_sp<Bitmap> allocateHeapBitmap(size_t size, const SkImageInfo& i, size_t rowBytes); + + Bitmap(void* address, size_t allocSize, const SkImageInfo& info, size_t rowBytes); + Bitmap(void* address, void* context, FreeFunc freeFunc, const SkImageInfo& info, + size_t rowBytes); + Bitmap(void* address, int fd, size_t mappedSize, const SkImageInfo& info, size_t rowBytes); + Bitmap(GraphicBuffer* buffer, const SkImageInfo& info, BitmapPalette palette); + virtual ~Bitmap(); void* getStorage() const; diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp index c67134cddb8a..1d3a24463057 100644 --- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp @@ -158,7 +158,7 @@ sk_sp<Bitmap> SkiaVulkanPipeline::allocateHardwareBitmap(renderthread::RenderThr ALOGW("SkiaVulkanPipeline::allocateHardwareBitmap() failed in GraphicBuffer.create()"); return nullptr; } - return sk_sp<Bitmap>(new Bitmap(buffer.get(), skBitmap.info())); + return Bitmap::createFrom(buffer, skBitmap.refColorSpace()); } } /* namespace skiapipeline */ diff --git a/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp b/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp index 448408d19eb1..ec81f629ee45 100644 --- a/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp +++ b/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp @@ -50,7 +50,7 @@ public: pixels[4000 + 4 * i + 3] = 255; } buffer->unlock(); - sk_sp<Bitmap> hardwareBitmap(Bitmap::createFrom(buffer)); + sk_sp<Bitmap> hardwareBitmap(Bitmap::createFrom(buffer, SkColorSpace::MakeSRGB())); sk_sp<SkShader> hardwareShader(createBitmapShader(*hardwareBitmap)); SkPoint center; diff --git a/libs/hwui/utils/Color.h b/libs/hwui/utils/Color.h index 4daccda78e23..4473ce632b1b 100644 --- a/libs/hwui/utils/Color.h +++ b/libs/hwui/utils/Color.h @@ -17,6 +17,7 @@ #define COLOR_H #include <math.h> +#include <cutils/compiler.h> #include <system/graphics.h> #include <ui/PixelFormat.h> @@ -117,7 +118,7 @@ bool transferFunctionCloseToSRGB(const SkColorSpace* colorSpace); android::PixelFormat ColorTypeToPixelFormat(SkColorType colorType); -sk_sp<SkColorSpace> DataSpaceToColorSpace(android_dataspace dataspace); +ANDROID_API sk_sp<SkColorSpace> DataSpaceToColorSpace(android_dataspace dataspace); struct Lab { float L; diff --git a/location/tests/locationtests/Android.mk b/location/tests/locationtests/Android.mk index b2fd8ecef734..3dcf69426362 100644 --- a/location/tests/locationtests/Android.mk +++ b/location/tests/locationtests/Android.mk @@ -12,7 +12,7 @@ LOCAL_PACKAGE_NAME := FrameworksLocationTests LOCAL_PRIVATE_PLATFORM_APIS := true LOCAL_STATIC_JAVA_LIBRARIES := \ - android-support-test \ + androidx.test.rules \ core-test-rules \ guava \ mockito-target-minus-junit4 \ diff --git a/location/tests/locationtests/AndroidManifest.xml b/location/tests/locationtests/AndroidManifest.xml index ddb8ea6aa53a..5010d3d56a50 100644 --- a/location/tests/locationtests/AndroidManifest.xml +++ b/location/tests/locationtests/AndroidManifest.xml @@ -29,7 +29,7 @@ </application> <instrumentation - android:name="android.support.test.runner.AndroidJUnitRunner" + android:name="androidx.test.runner.AndroidJUnitRunner" android:targetPackage="com.android.frameworks.locationtests" android:label="Frameworks Location Tests" /> </manifest> diff --git a/location/tests/locationtests/AndroidTest.xml b/location/tests/locationtests/AndroidTest.xml index bb6547bec0f7..7bddb58f2cf2 100644 --- a/location/tests/locationtests/AndroidTest.xml +++ b/location/tests/locationtests/AndroidTest.xml @@ -22,7 +22,7 @@ <option name="test-tag" value="FrameworksLocationTests" /> <test class="com.android.tradefed.testtype.AndroidJUnitTest" > <option name="package" value="com.android.frameworks.locationtests" /> - <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" /> + <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> <option name="hidden-api-checks" value="false"/> </test> </configuration> diff --git a/media/java/android/media/MediaItem2.java b/media/java/android/media/MediaItem2.java new file mode 100644 index 000000000000..aa2a937956c5 --- /dev/null +++ b/media/java/android/media/MediaItem2.java @@ -0,0 +1,324 @@ +/* + * Copyright 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.media; + +import static android.media.MediaMetadata.METADATA_KEY_MEDIA_ID; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; +import android.util.Log; +import android.util.Pair; + +import com.android.internal.annotations.GuardedBy; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Executor; + +/** + * A class with information on a single media item with the metadata information. Here are use + * cases. + * <ul> + * <li>Specify media items to {@link SessionPlayer2} for playback. + * <li>Share media items across the processes. + * </ul> + * <p> + * Subclasses of the session player may only accept certain subclasses of the media items. Check + * the player documentation that you're interested in. + * <p> + * When it's shared across the processes, we cannot guarantee that they contain the right values + * because media items are application dependent especially for the metadata. + * <p> + * This object is thread safe. + * <p> + * This API is not generally intended for third party application developers. + * Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a> + * {@link androidx.media2.MediaItem} for consistent behavior across all devices. + * </p> + * @hide + */ +public class MediaItem2 implements Parcelable { + private static final String TAG = "MediaItem2"; + + // intentionally less than long.MAX_VALUE. + // Declare this first to avoid 'illegal forward reference'. + static final long LONG_MAX = 0x7ffffffffffffffL; + + /** + * Used when a position is unknown. + * + * @see #getEndPosition() + */ + public static final long POSITION_UNKNOWN = LONG_MAX; + + public static final Parcelable.Creator<MediaItem2> CREATOR = + new Parcelable.Creator<MediaItem2>() { + @Override + public MediaItem2 createFromParcel(Parcel in) { + return new MediaItem2(in); + } + + @Override + public MediaItem2[] newArray(int size) { + return new MediaItem2[size]; + } + }; + + // TODO: Use SessionPlayer2.UNKNOWN_TIME instead + private static final long UNKNOWN_TIME = -1; + + private final long mStartPositionMs; + private final long mEndPositionMs; + + private final Object mLock = new Object(); + + @GuardedBy("mLock") + private MediaMetadata mMetadata; + @GuardedBy("mLock") + private final List<Pair<OnMetadataChangedListener, Executor>> mListeners = new ArrayList<>(); + + /** + * Used by {@link MediaItem2.Builder}. + */ + // Note: Needs to be protected when we want to allow 3rd party player to define customized + // MediaItem2. + @SuppressWarnings("WeakerAccess") /* synthetic access */ + MediaItem2(Builder builder) { + this(builder.mMetadata, builder.mStartPositionMs, builder.mEndPositionMs); + } + + /** + * Used by Parcelable.Creator. + */ + // Note: Needs to be protected when we want to allow 3rd party player to define customized + // MediaItem2. + @SuppressWarnings("WeakerAccess") /* synthetic access */ + MediaItem2(Parcel in) { + this(in.readParcelable(MediaItem2.class.getClassLoader()), in.readLong(), in.readLong()); + } + + @SuppressWarnings("WeakerAccess") /* synthetic access */ + MediaItem2(MediaItem2 item) { + this(item.mMetadata, item.mStartPositionMs, item.mEndPositionMs); + } + + @SuppressWarnings("WeakerAccess") /* synthetic access */ + MediaItem2(@Nullable MediaMetadata metadata, long startPositionMs, long endPositionMs) { + if (startPositionMs > endPositionMs) { + throw new IllegalArgumentException("Illegal start/end position: " + + startPositionMs + " : " + endPositionMs); + } + if (metadata != null && metadata.containsKey(MediaMetadata.METADATA_KEY_DURATION)) { + long durationMs = metadata.getLong(MediaMetadata.METADATA_KEY_DURATION); + if (durationMs != UNKNOWN_TIME && endPositionMs != POSITION_UNKNOWN + && endPositionMs > durationMs) { + throw new IllegalArgumentException("endPositionMs shouldn't be greater than" + + " duration in the metdata, endPositionMs=" + endPositionMs + + ", durationMs=" + durationMs); + } + } + mMetadata = metadata; + mStartPositionMs = startPositionMs; + mEndPositionMs = endPositionMs; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(getClass().getSimpleName()); + synchronized (mLock) { + sb.append("{mMetadata=").append(mMetadata); + sb.append(", mStartPositionMs=").append(mStartPositionMs); + sb.append(", mEndPositionMs=").append(mEndPositionMs); + sb.append('}'); + } + return sb.toString(); + } + + /** + * Sets metadata. If the metadata is not {@code null}, its id should be matched with this + * instance's media id. + * + * @param metadata metadata to update + * @see MediaMetadata#METADATA_KEY_MEDIA_ID + */ + public void setMetadata(@Nullable MediaMetadata metadata) { + List<Pair<OnMetadataChangedListener, Executor>> listeners = new ArrayList<>(); + synchronized (mLock) { + if (mMetadata != null && metadata != null + && !TextUtils.equals(getMediaId(), metadata.getString(METADATA_KEY_MEDIA_ID))) { + Log.d(TAG, "MediaItem2's media ID shouldn't be changed"); + return; + } + mMetadata = metadata; + listeners.addAll(mListeners); + } + + for (Pair<OnMetadataChangedListener, Executor> pair : listeners) { + final OnMetadataChangedListener listener = pair.first; + pair.second.execute(new Runnable() { + @Override + public void run() { + listener.onMetadataChanged(MediaItem2.this); + } + }); + } + } + + /** + * Gets the metadata of the media. + * + * @return metadata from the session + */ + public @Nullable MediaMetadata getMetadata() { + synchronized (mLock) { + return mMetadata; + } + } + + /** + * Return the position in milliseconds at which the playback will start. + * @return the position in milliseconds at which the playback will start + */ + public long getStartPosition() { + return mStartPositionMs; + } + + /** + * Return the position in milliseconds at which the playback will end. + * {@link #POSITION_UNKNOWN} means ending at the end of source content. + * @return the position in milliseconds at which the playback will end + */ + public long getEndPosition() { + return mEndPositionMs; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeParcelable(mMetadata, 0); + dest.writeLong(mStartPositionMs); + dest.writeLong(mEndPositionMs); + } + + /** + * Gets the media id for this item. If it's not {@code null}, it's a persistent unique key + * for the underlying media content. + * + * @return media Id from the session + */ + @Nullable String getMediaId() { + synchronized (mLock) { + return mMetadata != null + ? mMetadata.getString(METADATA_KEY_MEDIA_ID) : null; + } + } + + void addOnMetadataChangedListener(Executor executor, OnMetadataChangedListener listener) { + synchronized (mLock) { + for (Pair<OnMetadataChangedListener, Executor> pair : mListeners) { + if (pair.first == listener) { + return; + } + } + mListeners.add(new Pair<>(listener, executor)); + } + } + + void removeOnMetadataChangedListener(OnMetadataChangedListener listener) { + synchronized (mLock) { + for (int i = mListeners.size() - 1; i >= 0; i--) { + if (mListeners.get(i).first == listener) { + mListeners.remove(i); + return; + } + } + } + } + + /** + * Builder for {@link MediaItem2}. + */ + public static class Builder { + @SuppressWarnings("WeakerAccess") /* synthetic access */ + MediaMetadata mMetadata; + @SuppressWarnings("WeakerAccess") /* synthetic access */ + long mStartPositionMs = 0; + @SuppressWarnings("WeakerAccess") /* synthetic access */ + long mEndPositionMs = POSITION_UNKNOWN; + + /** + * Set the metadata of this instance. {@code null} for unset. + * + * @param metadata metadata + * @return this instance for chaining + */ + public @NonNull Builder setMetadata(@Nullable MediaMetadata metadata) { + mMetadata = metadata; + return this; + } + + /** + * Sets the start position in milliseconds at which the playback will start. + * Any negative number is treated as 0. + * + * @param position the start position in milliseconds at which the playback will start + * @return the same Builder instance. + */ + public @NonNull Builder setStartPosition(long position) { + if (position < 0) { + position = 0; + } + mStartPositionMs = position; + return this; + } + + /** + * Sets the end position in milliseconds at which the playback will end. + * Any negative number is treated as maximum length of the media item. + * + * @param position the end position in milliseconds at which the playback will end + * @return the same Builder instance. + */ + public @NonNull Builder setEndPosition(long position) { + if (position < 0) { + position = POSITION_UNKNOWN; + } + mEndPositionMs = position; + return this; + } + + /** + * Build {@link MediaItem2}. + * + * @return a new {@link MediaItem2}. + */ + public @NonNull MediaItem2 build() { + return new MediaItem2(this); + } + } + + interface OnMetadataChangedListener { + void onMetadataChanged(MediaItem2 item); + } +} diff --git a/media/java/android/media/MediaMetadataRetriever.java b/media/java/android/media/MediaMetadataRetriever.java index 00a393a902b4..d656fa359826 100644 --- a/media/java/android/media/MediaMetadataRetriever.java +++ b/media/java/android/media/MediaMetadataRetriever.java @@ -40,8 +40,7 @@ import java.util.Map; * MediaMetadataRetriever class provides a unified interface for retrieving * frame and meta data from an input media file. */ -public class MediaMetadataRetriever -{ +public class MediaMetadataRetriever implements AutoCloseable { static { System.loadLibrary("media_jni"); native_init(); @@ -672,6 +671,11 @@ public class MediaMetadataRetriever @UnsupportedAppUsage private native byte[] getEmbeddedPicture(int pictureType); + @Override + public void close() { + release(); + } + /** * Call it when one is done with the object. This method releases the memory * allocated internally. diff --git a/media/java/android/media/ThumbnailUtils.java b/media/java/android/media/ThumbnailUtils.java index fd1406078e7a..f07076ad14aa 100644 --- a/media/java/android/media/ThumbnailUtils.java +++ b/media/java/android/media/ThumbnailUtils.java @@ -16,33 +16,53 @@ package android.media; +import static android.media.MediaMetadataRetriever.METADATA_KEY_DURATION; +import static android.media.MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT; +import static android.media.MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH; +import static android.media.MediaMetadataRetriever.OPTION_CLOSEST_SYNC; +import static android.os.Environment.MEDIA_UNKNOWN; + +import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.UnsupportedAppUsage; import android.content.ContentResolver; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; +import android.graphics.ImageDecoder; +import android.graphics.ImageDecoder.ImageInfo; +import android.graphics.ImageDecoder.Source; import android.graphics.Matrix; +import android.graphics.Point; import android.graphics.Rect; import android.net.Uri; +import android.os.CancellationSignal; +import android.os.Environment; import android.os.ParcelFileDescriptor; -import android.provider.MediaStore.Images; +import android.provider.MediaStore.ThumbnailConstants; import android.util.Log; +import android.util.Size; -import java.io.FileDescriptor; -import java.io.FileInputStream; +import com.android.internal.util.ArrayUtils; + +import libcore.io.IoUtils; + +import java.io.File; import java.io.IOException; +import java.util.Arrays; +import java.util.Comparator; +import java.util.function.ToIntFunction; /** - * Thumbnail generation routines for media provider. + * Utilities for generating visual thumbnails from files. */ - public class ThumbnailUtils { private static final String TAG = "ThumbnailUtils"; - /* Maximum pixels size for created bitmap. */ - private static final int MAX_NUM_PIXELS_THUMBNAIL = 512 * 384; - private static final int MAX_NUM_PIXELS_MICRO_THUMBNAIL = 160 * 120; - private static final int UNCONSTRAINED = -1; + /** @hide */ + @Deprecated + @UnsupportedAppUsage + public static final int TARGET_SIZE_MICRO_THUMBNAIL = 96; /* Options used internally. */ private static final int OPTIONS_NONE = 0x0; @@ -54,153 +74,252 @@ public class ThumbnailUtils { */ public static final int OPTIONS_RECYCLE_INPUT = 0x2; + private static Size convertKind(int kind) { + if (kind == ThumbnailConstants.MICRO_KIND) { + return Point.convert(ThumbnailConstants.MICRO_SIZE); + } else if (kind == ThumbnailConstants.FULL_SCREEN_KIND) { + return Point.convert(ThumbnailConstants.FULL_SCREEN_SIZE); + } else if (kind == ThumbnailConstants.MINI_KIND) { + return Point.convert(ThumbnailConstants.MINI_SIZE); + } else { + throw new IllegalArgumentException("Unsupported kind: " + kind); + } + } + + private static class Resizer implements ImageDecoder.OnHeaderDecodedListener { + private final Size size; + private final CancellationSignal signal; + + public Resizer(Size size, CancellationSignal signal) { + this.size = size; + this.signal = signal; + } + + @Override + public void onHeaderDecoded(ImageDecoder decoder, ImageInfo info, Source source) { + // One last-ditch check to see if we've been canceled. + if (signal != null) signal.throwIfCanceled(); + + // We don't know how clients will use the decoded data, so we have + // to default to the more flexible "software" option. + decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE); + + // We requested a rough thumbnail size, but the remote size may have + // returned something giant, so defensively scale down as needed. + final int widthSample = info.getSize().getWidth() / size.getWidth(); + final int heightSample = info.getSize().getHeight() / size.getHeight(); + final int sample = Math.max(widthSample, heightSample); + if (sample > 1) { + decoder.setTargetSampleSize(sample); + } + } + } + /** - * Constant used to indicate the dimension of mini thumbnail. - * @hide Only used by media framework and media provider internally. + * Create a thumbnail for given audio file. + * + * @param filePath The audio file. + * @param kind The desired thumbnail kind, such as + * {@link android.provider.MediaStore.Images.Thumbnails#MINI_KIND}. */ - public static final int TARGET_SIZE_MINI_THUMBNAIL = 320; + @Deprecated + public static @Nullable Bitmap createAudioThumbnail(@NonNull String filePath, int kind) { + try { + return createAudioThumbnail(new File(filePath), convertKind(kind), null); + } catch (IOException e) { + Log.w(TAG, e); + return null; + } + } /** - * Constant used to indicate the dimension of micro thumbnail. - * @hide Only used by media framework and media provider internally. + * Create a thumbnail for given audio file. + * + * @param file The audio file. + * @param size The desired thumbnail size. + * @throws IOException If any trouble was encountered while generating or + * loading the thumbnail, or if + * {@link CancellationSignal#cancel()} was invoked. */ - @UnsupportedAppUsage - public static final int TARGET_SIZE_MICRO_THUMBNAIL = 96; + public static @NonNull Bitmap createAudioThumbnail(@NonNull File file, @NonNull Size size, + @Nullable CancellationSignal signal) throws IOException { + // Checkpoint before going deeper + if (signal != null) signal.throwIfCanceled(); + + final Resizer resizer = new Resizer(size, signal); + try (MediaMetadataRetriever retriever = new MediaMetadataRetriever()) { + retriever.setDataSource(file.getAbsolutePath()); + final byte[] raw = retriever.getEmbeddedPicture(); + if (raw != null) { + return ImageDecoder.decodeBitmap(ImageDecoder.createSource(raw), resizer); + } + } catch (RuntimeException e) { + throw new IOException("Failed to create thumbnail", e); + } + + // Only poke around for files on external storage + if (MEDIA_UNKNOWN.equals(Environment.getExternalStorageState(file))) { + throw new IOException("No embedded album art found"); + } + + // Ignore "Downloads" or top-level directories + final File parent = file.getParentFile(); + final File grandParent = parent != null ? parent.getParentFile() : null; + if (parent != null + && parent.getName().equals(Environment.DIRECTORY_DOWNLOADS)) { + throw new IOException("No thumbnails in Downloads directories"); + } + if (grandParent != null + && MEDIA_UNKNOWN.equals(Environment.getExternalStorageState(grandParent))) { + throw new IOException("No thumbnails in top-level directories"); + } + + // If no embedded image found, look around for best standalone file + final File[] found = ArrayUtils + .defeatNullable(file.getParentFile().listFiles((dir, name) -> { + final String lower = name.toLowerCase(); + return (lower.endsWith(".jpg") || lower.endsWith(".png")); + })); + + final ToIntFunction<File> score = (f) -> { + final String lower = f.getName().toLowerCase(); + if (lower.equals("albumart.jpg")) return 4; + if (lower.startsWith("albumart") && lower.endsWith(".jpg")) return 3; + if (lower.contains("albumart") && lower.endsWith(".jpg")) return 2; + if (lower.endsWith(".jpg")) return 1; + return 0; + }; + final Comparator<File> bestScore = (a, b) -> { + return score.applyAsInt(a) - score.applyAsInt(b); + }; + + final File bestFile = Arrays.asList(found).stream().max(bestScore).orElse(null); + if (bestFile == null) { + throw new IOException("No album art found"); + } + + // Checkpoint before going deeper + if (signal != null) signal.throwIfCanceled(); + + return ImageDecoder.decodeBitmap(ImageDecoder.createSource(bestFile), resizer); + } /** - * This method first examines if the thumbnail embedded in EXIF is bigger than our target - * size. If not, then it'll create a thumbnail from original image. Due to efficiency - * consideration, we want to let MediaThumbRequest avoid calling this method twice for - * both kinds, so it only requests for MICRO_KIND and set saveImage to true. - * - * This method always returns a "square thumbnail" for MICRO_KIND thumbnail. + * Create a thumbnail for given image file. * - * @param filePath the path of image file - * @param kind could be MINI_KIND or MICRO_KIND - * @return Bitmap, or null on failures + * @param filePath The image file. + * @param kind The desired thumbnail kind, such as + * {@link android.provider.MediaStore.Images.Thumbnails#MINI_KIND}. + */ + @Deprecated + public static @Nullable Bitmap createImageThumbnail(@NonNull String filePath, int kind) { + try { + return createImageThumbnail(new File(filePath), convertKind(kind), null); + } catch (IOException e) { + Log.w(TAG, e); + return null; + } + } + + /** + * Create a thumbnail for given image file. * - * @hide This method is only used by media framework and media provider internally. + * @param file The audio file. + * @param size The desired thumbnail size. + * @throws IOException If any trouble was encountered while generating or + * loading the thumbnail, or if + * {@link CancellationSignal#cancel()} was invoked. */ - @UnsupportedAppUsage - public static Bitmap createImageThumbnail(String filePath, int kind) { - boolean wantMini = (kind == Images.Thumbnails.MINI_KIND); - int targetSize = wantMini - ? TARGET_SIZE_MINI_THUMBNAIL - : TARGET_SIZE_MICRO_THUMBNAIL; - int maxPixels = wantMini - ? MAX_NUM_PIXELS_THUMBNAIL - : MAX_NUM_PIXELS_MICRO_THUMBNAIL; - SizedThumbnailBitmap sizedThumbnailBitmap = new SizedThumbnailBitmap(); - Bitmap bitmap = null; - String mimeType = MediaFile.getMimeTypeForFile(filePath); + public static @NonNull Bitmap createImageThumbnail(@NonNull File file, @NonNull Size size, + @Nullable CancellationSignal signal) throws IOException { + // Checkpoint before going deeper + if (signal != null) signal.throwIfCanceled(); + + final Resizer resizer = new Resizer(size, signal); + final String mimeType = MediaFile.getMimeTypeForFile(file.getName()); if (mimeType.equals("image/heif") || mimeType.equals("image/heif-sequence") || mimeType.equals("image/heic") || mimeType.equals("image/heic-sequence")) { - bitmap = createThumbnailFromMetadataRetriever(filePath, targetSize, maxPixels); + try (MediaMetadataRetriever retriever = new MediaMetadataRetriever()) { + retriever.setDataSource(file.getAbsolutePath()); + return retriever.getThumbnailImageAtIndex(-1, + new MediaMetadataRetriever.BitmapParams(), size.getWidth(), + size.getWidth() * size.getHeight()); + } catch (RuntimeException e) { + throw new IOException("Failed to create thumbnail", e); + } } else if (MediaFile.isExifMimeType(mimeType)) { - createThumbnailFromEXIF(filePath, targetSize, maxPixels, sizedThumbnailBitmap); - bitmap = sizedThumbnailBitmap.mBitmap; + final ExifInterface exif = new ExifInterface(file); + final byte[] raw = exif.getThumbnailBytes(); + if (raw != null) { + return ImageDecoder.decodeBitmap(ImageDecoder.createSource(raw), resizer); + } } - if (bitmap == null) { - FileInputStream stream = null; - try { - stream = new FileInputStream(filePath); - FileDescriptor fd = stream.getFD(); - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inSampleSize = 1; - options.inJustDecodeBounds = true; - BitmapFactory.decodeFileDescriptor(fd, null, options); - if (options.mCancel || options.outWidth == -1 - || options.outHeight == -1) { - return null; - } - options.inSampleSize = computeSampleSize( - options, targetSize, maxPixels); - options.inJustDecodeBounds = false; - - options.inDither = false; - options.inPreferredConfig = Bitmap.Config.ARGB_8888; - bitmap = BitmapFactory.decodeFileDescriptor(fd, null, options); - } catch (IOException ex) { - Log.e(TAG, "", ex); - } catch (OutOfMemoryError oom) { - Log.e(TAG, "Unable to decode file " + filePath + ". OutOfMemoryError.", oom); - } finally { - try { - if (stream != null) { - stream.close(); - } - } catch (IOException ex) { - Log.e(TAG, "", ex); - } - } + // Checkpoint before going deeper + if (signal != null) signal.throwIfCanceled(); - } + return ImageDecoder.decodeBitmap(ImageDecoder.createSource(file), resizer); + } - if (kind == Images.Thumbnails.MICRO_KIND) { - // now we make it a "square thumbnail" for MICRO_KIND thumbnail - bitmap = extractThumbnail(bitmap, - TARGET_SIZE_MICRO_THUMBNAIL, - TARGET_SIZE_MICRO_THUMBNAIL, OPTIONS_RECYCLE_INPUT); + /** + * Create a thumbnail for given video file. + * + * @param filePath The video file. + * @param kind The desired thumbnail kind, such as + * {@link android.provider.MediaStore.Images.Thumbnails#MINI_KIND}. + */ + @Deprecated + public static @Nullable Bitmap createVideoThumbnail(@NonNull String filePath, int kind) { + try { + return createVideoThumbnail(new File(filePath), convertKind(kind), null); + } catch (IOException e) { + Log.w(TAG, e); + return null; } - return bitmap; } /** - * Create a video thumbnail for a video. May return null if the video is - * corrupt or the format is not supported. + * Create a thumbnail for given video file. * - * @param filePath the path of video file - * @param kind could be MINI_KIND or MICRO_KIND + * @param file The video file. + * @param size The desired thumbnail size. + * @throws IOException If any trouble was encountered while generating or + * loading the thumbnail, or if + * {@link CancellationSignal#cancel()} was invoked. */ - public static Bitmap createVideoThumbnail(String filePath, int kind) { - Bitmap bitmap = null; - MediaMetadataRetriever retriever = new MediaMetadataRetriever(); - try { - retriever.setDataSource(filePath); - // First retrieve album art in metadata if set. - byte[] embeddedPicture = retriever.getEmbeddedPicture(); - if (embeddedPicture != null && embeddedPicture.length > 0) { - bitmap = BitmapFactory.decodeByteArray(embeddedPicture, 0, embeddedPicture.length); + public static @NonNull Bitmap createVideoThumbnail(@NonNull File file, @NonNull Size size, + @Nullable CancellationSignal signal) throws IOException { + // Checkpoint before going deeper + if (signal != null) signal.throwIfCanceled(); + + final Resizer resizer = new Resizer(size, signal); + try (MediaMetadataRetriever mmr = new MediaMetadataRetriever()) { + mmr.setDataSource(file.getAbsolutePath()); + + // Try to retrieve thumbnail from metadata + final byte[] raw = mmr.getEmbeddedPicture(); + if (raw != null) { + return ImageDecoder.decodeBitmap(ImageDecoder.createSource(raw), resizer); } - // Fall back to first frame of the video. - if (bitmap == null) { - bitmap = retriever.getFrameAtTime(-1); - } - } catch (IllegalArgumentException ex) { - // Assume this is a corrupt video file - } catch (RuntimeException ex) { - // Assume this is a corrupt video file. - } finally { - try { - retriever.release(); - } catch (RuntimeException ex) { - // Ignore failures while cleaning up. - } - } - if (bitmap == null) return null; - - if (kind == Images.Thumbnails.MINI_KIND) { - // Scale down the bitmap if it's too large. - int width = bitmap.getWidth(); - int height = bitmap.getHeight(); - int max = Math.max(width, height); - if (max > 512) { - float scale = 512f / max; - int w = Math.round(scale * width); - int h = Math.round(scale * height); - bitmap = Bitmap.createScaledBitmap(bitmap, w, h, true); + // Fall back to middle of video + final int width = Integer.parseInt(mmr.extractMetadata(METADATA_KEY_VIDEO_WIDTH)); + final int height = Integer.parseInt(mmr.extractMetadata(METADATA_KEY_VIDEO_HEIGHT)); + final long duration = Long.parseLong(mmr.extractMetadata(METADATA_KEY_DURATION)); + + // If we're okay with something larger than native format, just + // return a frame without up-scaling it + if (size.getWidth() > width && size.getHeight() > height) { + return mmr.getFrameAtTime(duration / 2, OPTION_CLOSEST_SYNC); + } else { + return mmr.getScaledFrameAtTime(duration / 2, OPTION_CLOSEST_SYNC, + size.getWidth(), size.getHeight()); } - } else if (kind == Images.Thumbnails.MICRO_KIND) { - bitmap = extractThumbnail(bitmap, - TARGET_SIZE_MICRO_THUMBNAIL, - TARGET_SIZE_MICRO_THUMBNAIL, - OPTIONS_RECYCLE_INPUT); + } catch (RuntimeException e) { + throw new IOException("Failed to create thumbnail", e); } - return bitmap; } /** @@ -242,122 +361,27 @@ public class ThumbnailUtils { return thumbnail; } - /* - * Compute the sample size as a function of minSideLength - * and maxNumOfPixels. - * minSideLength is used to specify that minimal width or height of a - * bitmap. - * maxNumOfPixels is used to specify the maximal size in pixels that is - * tolerable in terms of memory usage. - * - * The function returns a sample size based on the constraints. - * Both size and minSideLength can be passed in as IImage.UNCONSTRAINED, - * which indicates no care of the corresponding constraint. - * The functions prefers returning a sample size that - * generates a smaller bitmap, unless minSideLength = IImage.UNCONSTRAINED. - * - * Also, the function rounds up the sample size to a power of 2 or multiple - * of 8 because BitmapFactory only honors sample size this way. - * For example, BitmapFactory downsamples an image by 2 even though the - * request is 3. So we round up the sample size to avoid OOM. - */ + @Deprecated @UnsupportedAppUsage private static int computeSampleSize(BitmapFactory.Options options, int minSideLength, int maxNumOfPixels) { - int initialSize = computeInitialSampleSize(options, minSideLength, - maxNumOfPixels); - - int roundedSize; - if (initialSize <= 8 ) { - roundedSize = 1; - while (roundedSize < initialSize) { - roundedSize <<= 1; - } - } else { - roundedSize = (initialSize + 7) / 8 * 8; - } - - return roundedSize; + return 1; } + @Deprecated @UnsupportedAppUsage private static int computeInitialSampleSize(BitmapFactory.Options options, int minSideLength, int maxNumOfPixels) { - double w = options.outWidth; - double h = options.outHeight; - - int lowerBound = (maxNumOfPixels == UNCONSTRAINED) ? 1 : - (int) Math.ceil(Math.sqrt(w * h / maxNumOfPixels)); - int upperBound = (minSideLength == UNCONSTRAINED) ? 128 : - (int) Math.min(Math.floor(w / minSideLength), - Math.floor(h / minSideLength)); - - if (upperBound < lowerBound) { - // return the larger one when there is no overlapping zone. - return lowerBound; - } - - if ((maxNumOfPixels == UNCONSTRAINED) && - (minSideLength == UNCONSTRAINED)) { - return 1; - } else if (minSideLength == UNCONSTRAINED) { - return lowerBound; - } else { - return upperBound; - } - } - - /** - * Make a bitmap from a given Uri, minimal side length, and maximum number of pixels. - * The image data will be read from specified pfd if it's not null, otherwise - * a new input stream will be created using specified ContentResolver. - * - * Clients are allowed to pass their own BitmapFactory.Options used for bitmap decoding. A - * new BitmapFactory.Options will be created if options is null. - */ - private static Bitmap makeBitmap(int minSideLength, int maxNumOfPixels, - Uri uri, ContentResolver cr, ParcelFileDescriptor pfd, - BitmapFactory.Options options) { - Bitmap b = null; - try { - if (pfd == null) pfd = makeInputStream(uri, cr); - if (pfd == null) return null; - if (options == null) options = new BitmapFactory.Options(); - - FileDescriptor fd = pfd.getFileDescriptor(); - options.inSampleSize = 1; - options.inJustDecodeBounds = true; - BitmapFactory.decodeFileDescriptor(fd, null, options); - if (options.mCancel || options.outWidth == -1 - || options.outHeight == -1) { - return null; - } - options.inSampleSize = computeSampleSize( - options, minSideLength, maxNumOfPixels); - options.inJustDecodeBounds = false; - - options.inDither = false; - options.inPreferredConfig = Bitmap.Config.ARGB_8888; - b = BitmapFactory.decodeFileDescriptor(fd, null, options); - } catch (OutOfMemoryError ex) { - Log.e(TAG, "Got oom exception ", ex); - return null; - } finally { - closeSilently(pfd); - } - return b; + return 1; } + @Deprecated @UnsupportedAppUsage private static void closeSilently(ParcelFileDescriptor c) { - if (c == null) return; - try { - c.close(); - } catch (Throwable t) { - // do nothing - } + IoUtils.closeQuietly(c); } + @Deprecated @UnsupportedAppUsage private static ParcelFileDescriptor makeInputStream( Uri uri, ContentResolver cr) { @@ -371,6 +395,7 @@ public class ThumbnailUtils { /** * Transform source Bitmap to targeted width and height. */ + @Deprecated @UnsupportedAppUsage private static Bitmap transform(Matrix scaler, Bitmap source, @@ -468,14 +493,7 @@ public class ThumbnailUtils { return b2; } - /** - * SizedThumbnailBitmap contains the bitmap, which is downsampled either from - * the thumbnail in exif or the full image. - * mThumbnailData, mThumbnailWidth and mThumbnailHeight are set together only if mThumbnail - * is not null. - * - * The width/height of the sized bitmap may be different from mThumbnailWidth/mThumbnailHeight. - */ + @Deprecated private static class SizedThumbnailBitmap { public byte[] mThumbnailData; public Bitmap mBitmap; @@ -483,81 +501,9 @@ public class ThumbnailUtils { public int mThumbnailHeight; } - /** - * Creates a bitmap by either downsampling from the thumbnail in EXIF or the full image. - * The functions returns a SizedThumbnailBitmap, - * which contains a downsampled bitmap and the thumbnail data in EXIF if exists. - */ + @Deprecated @UnsupportedAppUsage private static void createThumbnailFromEXIF(String filePath, int targetSize, int maxPixels, SizedThumbnailBitmap sizedThumbBitmap) { - if (filePath == null) return; - - ExifInterface exif = null; - byte [] thumbData = null; - try { - exif = new ExifInterface(filePath); - thumbData = exif.getThumbnail(); - } catch (IOException ex) { - Log.w(TAG, ex); - } - - BitmapFactory.Options fullOptions = new BitmapFactory.Options(); - BitmapFactory.Options exifOptions = new BitmapFactory.Options(); - int exifThumbWidth = 0; - int fullThumbWidth = 0; - - // Compute exifThumbWidth. - if (thumbData != null) { - exifOptions.inJustDecodeBounds = true; - BitmapFactory.decodeByteArray(thumbData, 0, thumbData.length, exifOptions); - exifOptions.inSampleSize = computeSampleSize(exifOptions, targetSize, maxPixels); - exifThumbWidth = exifOptions.outWidth / exifOptions.inSampleSize; - } - - // Compute fullThumbWidth. - fullOptions.inJustDecodeBounds = true; - BitmapFactory.decodeFile(filePath, fullOptions); - fullOptions.inSampleSize = computeSampleSize(fullOptions, targetSize, maxPixels); - fullThumbWidth = fullOptions.outWidth / fullOptions.inSampleSize; - - // Choose the larger thumbnail as the returning sizedThumbBitmap. - if (thumbData != null && exifThumbWidth >= fullThumbWidth) { - int width = exifOptions.outWidth; - int height = exifOptions.outHeight; - exifOptions.inJustDecodeBounds = false; - sizedThumbBitmap.mBitmap = BitmapFactory.decodeByteArray(thumbData, 0, - thumbData.length, exifOptions); - if (sizedThumbBitmap.mBitmap != null) { - sizedThumbBitmap.mThumbnailData = thumbData; - sizedThumbBitmap.mThumbnailWidth = width; - sizedThumbBitmap.mThumbnailHeight = height; - } - } else { - fullOptions.inJustDecodeBounds = false; - sizedThumbBitmap.mBitmap = BitmapFactory.decodeFile(filePath, fullOptions); - } - } - - private static Bitmap createThumbnailFromMetadataRetriever( - String filePath, int targetSize, int maxPixels) { - if (filePath == null) { - return null; - } - Bitmap thumbnail = null; - MediaMetadataRetriever retriever = new MediaMetadataRetriever(); - try { - retriever.setDataSource(filePath); - MediaMetadataRetriever.BitmapParams params = new MediaMetadataRetriever.BitmapParams(); - params.setPreferredConfig(Bitmap.Config.ARGB_8888); - thumbnail = retriever.getThumbnailImageAtIndex(-1, params, targetSize, maxPixels); - } catch (RuntimeException ex) { - // Assume this is a corrupt video file. - } finally { - if (retriever != null) { - retriever.release(); - } - } - return thumbnail; } } diff --git a/native/android/net.c b/native/android/net.c index e32b7875b4e7..4cac371f313b 100644 --- a/native/android/net.c +++ b/native/android/net.c @@ -84,8 +84,7 @@ int android_getaddrinfofornetwork(net_handle_t network, return android_getaddrinfofornet(node, service, hints, netid, 0, res); } -int android_res_nquery(net_handle_t network, - const char *dname, int ns_class, int ns_type) { +int android_res_nquery(net_handle_t network, const char *dname, int ns_class, int ns_type) { unsigned netid; if (!getnetidfromhandle(network, &netid)) { return -ENONET; @@ -94,12 +93,11 @@ int android_res_nquery(net_handle_t network, return resNetworkQuery(netid, dname, ns_class, ns_type); } -int android_res_nresult(int fd, int *rcode, unsigned char *answer, int anslen) { +int android_res_nresult(int fd, int *rcode, uint8_t *answer, size_t anslen) { return resNetworkResult(fd, rcode, answer, anslen); } -int android_res_nsend(net_handle_t network, - const unsigned char *msg, int msglen) { +int android_res_nsend(net_handle_t network, const uint8_t *msg, size_t msglen) { unsigned netid; if (!getnetidfromhandle(network, &netid)) { return -ENONET; diff --git a/packages/CarSystemUI/Android.bp b/packages/CarSystemUI/Android.bp index 74d6605a1ffb..9b6ad38545b4 100644 --- a/packages/CarSystemUI/Android.bp +++ b/packages/CarSystemUI/Android.bp @@ -69,6 +69,7 @@ android_app { ], }, resource_dirs: [ + "res-keyguard", "res", ], @@ -80,4 +81,5 @@ android_app { "com.android.keyguard", ], + annotation_processors: ["dagger2-compiler-2.19"], } diff --git a/packages/CarSystemUI/res-keyguard/drawable/ic_backspace.xml b/packages/CarSystemUI/res-keyguard/drawable/ic_backspace.xml new file mode 100644 index 000000000000..f3a2f0f76a08 --- /dev/null +++ b/packages/CarSystemUI/res-keyguard/drawable/ic_backspace.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 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="36dp" + android:height="36dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M9,15.59L12.59,12L9,8.41L10.41,7L14,10.59L17.59,7L19,8.41L15.41,12L19,15.59L17.59,17L14,13.41L10.41,17L9,15.59zM21,6H8l-4.5,6L8,18h13V6M21,4c1.1,0 2,0.9 2,2v12c0,1.1 -0.9,2 -2,2H8c-0.63,0 -1.22,-0.3 -1.6,-0.8L1,12l5.4,-7.2C6.78,4.3 7.37,4 8,4H21L21,4z"/> +</vector> diff --git a/packages/CarSystemUI/res-keyguard/drawable/ic_done.xml b/packages/CarSystemUI/res-keyguard/drawable/ic_done.xml new file mode 100644 index 000000000000..ef0aac27b84e --- /dev/null +++ b/packages/CarSystemUI/res-keyguard/drawable/ic_done.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 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="36dp" + android:height="36dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M9,16.2l-3.5,-3.5a0.984,0.984 0,0 0,-1.4 0,0.984 0.984,0 0,0 0,1.4l4.19,4.19c0.39,0.39 1.02,0.39 1.41,0L20.3,7.7a0.984,0.984 0,0 0,0 -1.4,0.984 0.984,0 0,0 -1.4,0L9,16.2z"/> +</vector> diff --git a/packages/CarSystemUI/res-keyguard/drawable/keyguard_button_background.xml b/packages/CarSystemUI/res-keyguard/drawable/keyguard_button_background.xml new file mode 100644 index 000000000000..b428931670f5 --- /dev/null +++ b/packages/CarSystemUI/res-keyguard/drawable/keyguard_button_background.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 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. +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_pressed="true"> + <shape android:shape="rectangle"> + <corners android:radius="@dimen/car_button_radius"/> + <solid android:color="#131315"/> + </shape> + </item> + <item> + <shape android:shape="rectangle"> + <corners android:radius="@dimen/car_button_radius"/> + <solid android:color="@color/button_background"/> + </shape> + </item> +</selector> diff --git a/packages/CarSystemUI/res-keyguard/layout-land/keyguard_pattern_view.xml b/packages/CarSystemUI/res-keyguard/layout-land/keyguard_pattern_view.xml new file mode 100644 index 000000000000..b115a1f3c131 --- /dev/null +++ b/packages/CarSystemUI/res-keyguard/layout-land/keyguard_pattern_view.xml @@ -0,0 +1,82 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +** +** Copyright 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. +*/ +--> + +<!-- Car customizations + - Added title "Enter your Pattern" at the top + - Hid the emergency call at the bottom +--> + +<com.android.keyguard.KeyguardPatternView + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/keyguard_pattern_view" + android:orientation="horizontal" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:paddingHorizontal="@dimen/car_margin"> + + <FrameLayout + android:layout_height="match_parent" + android:layout_width="0dp" + android:layout_weight="1"> + + <com.android.internal.widget.LockPatternView + android:id="@+id/lockPatternView" + android:layout_width="@dimen/keyguard_pattern_dimension" + android:layout_height="@dimen/keyguard_pattern_dimension" + android:layout_gravity="center"/> + </FrameLayout> + + <LinearLayout + android:id="@+id/container" + android:layout_height="match_parent" + android:layout_width="0dp" + android:layout_weight="1" + android:orientation="vertical" + android:gravity="center_vertical"> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="@dimen/car_padding_2" + android:gravity="center" + android:textColor="@android:color/white" + android:textSize="@dimen/car_body1_size" + android:text="@string/keyguard_enter_your_pattern" /> + + <include layout="@layout/keyguard_message_area" /> + + <Button + android:id="@+id/cancel_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + style="@style/KeyguardButton" + android:text="@string/cancel"/> + + <include layout="@layout/keyguard_eca" + android:id="@+id/keyguard_selector_fade_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:layout_gravity="bottom|center_horizontal" + android:gravity="center_horizontal" + android:visibility="gone" /> + </LinearLayout> + +</com.android.keyguard.KeyguardPatternView> diff --git a/packages/CarSystemUI/res-keyguard/layout-land/keyguard_pin_view.xml b/packages/CarSystemUI/res-keyguard/layout-land/keyguard_pin_view.xml new file mode 100644 index 000000000000..ed88c6235d58 --- /dev/null +++ b/packages/CarSystemUI/res-keyguard/layout-land/keyguard_pin_view.xml @@ -0,0 +1,126 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 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. +--> + +<!-- Car customizations + - Added title "Enter your PIN" under the entry field + - Put backspace and enter buttons in row 4 + - PIN pad is on start side while entry field and title are on the end side + - Hid the emergency call at the bottom +--> + +<com.android.keyguard.KeyguardPINView + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:id="@+id/keyguard_pin_view" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="horizontal" + android:paddingHorizontal="@dimen/car_margin"> + + <FrameLayout + android:layout_width="0dp" + android:layout_weight="1" + android:layout_height="match_parent"> + + <GridLayout + android:id="@+id/container" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:gravity="center" + android:columnCount="3"> + + <include layout="@layout/num_pad_keys"/> + </GridLayout> + </FrameLayout> + + <LinearLayout + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="1" + android:gravity="center" + android:orientation="vertical"> + + <com.android.keyguard.PasswordTextView + android:id="@+id/pinEntry" + android:layout_width="@dimen/keyguard_security_width" + android:layout_height="@dimen/pin_entry_height" + android:gravity="center" + app:scaledTextSize="@integer/password_text_view_scale" + android:contentDescription="@string/keyguard_accessibility_pin_area" /> + + <View + android:id="@+id/divider" + android:layout_width="@dimen/keyguard_security_width" + android:layout_height="@dimen/divider_height" + android:background="@android:color/white" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_margin="@dimen/car_padding_2" + android:gravity="center" + android:textColor="@android:color/white" + android:textSize="@dimen/car_body1_size" + android:text="@string/keyguard_enter_your_pin" /> + + <include layout="@layout/keyguard_message_area" /> + + <Button + android:id="@+id/cancel_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + style="@style/KeyguardButton" + android:text="@string/cancel"/> + </LinearLayout> + + <!-- KeyguardPinView references these resources ids in code so removing them will cause the + keyguard to crash. Instead put them down here where they are out of the way and set their + visibility to gone. --> + <com.android.keyguard.AlphaOptimizedRelativeLayout + android:id="@+id/row0" + android:layout_width="0dp" + android:layout_height="0dp" + android:visibility="gone" /> + <LinearLayout + android:id="@+id/row1" + android:layout_width="0dp" + android:layout_height="0dp" + android:visibility="gone" /> + <LinearLayout + android:id="@+id/row2" + android:layout_width="0dp" + android:layout_height="0dp" + android:visibility="gone" /> + <LinearLayout + android:id="@+id/row3" + android:layout_width="0dp" + android:layout_height="0dp" + android:visibility="gone" /> + <LinearLayout + android:id="@+id/row4" + android:layout_width="0dp" + android:layout_height="0dp" + android:visibility="gone" /> + + <include layout="@layout/keyguard_eca" + android:id="@+id/keyguard_selector_fade_container" + android:layout_width="0dp" + android:layout_height="0dp" + android:visibility="gone" /> +</com.android.keyguard.KeyguardPINView> diff --git a/packages/CarSystemUI/res-keyguard/layout/keyguard_bouncer.xml b/packages/CarSystemUI/res-keyguard/layout/keyguard_bouncer.xml new file mode 100644 index 000000000000..062f7bd8615e --- /dev/null +++ b/packages/CarSystemUI/res-keyguard/layout/keyguard_bouncer.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ 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 + --> + +<!-- Car customizations + Car has solid black background instead of a transparent one +--> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@android:color/black" + android:fitsSystemWindows="true"> + + <include + style="@style/BouncerSecurityContainer" + layout="@layout/keyguard_host_view" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> +</FrameLayout> + diff --git a/packages/CarSystemUI/res-keyguard/layout/keyguard_message_area.xml b/packages/CarSystemUI/res-keyguard/layout/keyguard_message_area.xml new file mode 100644 index 000000000000..c2304147982a --- /dev/null +++ b/packages/CarSystemUI/res-keyguard/layout/keyguard_message_area.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +** +** Copyright 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. +*/ +--> + +<com.android.keyguard.KeyguardMessageArea + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="center" + style="@style/Keyguard.TextView" + android:id="@+id/keyguard_message_area" + android:singleLine="true" + android:ellipsize="marquee" + android:focusable="true" + android:layout_marginBottom="@dimen/car_padding_4" + android:textSize="@dimen/car_body2_size" /> diff --git a/packages/CarSystemUI/res-keyguard/layout/keyguard_num_pad_key.xml b/packages/CarSystemUI/res-keyguard/layout/keyguard_num_pad_key.xml new file mode 100644 index 000000000000..c7eda3888d7a --- /dev/null +++ b/packages/CarSystemUI/res-keyguard/layout/keyguard_num_pad_key.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ 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 + --> + +<!-- Car customizations + The mnemonics is not shown for car but KeyguardPinView references the resource id in code. + Removing it will cause the keyguard to crash. Hide them instead +--> +<merge xmlns:android="http://schemas.android.com/apk/res/android"> + <TextView + android:id="@+id/digit_text" + style="@style/Widget.TextView.NumPadKey" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + /> + <!-- The mnemonics is not shown for car but KeyguardPinView references the resource id in code. + Removing it will cause the keyguard to crash. Hide them instead --> + <TextView + android:id="@+id/klondike_text" + android:layout_width="0dp" + android:layout_height="0dp" + android:visibility="gone" + /> +</merge> + diff --git a/packages/CarSystemUI/res-keyguard/layout/keyguard_password_view.xml b/packages/CarSystemUI/res-keyguard/layout/keyguard_password_view.xml new file mode 100644 index 000000000000..e701fdb956f5 --- /dev/null +++ b/packages/CarSystemUI/res-keyguard/layout/keyguard_password_view.xml @@ -0,0 +1,104 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +** +** Copyright 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. +*/ +--> + +<!-- Car customizations + - Added title "Enter your Password" below the password field + - Hid the emergency call at the bottom +--> + +<com.android.keyguard.KeyguardPasswordView + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/res-auto" + android:id="@+id/keyguard_password_view" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent" + androidprv:layout_maxWidth="@dimen/keyguard_security_width" + androidprv:layout_maxHeight="@dimen/keyguard_security_height" + android:gravity="center"> + + <include layout="@layout/keyguard_message_area" /> + + <!-- Password entry field --> + <LinearLayout + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:layout_gravity="center_horizontal" + android:orientation="vertical" + android:theme="?attr/passwordStyle"> + + <EditText + android:id="@+id/passwordEntry" + android:layout_width="@dimen/password_field_width" + android:layout_height="wrap_content" + android:gravity="center_horizontal" + android:singleLine="true" + android:textStyle="normal" + android:inputType="textPassword" + android:textSize="@dimen/car_body1_size" + android:textColor="?attr/wallpaperTextColor" + android:textAppearance="?android:attr/textAppearanceMedium" + android:imeOptions="flagForceAscii|actionDone" + android:maxLength="@integer/password_text_view_scale" + /> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="@dimen/car_padding_2" + android:gravity="center" + android:textColor="@android:color/white" + android:textSize="@dimen/car_body1_size" + android:text="@string/keyguard_enter_your_password" /> + + <Button + android:id="@+id/cancel_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + style="@style/KeyguardButton" + android:text="@string/cancel"/> + + <ImageView android:id="@+id/switch_ime_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginBottom="12dp" + android:src="@drawable/ic_lockscreen_ime" + android:contentDescription="@string/accessibility_ime_switch_button" + android:clickable="true" + android:padding="8dp" + android:tint="@color/background_protected" + android:layout_gravity="end|center_vertical" + android:background="?android:attr/selectableItemBackground" + android:visibility="gone" + /> + </LinearLayout> + + <include layout="@layout/keyguard_eca" + android:id="@+id/keyguard_selector_fade_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="12dp" + android:orientation="vertical" + android:layout_gravity="bottom|center_horizontal" + android:gravity="center_horizontal" + android:visibility="gone" + /> + +</com.android.keyguard.KeyguardPasswordView> diff --git a/packages/CarSystemUI/res-keyguard/layout/keyguard_pattern_view.xml b/packages/CarSystemUI/res-keyguard/layout/keyguard_pattern_view.xml new file mode 100644 index 000000000000..00333a8a826e --- /dev/null +++ b/packages/CarSystemUI/res-keyguard/layout/keyguard_pattern_view.xml @@ -0,0 +1,84 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +** +** Copyright 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. +*/ +--> + +<!-- Car customizations + - Added title "Enter your Pattern" at the top + - Hid the emergency call at the bottom +--> + +<com.android.keyguard.KeyguardPatternView + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/res-auto" + android:id="@+id/keyguard_pattern_view" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent" + androidprv:layout_maxWidth="@dimen/keyguard_security_width" + android:gravity="center_horizontal"> + + <FrameLayout + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <LinearLayout + android:id="@+id/container" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:orientation="vertical" + android:layout_gravity="center"> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="@dimen/car_padding_2" + android:gravity="center" + android:textColor="@android:color/white" + android:textSize="@dimen/car_body1_size" + android:text="@string/keyguard_enter_your_pattern" /> + + <include layout="@layout/keyguard_message_area" /> + + <com.android.internal.widget.LockPatternView + android:id="@+id/lockPatternView" + android:layout_width="@dimen/keyguard_pattern_dimension" + android:layout_height="@dimen/keyguard_pattern_dimension" + android:layout_marginVertical="@dimen/pin_pattern_pad_margin_vertical" + android:layout_gravity="center_horizontal" + android:gravity="center" /> + + <Button + android:id="@+id/cancel_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + style="@style/KeyguardButton" + android:text="@string/cancel"/> + + <include layout="@layout/keyguard_eca" + android:id="@+id/keyguard_selector_fade_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:layout_gravity="bottom|center_horizontal" + android:gravity="center_horizontal" + android:visibility="gone" /> + </LinearLayout> + </FrameLayout> + +</com.android.keyguard.KeyguardPatternView> diff --git a/packages/CarSystemUI/res-keyguard/layout/keyguard_pin_view.xml b/packages/CarSystemUI/res-keyguard/layout/keyguard_pin_view.xml new file mode 100644 index 000000000000..16622518c36f --- /dev/null +++ b/packages/CarSystemUI/res-keyguard/layout/keyguard_pin_view.xml @@ -0,0 +1,127 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 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. +--> + +<!-- Car customizations + - Added title "Enter your PIN" under the entry field + - Put backspace and enter buttons in row 4 + - Hid the emergency call at the bottom +--> + +<com.android.keyguard.KeyguardPINView + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:id="@+id/keyguard_pin_view" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_marginLeft="@dimen/num_pad_margin_left" + android:layout_marginRight="@dimen/num_pad_margin_right" + android:orientation="vertical" + android:gravity="center"> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="center" + android:orientation="vertical"> + + <com.android.keyguard.PasswordTextView + android:id="@+id/pinEntry" + android:layout_width="@dimen/keyguard_security_width" + android:layout_height="@dimen/pin_entry_height" + android:gravity="center" + app:scaledTextSize="@integer/password_text_view_scale" + android:contentDescription="@string/keyguard_accessibility_pin_area" /> + + <View + android:id="@+id/divider" + android:layout_width="@dimen/keyguard_security_width" + android:layout_height="@dimen/divider_height" + android:background="@android:color/white" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_margin="@dimen/car_padding_2" + android:gravity="center" + android:textColor="@android:color/white" + android:textSize="@dimen/car_body1_size" + android:text="@string/keyguard_enter_your_pin" /> + + <include layout="@layout/keyguard_message_area" /> + + </LinearLayout> + + <GridLayout + android:id="@+id/container" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginVertical="@dimen/pin_pattern_pad_margin_vertical" + android:columnCount="3"> + + <include layout="@layout/num_pad_keys"/> + </GridLayout> + + <Button + android:id="@+id/cancel_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + style="@style/KeyguardButton" + android:text="@string/cancel"/> + + </LinearLayout> + + <!-- KeyguardPinView references these resources ids in code so removing them will cause the + keyguard to crash. Instead put them down here where they are out of the way and set their + visibility to gone. --> + <com.android.keyguard.AlphaOptimizedRelativeLayout + android:id="@+id/row0" + android:layout_width="0dp" + android:layout_height="0dp" + android:visibility="gone" /> + <LinearLayout + android:id="@+id/row1" + android:layout_width="0dp" + android:layout_height="0dp" + android:visibility="gone" /> + <LinearLayout + android:id="@+id/row2" + android:layout_width="0dp" + android:layout_height="0dp" + android:visibility="gone" /> + <LinearLayout + android:id="@+id/row3" + android:layout_width="0dp" + android:layout_height="0dp" + android:visibility="gone" /> + <LinearLayout + android:id="@+id/row4" + android:layout_width="0dp" + android:layout_height="0dp" + android:visibility="gone" /> + + <include + layout="@layout/keyguard_eca" + android:id="@+id/keyguard_selector_fade_container" + android:layout_width="0dp" + android:layout_height="0dp" + android:visibility="gone" /> +</com.android.keyguard.KeyguardPINView> diff --git a/packages/CarSystemUI/res-keyguard/layout/num_pad_keys.xml b/packages/CarSystemUI/res-keyguard/layout/num_pad_keys.xml new file mode 100644 index 000000000000..8306cb4a708a --- /dev/null +++ b/packages/CarSystemUI/res-keyguard/layout/num_pad_keys.xml @@ -0,0 +1,83 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ 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 + --> + +<merge xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto"> + <!-- Row 1 --> + <com.android.keyguard.NumPadKey + android:id="@+id/key1" + style="@style/NumPadKeyButton" + app:digit="1" /> + <com.android.keyguard.NumPadKey + android:id="@+id/key2" + style="@style/NumPadKeyButton.MiddleColumn" + app:digit="2" /> + <com.android.keyguard.NumPadKey + android:id="@+id/key3" + style="@style/NumPadKeyButton" + app:digit="3" /> + + <!-- Row 2 --> + <com.android.keyguard.NumPadKey + android:id="@+id/key4" + style="@style/NumPadKeyButton" + app:digit="4" /> + <com.android.keyguard.NumPadKey + android:id="@+id/key5" + style="@style/NumPadKeyButton.MiddleColumn" + app:digit="5" /> + <com.android.keyguard.NumPadKey + android:id="@+id/key6" + style="@style/NumPadKeyButton" + app:digit="6" /> + + <!-- Row 3 --> + <com.android.keyguard.NumPadKey + android:id="@+id/key7" + style="@style/NumPadKeyButton" + app:digit="7" /> + <com.android.keyguard.NumPadKey + android:id="@+id/key8" + style="@style/NumPadKeyButton.MiddleColumn" + app:digit="8" /> + <com.android.keyguard.NumPadKey + android:id="@+id/key9" + style="@style/NumPadKeyButton" + app:digit="9" /> + + <!-- Row 4 --> + <ImageButton + android:id="@+id/delete_button" + style="@style/NumPadKeyButton.LastRow" + android:gravity="center_vertical" + android:src="@drawable/ic_backspace" + android:clickable="true" + android:tint="@android:color/white" + android:background="@drawable/ripple_drawable" + android:contentDescription="@string/keyboardview_keycode_delete" /> + <com.android.keyguard.NumPadKey + android:id="@+id/key0" + style="@style/NumPadKeyButton.LastRow.MiddleColumn" + app:digit="0" /> + <ImageButton + android:id="@+id/key_enter" + style="@style/NumPadKeyButton.LastRow" + android:src="@drawable/ic_done" + android:tint="@android:color/white" + android:background="@drawable/ripple_drawable" + android:contentDescription="@string/keyboardview_keycode_enter" /> +</merge> + diff --git a/packages/CarSystemUI/res-keyguard/values-h1000dp/dimens.xml b/packages/CarSystemUI/res-keyguard/values-h1000dp/dimens.xml new file mode 100644 index 000000000000..d055efa25078 --- /dev/null +++ b/packages/CarSystemUI/res-keyguard/values-h1000dp/dimens.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 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. +--> +<resources> + <dimen name="pin_pattern_pad_margin_vertical">178dp</dimen> +</resources> diff --git a/packages/CarSystemUI/res-keyguard/values-land/dimens.xml b/packages/CarSystemUI/res-keyguard/values-land/dimens.xml new file mode 100644 index 000000000000..805a13497f6f --- /dev/null +++ b/packages/CarSystemUI/res-keyguard/values-land/dimens.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 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. +--> +<resources> + <dimen name="num_pad_key_margin_horizontal">@dimen/car_padding_5</dimen> + <dimen name="num_pad_key_margin_bottom">@dimen/car_padding_4</dimen> +</resources> diff --git a/packages/CarSystemUI/res-keyguard/values/colors.xml b/packages/CarSystemUI/res-keyguard/values/colors.xml new file mode 100644 index 000000000000..e6edbea3f80e --- /dev/null +++ b/packages/CarSystemUI/res-keyguard/values/colors.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 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. +--> + +<resources> + <color name="button_background">@color/car_dark_blue_grey_600</color> + <color name="button_text">@color/car_action1_light</color> +</resources>
\ No newline at end of file diff --git a/packages/CarSystemUI/res-keyguard/values/dimens.xml b/packages/CarSystemUI/res-keyguard/values/dimens.xml new file mode 100644 index 000000000000..9424dc3870ba --- /dev/null +++ b/packages/CarSystemUI/res-keyguard/values/dimens.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 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. +--> +<resources> + <dimen name="num_pad_margin_left">112dp</dimen> + <dimen name="num_pad_margin_right">144dp</dimen> + <dimen name="num_pad_key_width">80dp</dimen> + <dimen name="num_pad_key_height">80dp</dimen> + <dimen name="num_pad_key_margin_horizontal">@dimen/car_padding_6</dimen> + <dimen name="num_pad_key_margin_bottom">@dimen/car_padding_5</dimen> + <dimen name="pin_entry_height">@dimen/num_pad_key_height</dimen> + <dimen name="divider_height">1dp</dimen> + <dimen name="key_enter_margin_top">128dp</dimen> + <dimen name="keyguard_pattern_dimension">400dp</dimen> + <dimen name="password_field_width">350dp</dimen> + <dimen name="pin_pattern_pad_margin_vertical">0dp</dimen> +</resources> diff --git a/packages/CarSystemUI/res-keyguard/values/integers.xml b/packages/CarSystemUI/res-keyguard/values/integers.xml new file mode 100644 index 000000000000..bad1346af452 --- /dev/null +++ b/packages/CarSystemUI/res-keyguard/values/integers.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 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. +--> +<resources> + <integer name="password_text_view_scale">40</integer> + <integer name="password_max_length">500</integer> +</resources> diff --git a/packages/CarSystemUI/res-keyguard/values/styles.xml b/packages/CarSystemUI/res-keyguard/values/styles.xml new file mode 100644 index 000000000000..b39e6e64316e --- /dev/null +++ b/packages/CarSystemUI/res-keyguard/values/styles.xml @@ -0,0 +1,53 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 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. +--> +<resources xmlns:android="http://schemas.android.com/apk/res/android"> + + <!-- The style for the volume icons in the volume dialog. This style makes the icon scale to + fit its container since auto wants the icon to be larger. The padding is added to make it + so the icon does not press along the edges of the dialog. --> + <style name="NumPadKeyButton"> + <item name="android:layout_width">@dimen/num_pad_key_width</item> + <item name="android:layout_height">@dimen/num_pad_key_height</item> + <item name="android:layout_marginBottom">@dimen/num_pad_key_margin_bottom</item> + <item name="textView">@id/pinEntry</item> + </style> + + <style name="NumPadKeyButton.MiddleColumn"> + <item name="android:layout_marginStart">@dimen/num_pad_key_margin_horizontal</item> + <item name="android:layout_marginEnd">@dimen/num_pad_key_margin_horizontal</item> + </style> + + <style name="NumPadKeyButton.LastRow"> + <item name="android:layout_marginBottom">0dp</item> + </style> + + <style name="NumPadKeyButton.LastRow.MiddleColumn"> + <item name="android:layout_marginStart">@dimen/num_pad_key_margin_horizontal</item> + <item name="android:layout_marginEnd">@dimen/num_pad_key_margin_horizontal</item> + </style> + + <style name="KeyguardButton" parent="Widget.Car.Button"> + <item name="android:background">@drawable/keyguard_button_background</item> + <item name="android:textColor">@color/button_text</item> + <item name="android:textAllCaps">false</item> + </style> + + <style name="Widget.TextView.NumPadKey" parent="@android:style/Widget.TextView"> + <!-- Only replaces the text size. --> + <item name="android:textSize">@dimen/car_body1_size</item> + </style> +</resources> diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIFactory.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIFactory.java index f57f26db118c..7039a2c0a957 100644 --- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIFactory.java +++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIFactory.java @@ -17,25 +17,42 @@ package com.android.systemui; import android.content.Context; -import android.util.ArrayMap; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.ViewMediatorCallback; -import com.android.systemui.Dependency.DependencyProvider; import com.android.systemui.car.CarNotificationEntryManager; import com.android.systemui.statusbar.car.CarFacetButtonController; import com.android.systemui.statusbar.car.CarStatusBarKeyguardViewManager; -import com.android.systemui.statusbar.car.hvac.HvacController; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.volume.CarVolumeDialogComponent; import com.android.systemui.volume.VolumeDialogComponent; +import javax.inject.Singleton; + +import dagger.Component; +import dagger.Module; +import dagger.Provides; + /** * Class factory to provide car specific SystemUI components. */ public class CarSystemUIFactory extends SystemUIFactory { + private CarDependencyComponent mCarDependencyComponent; + + @Override + protected void init(Context context) { + super.init(context); + mCarDependencyComponent = DaggerCarSystemUIFactory_CarDependencyComponent.builder() + .contextHolder(new ContextHolder(context)) + .build(); + } + + public CarDependencyComponent getCarDependencyComponent() { + return mCarDependencyComponent; + } + public StatusBarKeyguardViewManager createStatusBarKeyguardViewManager(Context context, ViewMediatorCallback viewMediatorCallback, LockPatternUtils lockPatternUtils) { return new CarStatusBarKeyguardViewManager(context, viewMediatorCallback, lockPatternUtils); @@ -46,12 +63,27 @@ public class CarSystemUIFactory extends SystemUIFactory { } @Override - public void injectDependencies(ArrayMap<Object, DependencyProvider> providers, - Context context) { - super.injectDependencies(providers, context); - providers.put(NotificationEntryManager.class, - () -> new CarNotificationEntryManager(context)); - providers.put(CarFacetButtonController.class, () -> new CarFacetButtonController(context)); - providers.put(HvacController.class, () -> new HvacController(context)); + public NotificationEntryManager provideNotificationEntryManager(Context context) { + return new CarNotificationEntryManager(context); + } + + @Module + protected static class ContextHolder { + private Context mContext; + + public ContextHolder(Context context) { + mContext = context; + } + + @Provides + public Context provideContext() { + return mContext; + } + } + + @Singleton + @Component(modules = ContextHolder.class) + public interface CarDependencyComponent { + CarFacetButtonController getCarFacetButtonController(); } } diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarFacetButton.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarFacetButton.java index cea4ab0e4992..0a20eaa0b888 100644 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarFacetButton.java +++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarFacetButton.java @@ -26,8 +26,9 @@ import android.widget.ImageView; import android.widget.LinearLayout; import com.android.keyguard.AlphaOptimizedImageButton; -import com.android.systemui.Dependency; +import com.android.systemui.CarSystemUIFactory; import com.android.systemui.R; +import com.android.systemui.SystemUIFactory; /** * CarFacetButton is a ui component designed to be used as a shortcut for an app of a defined @@ -76,8 +77,9 @@ public class CarFacetButton extends LinearLayout { TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CarFacetButton); setupIntents(typedArray); setupIcons(typedArray); - CarFacetButtonController carFacetButtonController = Dependency.get( - CarFacetButtonController.class); + CarSystemUIFactory factory = SystemUIFactory.getInstance(); + CarFacetButtonController carFacetButtonController = factory.getCarDependencyComponent() + .getCarFacetButtonController(); carFacetButtonController.addFacetButton(this); } diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarFacetButtonController.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarFacetButtonController.java index 56db242f1eb9..7811a1caeb88 100644 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarFacetButtonController.java +++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarFacetButtonController.java @@ -29,11 +29,15 @@ import java.util.HashMap; import java.util.List; import java.util.Set; +import javax.inject.Inject; +import javax.inject.Singleton; + /** * CarFacetButtons placed on the nav bar are designed to have visual indication that the active * application on screen is associated with it. This is basically a similar concept to a radio * button group. */ +@Singleton public class CarFacetButtonController { protected HashMap<String, CarFacetButton> mButtonsByCategory = new HashMap<>(); @@ -42,6 +46,7 @@ public class CarFacetButtonController { protected CarFacetButton mSelectedFacetButton; protected Context mContext; + @Inject public CarFacetButtonController(Context context) { mContext = context; } diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java index 5da236ceb211..7028999c159c 100644 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java +++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java @@ -29,9 +29,11 @@ import android.view.WindowManager; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.BatteryMeterView; +import com.android.systemui.CarSystemUIFactory; import com.android.systemui.Dependency; import com.android.systemui.Prefs; import com.android.systemui.R; +import com.android.systemui.SystemUIFactory; import com.android.systemui.classifier.FalsingLog; import com.android.systemui.classifier.FalsingManager; import com.android.systemui.fragments.FragmentHostManager; @@ -102,7 +104,9 @@ public class CarStatusBar extends StatusBar implements mHvacController.connectToCarService(); - mCarFacetButtonController = Dependency.get(CarFacetButtonController.class); + CarSystemUIFactory factory = SystemUIFactory.getInstance(); + mCarFacetButtonController = factory.getCarDependencyComponent() + .getCarFacetButtonController(); mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class); mDeviceIsProvisioned = mDeviceProvisionedController.isDeviceProvisioned(); if (!mDeviceIsProvisioned) { @@ -239,7 +243,7 @@ public class CarStatusBar extends StatusBar implements @Override protected void makeStatusBarView() { super.makeStatusBarView(); - mHvacController = Dependency.get(HvacController.class); + mHvacController = new HvacController(mContext); mNotificationPanelBackground = getDefaultWallpaper(); mScrimController.setScrimBehindDrawable(mNotificationPanelBackground); diff --git a/packages/CompanionDeviceManager/AndroidManifest.xml b/packages/CompanionDeviceManager/AndroidManifest.xml index 34bc4ebcd0aa..0be71e6d17b9 100644 --- a/packages/CompanionDeviceManager/AndroidManifest.xml +++ b/packages/CompanionDeviceManager/AndroidManifest.xml @@ -26,6 +26,7 @@ <uses-permission android:name="android.permission.BLUETOOTH"/> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> + <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/> diff --git a/packages/ExtServices/src/android/ext/services/sms/FinancialSmsServiceImpl.java b/packages/ExtServices/src/android/ext/services/sms/FinancialSmsServiceImpl.java index ab71802102ae..81a63dd80a63 100644 --- a/packages/ExtServices/src/android/ext/services/sms/FinancialSmsServiceImpl.java +++ b/packages/ExtServices/src/android/ext/services/sms/FinancialSmsServiceImpl.java @@ -17,14 +17,10 @@ package android.ext.services.sms; import android.annotation.NonNull; import android.annotation.Nullable; -import android.database.Cursor; import android.database.CursorWindow; -import android.net.Uri; import android.os.Bundle; import android.service.sms.FinancialSmsService; -import android.util.Log; -import java.util.ArrayList; /** * Service to provide financial apps access to sms messages. */ @@ -36,45 +32,6 @@ public class FinancialSmsServiceImpl extends FinancialSmsService { @Nullable @Override public CursorWindow onGetSmsMessages(@NonNull Bundle params) { - ArrayList<String> columnNames = params.getStringArrayList(KEY_COLUMN_NAMES); - if (columnNames == null || columnNames.size() <= 0) { - return null; - } - - Uri inbox = Uri.parse("content://sms/inbox"); - - try (Cursor cursor = getContentResolver().query(inbox, null, null, null, null); - CursorWindow window = new CursorWindow("FinancialSmsMessages")) { - int messageCount = cursor.getCount(); - if (messageCount > 0 && cursor.moveToFirst()) { - window.setNumColumns(columnNames.size()); - for (int row = 0; row < messageCount; row++) { - if (!window.allocRow()) { - Log.e(TAG, "CursorWindow ran out of memory."); - return null; - } - for (int col = 0; col < columnNames.size(); col++) { - String columnName = columnNames.get(col); - int inboxColumnIndex = cursor.getColumnIndexOrThrow(columnName); - String inboxColumnValue = cursor.getString(inboxColumnIndex); - boolean addedToCursorWindow = window.putString(inboxColumnValue, row, col); - if (!addedToCursorWindow) { - Log.e(TAG, "Failed to add:" - + inboxColumnValue - + ";column:" - + columnName); - return null; - } - } - cursor.moveToNext(); - } - } else { - Log.w(TAG, "No sms messages."); - } - return window; - } catch (Exception e) { - Log.e(TAG, "Failed to get sms messages."); - return null; - } + return null; } } diff --git a/packages/PackageInstaller/res/values/strings.xml b/packages/PackageInstaller/res/values/strings.xml index 1e0ff506cb20..a05a219b917b 100644 --- a/packages/PackageInstaller/res/values/strings.xml +++ b/packages/PackageInstaller/res/values/strings.xml @@ -121,7 +121,7 @@ <string name="uninstall_update_text_multiuser">Replace this app with the factory version? All data will be removed. This affects all users of this device, including those with work profiles.</string> <!-- Label of a checkbox that allows to remove the files contributed by app during uninstall [CHAR LIMIT=none] --> <string name="uninstall_remove_contributed_files">Also remove <xliff:g id="size" example="1.5MB">%1$s</xliff:g> of associated media files.</string> - <!-- Label of a checkbox that allows to remove the files contributed by app during uninstall [CHAR LIMIT=none] --> + <!-- Label of a checkbox that allows to keep the data (e.g. files, settings) of the app on uninstall [CHAR LIMIT=none] --> <string name="uninstall_keep_data">Keep <xliff:g id="size" example="1.5MB">%1$s</xliff:g> of app data.</string> <!-- Label for the notification channel containing notifications for current uninstall operations [CHAR LIMIT=40] --> diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp index 042808a07f8f..2321790fa960 100644 --- a/packages/SettingsLib/Android.bp +++ b/packages/SettingsLib/Android.bp @@ -19,6 +19,7 @@ android_library { "SettingsLibLayoutPreference", "SettingsLibActionButtonsPreference", "SettingsLibEntityHeaderWidgets", + "SettingsLibBarChartPreference" ], // ANDROIDMK TRANSLATION ERROR: unsupported assignment to LOCAL_SHARED_JAVA_LIBRARIES diff --git a/packages/SettingsLib/BarChartPreference/Android.bp b/packages/SettingsLib/BarChartPreference/Android.bp new file mode 100644 index 000000000000..477e8979b03b --- /dev/null +++ b/packages/SettingsLib/BarChartPreference/Android.bp @@ -0,0 +1,13 @@ +android_library { + name: "SettingsLibBarChartPreference", + + srcs: ["src/**/*.java"], + resource_dirs: ["res"], + + static_libs: [ + "androidx.preference_preference", + ], + + sdk_version: "system_current", + min_sdk_version: "21", +} diff --git a/packages/SettingsLib/BarChartPreference/AndroidManifest.xml b/packages/SettingsLib/BarChartPreference/AndroidManifest.xml new file mode 100644 index 000000000000..4b9f1ab8d6cc --- /dev/null +++ b/packages/SettingsLib/BarChartPreference/AndroidManifest.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.settingslib.widget"> + + <uses-sdk android:minSdkVersion="21" /> + +</manifest> diff --git a/packages/SettingsLib/BarChartPreference/res/layout/settings_bar_chart.xml b/packages/SettingsLib/BarChartPreference/res/layout/settings_bar_chart.xml new file mode 100644 index 000000000000..1a4d7b7dee14 --- /dev/null +++ b/packages/SettingsLib/BarChartPreference/res/layout/settings_bar_chart.xml @@ -0,0 +1,65 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. + --> + +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:settings="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginStart="16dp" + android:layout_marginEnd="16dp" + android:gravity="center" + android:orientation="vertical"> + + <TextView + android:id="@+id/bar_chart_title" + android:layout_width="wrap_content" + android:layout_height="48dp" + android:gravity="center" + android:textAppearance="@style/BarChart.Text.HeaderTitle"/> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="center|bottom"> + + <com.android.settingslib.widget.BarView + android:id="@+id/bar_view1" + style="@style/BarViewStyle" + settings:barColor="#FA7B17"/> + <com.android.settingslib.widget.BarView + android:id="@+id/bar_view2" + style="@style/BarViewStyle" + settings:barColor="#F439A0"/> + <com.android.settingslib.widget.BarView + android:id="@+id/bar_view3" + style="@style/BarViewStyle" + settings:barColor="#A142F4"/> + <com.android.settingslib.widget.BarView + android:id="@+id/bar_view4" + style="@style/BarViewStyle" + settings:barColor="#24C1E0"/> + </LinearLayout> + + <Button + android:id="@+id/bar_chart_details" + style="@android:style/Widget.DeviceDefault.Button.Borderless.Colored" + android:layout_width="wrap_content" + android:layout_height="48dp" + android:gravity="center"/> + +</LinearLayout> diff --git a/packages/SettingsLib/BarChartPreference/res/layout/settings_bar_view.xml b/packages/SettingsLib/BarChartPreference/res/layout/settings_bar_view.xml new file mode 100644 index 000000000000..b053317b9de1 --- /dev/null +++ b/packages/SettingsLib/BarChartPreference/res/layout/settings_bar_view.xml @@ -0,0 +1,58 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. + --> + +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="center" + android:orientation="vertical"> + + <View + android:id="@+id/bar_view" + android:layout_width="8dp" + android:layout_height="wrap_content" + android:background="?android:attr/colorAccent"/> + + <ImageView + android:id="@+id/icon_view" + android:layout_width="@dimen/settings_bar_view_icon_size" + android:layout_height="@dimen/settings_bar_view_icon_size" + android:scaleType="fitCenter" + android:layout_marginTop="12dp" + android:layout_marginBottom="12dp"/> + + <TextView + android:id="@+id/bar_title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="10dp" + android:layout_marginBottom="2dp" + android:singleLine="true" + android:ellipsize="marquee" + android:textAppearance="@style/BarChart.Text.Title"/> + + <TextView + android:id="@+id/bar_summary" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginBottom="12dp" + android:singleLine="true" + android:ellipsize="marquee" + android:textAppearance="@style/BarChart.Text.Summary"/> + +</LinearLayout>
\ No newline at end of file diff --git a/packages/SettingsLib/BarChartPreference/res/values/attrs.xml b/packages/SettingsLib/BarChartPreference/res/values/attrs.xml new file mode 100644 index 000000000000..df3eb0a36919 --- /dev/null +++ b/packages/SettingsLib/BarChartPreference/res/values/attrs.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. + --> + +<resources> + + <declare-styleable name="SettingsBarView"> + <!-- The color of bar view --> + <attr name="barColor" format="color" /> + </declare-styleable> + +</resources> diff --git a/packages/SettingsLib/BarChartPreference/res/values/dimens.xml b/packages/SettingsLib/BarChartPreference/res/values/dimens.xml new file mode 100644 index 000000000000..7148afa2d62f --- /dev/null +++ b/packages/SettingsLib/BarChartPreference/res/values/dimens.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. + --> + +<resources> + <dimen name="settings_bar_view_max_height">106dp</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 new file mode 100644 index 000000000000..647d0800fe82 --- /dev/null +++ b/packages/SettingsLib/BarChartPreference/res/values/styles.xml @@ -0,0 +1,46 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. + --> + +<resources> + <style name="BarViewStyle"> + <item name="android:layout_width">0dp</item> + <item name="android:layout_height">wrap_content</item> + <item name="android:layout_weight">1</item> + <item name="android:layout_marginStart">8dp</item> + <item name="android:layout_marginEnd">8dp</item> + </style> + + <style name="BarChart.Text" + parent="@android:style/TextAppearance.Material.Subhead"> + <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item> + <item name="android:textColor">?android:attr/textColorPrimary</item> + </style> + + <style name="BarChart.Text.HeaderTitle"> + <item name="android:textSize">14sp</item> + </style> + + <style name="BarChart.Text.Title"> + <item name="android:textSize">22sp</item> + </style> + + <style name="BarChart.Text.Summary" + parent="@android:style/TextAppearance.Material.Body1"> + <item name="android:textColor">?android:attr/textColorSecondary</item> + <item name="android:textSize">14sp</item> + </style> +</resources>
\ No newline at end of file diff --git a/packages/SettingsLib/BarChartPreference/src/com/android/settingslib/widget/BarChartPreference.java b/packages/SettingsLib/BarChartPreference/src/com/android/settingslib/widget/BarChartPreference.java new file mode 100644 index 000000000000..89ebf4d1300a --- /dev/null +++ b/packages/SettingsLib/BarChartPreference/src/com/android/settingslib/widget/BarChartPreference.java @@ -0,0 +1,242 @@ +/* + * 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.settingslib.widget; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.widget.Button; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.StringRes; +import androidx.preference.Preference; +import androidx.preference.PreferenceViewHolder; + +import java.util.Arrays; + +/** + * This BarChartPreference shows four bar views in this preference at most. + * + * <p>The following code sample shows a typical use, with an XML layout and code to initialize the + * contents of the BarChartPreference: + * + * <pre> + * <com.android.settingslib.widget.BarChartPreference + * android:key="bar_chart"/> + * </pre> + * + * <p>This code sample demonstrates how to initialize the contents of the BarChartPreference defined + * in the previous XML layout: + * + * <pre> + * BarViewInfo[] viewsInfo = new BarViewInfo [] { + * new BarViewInfo(icon, 18, res of summary), + * new BarViewInfo(icon, 25, res of summary), + * new BarViewInfo(icon, 10, res of summary), + * new BarViewInfo(icon, 3, res of summary), + * }; + * </pre> + * + * <pre> + * BarChartPreference preference = ((BarChartPreference) findPreference("bar_chart")); + * + * preference.setBarChartTitleRes(R.string.title_res); + * preference.setBarChartDetailsRes(R.string.details_res); + * preference.setBarChartDetailsClickListener(v -> doSomething()); + * preference.setAllBarViewsData(viewsInfo); + * </pre> + */ +public class BarChartPreference extends Preference { + + private static final String TAG = "BarChartPreference"; + private static final int MAXIMUM_BAR_VIEWS = 4; + private static final int[] BAR_VIEWS = { + R.id.bar_view1, + R.id.bar_view2, + R.id.bar_view3, + R.id.bar_view4 + }; + + private int mMaxBarHeight; + private @StringRes int mTitleId; + private @StringRes int mDetailsId; + private BarViewInfo[] mBarViewsInfo; + private View.OnClickListener mDetailsOnClickListener; + + /** + * Constructs a new BarChartPreference with the given context's theme. + * It sets a layout with settings bar chart style + * + * @param context The Context the view is running in, through which it can + * access the current theme, resources, etc. + */ + public BarChartPreference(Context context) { + super(context); + init(); + } + + /** + * Constructs a new BarChartPreference with the given context's theme and the supplied + * attribute set. + * It sets a layout with settings bar chart style + * + * @param context the Context the view is running in + * @param attrs the attributes of the XML tag that is inflating the view. + */ + public BarChartPreference(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + /** + * Constructs a new BarChartPreference with the given context's theme, the supplied + * attribute set, and default style attribute. + * It sets a layout with settings bar chart style + * + * @param context The Context the view is running in, through which it can + * access the current theme, resources, etc. + * @param attrs The attributes of the XML tag that is inflating the view. + * @param defStyleAttr An attribute in the current theme that contains a + * reference to a style resource that supplies default + * values for the view. Can be 0 to not look for + * defaults. + */ + public BarChartPreference(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); + } + + /** + * Constructs a new BarChartPreference with the given context's theme, the supplied + * attribute set, and default styles. + * It sets a layout with settings bar chart style + * + * @param context The Context the view is running in, through which it can + * access the current theme, resources, etc. + * @param attrs The attributes of the XML tag that is inflating the view. + * @param defStyleAttr An attribute in the current theme that contains a + * reference to a style resource that supplies default + * values for the view. Can be 0 to not look for + * defaults. + * @param defStyleRes A resource identifier of a style resource that + * supplies default values for the view, used only if + * defStyleAttr is 0 or can not be found in the theme. + * Can be 0 to not look for defaults. + */ + public BarChartPreference(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + init(); + } + + /** + * Set the text resource for bar chart title. + */ + public void setBarChartTitle(@StringRes int resId) { + mTitleId = resId; + notifyChanged(); + } + + /** + * Set the text resource for bar chart details. + */ + public void setBarChartDetails(@StringRes int resId) { + mDetailsId = resId; + notifyChanged(); + } + + /** + * Register a callback to be invoked when bar chart details view is clicked. + */ + public void setBarChartDetailsClickListener(@Nullable View.OnClickListener clickListener) { + mDetailsOnClickListener = clickListener; + notifyChanged(); + } + + /** + * Set all bar view information which you'd like to show in preference. + * + * <p>This method helps you do a sort by {@linkBarViewInfo#mBarNumber} in descending order. + * + * @param barViewsInfo the barViewsInfo contain at least one {@link BarViewInfo}. + */ + public void setAllBarViewsInfo(@NonNull BarViewInfo[] barViewsInfo) { + mBarViewsInfo = barViewsInfo; + // Do a sort in descending order, the first element would have max {@link + // BarViewInfo#mBarNumber} + Arrays.sort(mBarViewsInfo); + caculateAllBarViewsHeight(); + notifyChanged(); + } + + @Override + public void onBindViewHolder(PreferenceViewHolder holder) { + super.onBindViewHolder(holder); + holder.setDividerAllowedAbove(true); + holder.setDividerAllowedBelow(true); + + bindChartTitleView(holder); + bindChartDetailsView(holder); + updateBarChart(holder); + } + + private void init() { + setSelectable(false); + setLayoutResource(R.layout.settings_bar_chart); + mMaxBarHeight = getContext().getResources().getDimensionPixelSize( + R.dimen.settings_bar_view_max_height); + } + + private void bindChartTitleView(PreferenceViewHolder holder) { + final TextView titleView = (TextView) holder.findViewById(R.id.bar_chart_title); + titleView.setText(mTitleId); + } + + private void bindChartDetailsView(PreferenceViewHolder holder) { + final Button detailsView = (Button) holder.findViewById(R.id.bar_chart_details); + detailsView.setText(mDetailsId); + detailsView.setOnClickListener(mDetailsOnClickListener); + } + + private void updateBarChart(PreferenceViewHolder holder) { + for (int index = 0; index < MAXIMUM_BAR_VIEWS; index++) { + final BarView barView = (BarView) holder.findViewById(BAR_VIEWS[index]); + + // If there is no bar views data can be shown. + if (mBarViewsInfo == null || index >= mBarViewsInfo.length) { + barView.setVisibility(View.GONE); + continue; + } + barView.setVisibility(View.VISIBLE); + barView.updateBarViewUI(mBarViewsInfo[index]); + } + } + + private void caculateAllBarViewsHeight() { + // Since we sorted this array in advance, the first element must have the max {@link + // BarViewInfo#mBarNumber}. + final int maxBarViewNumber = mBarViewsInfo[0].getBarNumber(); + // If the max number of bar view is zero, then we don't caculate the unit for bar height. + final int unit = maxBarViewNumber == 0 ? 0 : mMaxBarHeight / maxBarViewNumber; + + for (BarViewInfo barView : mBarViewsInfo) { + barView.setBarHeight(barView.getBarNumber() * unit); + } + } +} diff --git a/packages/SettingsLib/BarChartPreference/src/com/android/settingslib/widget/BarView.java b/packages/SettingsLib/BarChartPreference/src/com/android/settingslib/widget/BarView.java new file mode 100644 index 000000000000..6243a2de387a --- /dev/null +++ b/packages/SettingsLib/BarChartPreference/src/com/android/settingslib/widget/BarView.java @@ -0,0 +1,117 @@ +/* + * 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.settingslib.widget; + +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import androidx.annotation.ColorInt; +import androidx.annotation.VisibleForTesting; + +/** + * A extension view for bar chart. + */ +public class BarView extends LinearLayout { + + private static final String TAG = "BarView"; + + private View mBarView; + private ImageView mIcon; + private TextView mBarTitle; + private TextView mBarSummary; + + /** + * Constructs a new BarView with the given context's theme. + * + * @param context The Context the view is running in, through which it can + * access the current theme, resources, etc. + */ + public BarView(Context context) { + super(context); + init(); + } + + /** + * Constructs a new BarView with the given context's theme and the supplied + * attribute set. + * + * @param context the Context the view is running in + * @param attrs the attributes of the XML tag that is inflating the view. + */ + public BarView(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + + // Get accent color + TypedArray a = context.obtainStyledAttributes(new int[]{android.R.attr.colorAccent}); + @ColorInt final int colorAccent = a.getColor(0, 0); + + // Get bar color from layout XML + a = context.obtainStyledAttributes(attrs, R.styleable.SettingsBarView); + @ColorInt final int barColor = a.getColor(R.styleable.SettingsBarView_barColor, + colorAccent); + a.recycle(); + + mBarView.setBackgroundColor(barColor); + } + + /** + * This helps update the bar view UI with a {@link BarViewInfo}. + * + * @param barViewInfo A {@link BarViewInfo} saves bar view status. + */ + public void updateBarViewUI(BarViewInfo barViewInfo) { + //Set height of bar view + mBarView.getLayoutParams().height = barViewInfo.getBarHeight(); + mIcon.setImageDrawable(barViewInfo.getIcon()); + // For now, we use the bar number as title. + mBarTitle.setText(Integer.toString(barViewInfo.getBarNumber())); + mBarSummary.setText(barViewInfo.getSummaryRes()); + } + + @VisibleForTesting + CharSequence getTitle() { + return mBarTitle.getText(); + } + + @VisibleForTesting + CharSequence getSummary() { + return mBarSummary.getText(); + } + + private void init() { + LayoutInflater.from(getContext()).inflate(R.layout.settings_bar_view, this); + setOrientation(LinearLayout.VERTICAL); + setGravity(Gravity.CENTER); + + mBarView = findViewById(R.id.bar_view); + mIcon = (ImageView) findViewById(R.id.icon_view); + mBarTitle = (TextView) findViewById(R.id.bar_title); + mBarSummary = (TextView) findViewById(R.id.bar_summary); + } + + private void setOnClickListner(View.OnClickListener listener) { + mBarView.setOnClickListener(listener); + } +} diff --git a/packages/SettingsLib/BarChartPreference/src/com/android/settingslib/widget/BarViewInfo.java b/packages/SettingsLib/BarChartPreference/src/com/android/settingslib/widget/BarViewInfo.java new file mode 100644 index 000000000000..aa83ce99d200 --- /dev/null +++ b/packages/SettingsLib/BarChartPreference/src/com/android/settingslib/widget/BarViewInfo.java @@ -0,0 +1,146 @@ +/* + * 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.settingslib.widget; + +import android.graphics.drawable.Drawable; +import android.view.View; + +import androidx.annotation.IntRange; +import androidx.annotation.Nullable; +import androidx.annotation.StringRes; + +import java.util.Comparator; + +/** + * A class responsible for saving bar view information. + */ +public class BarViewInfo implements Comparable<BarViewInfo> { + + private final Drawable mIcon; + private View.OnClickListener mListener; + private @StringRes int mSummaryRes; + // A number indicates this bar's height. The larger number shows a higher bar view. + private int mBarNumber; + // A real height of bar view. + private int mBarHeight; + + /** + * Construct a BarViewInfo instance. + * + * @param icon the icon of bar view. + * @param barNumber the number of bar view. The larger number show a more height of bar view. + * @param summaryRes the resource identifier of the string resource to be displayed + * @return BarViewInfo object. + */ + public BarViewInfo(Drawable icon, @IntRange(from = 0) int barNumber, + @StringRes int summaryRes) { + mIcon = icon; + mBarNumber = barNumber; + mSummaryRes = summaryRes; + } + + /** + * Set number for bar view. + * + * @param barNumber the number of bar view. The larger number shows a higher bar view. + */ + public void setBarNumber(@IntRange(from = 0) int barNumber) { + mBarNumber = barNumber; + } + + /** + * Set summary resource for bar view + * + * @param resId the resource identifier of the string resource to be displayed + */ + public void setSummary(@StringRes int resId) { + mSummaryRes = resId; + } + + /** + * Set a click listner for bar view. + * + * @param listener the click listner is attached on bar view. + */ + public void setClickListener(@Nullable View.OnClickListener listener) { + mListener = listener; + } + + /** + * Get the icon of bar view. + * + * @return Drawable the icon of bar view. + */ + public Drawable getIcon() { + return mIcon; + } + + /** + * Get the OnClickListener of bar view. + * + * @return View.OnClickListener the click listner of bar view. + */ + public View.OnClickListener getListener() { + return mListener; + } + + /** + * Get the real height of bar view. + * + * @return the real height of bar view. + */ + public int getBarHeight() { + return mBarHeight; + } + + /** + * Get summary resource of bar view. + * + * @return summary resource of bar view. + */ + public int getSummaryRes() { + return mSummaryRes; + } + + /** + * Get the number of app uses this permisssion. + * + * @return the number of app uses this permission. + */ + public int getBarNumber() { + return mBarNumber; + } + + @Override + public int compareTo(BarViewInfo other) { + // Descending order + return Comparator.comparingInt((BarViewInfo barViewInfo) -> barViewInfo.mBarNumber) + .compare(other, this); + } + + /** + * Set a real height for bar view. + * + * <p>This method should not be called by outside. It usually should be called by + * {@link BarChartPreference#caculateAllBarViewsHeight} + * + * @param barHeight the real bar height for bar view. + */ + void setBarHeight(@IntRange(from = 0) int barHeight) { + mBarHeight = barHeight; + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java index 2b7babd06b47..7914a0b9d6fd 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java @@ -321,6 +321,11 @@ public class BluetoothEventManager { // Dispatch device add callback to show bonded but // not connected devices in discovery mode dispatchDeviceAdded(cachedDevice); + Log.d(TAG, "DeviceFoundHandler found bonded and not connected device:" + + cachedDevice); + } else { + Log.d(TAG, "DeviceFoundHandler found existing CachedBluetoothDevice:" + + cachedDevice); } cachedDevice.setRssi(rssi); cachedDevice.setJustDiscovered(true); diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java index 1bffff753513..e28c894ff8f3 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java @@ -116,8 +116,8 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> void onProfileStateChanged(LocalBluetoothProfile profile, int newProfileState) { if (BluetoothUtils.D) { - Log.d(TAG, "onProfileStateChanged: profile " + profile + - " newProfileState " + newProfileState); + Log.d(TAG, "onProfileStateChanged: profile " + profile + ", device=" + mDevice + + ", newProfileState " + newProfileState); } if (mLocalAdapter.getState() == BluetoothAdapter.STATE_TURNING_OFF) { @@ -570,7 +570,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> } if (BluetoothUtils.D) { - Log.e(TAG, "updating profiles for " + mDevice.getAliasName()); + Log.e(TAG, "updating profiles for " + mDevice.getAliasName() + ", " + mDevice); BluetoothClass bluetoothClass = mDevice.getBluetoothClass(); if (bluetoothClass != null) Log.v(TAG, "Class: " + bluetoothClass.toString()); diff --git a/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java index 9572fb3c629d..20fe495f1afa 100644 --- a/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java +++ b/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java @@ -1,6 +1,8 @@ package com.android.settingslib.core; import android.content.Context; +import android.text.TextUtils; +import android.util.Log; import androidx.preference.Preference; import androidx.preference.PreferenceGroup; @@ -11,6 +13,8 @@ import androidx.preference.PreferenceScreen; */ public abstract class AbstractPreferenceController { + private static final String TAG = "AbstractPrefController"; + protected final Context mContext; public AbstractPreferenceController(Context context) { @@ -22,6 +26,10 @@ public abstract class AbstractPreferenceController { */ public void displayPreference(PreferenceScreen screen) { final String prefKey = getPreferenceKey(); + if (TextUtils.isEmpty(prefKey)) { + Log.w(TAG, "Skipping displayPreference because key is empty:" + getClass().getName()); + return; + } if (isAvailable()) { setVisible(screen, prefKey, true /* visible */); if (this instanceof Preference.OnPreferenceChangeListener) { diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/AbstractPreferenceControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/AbstractPreferenceControllerTest.java index 28de1914838f..f695e0c35df6 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/AbstractPreferenceControllerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/AbstractPreferenceControllerTest.java @@ -35,6 +35,8 @@ import org.robolectric.RuntimeEnvironment; @RunWith(RobolectricTestRunner.class) public class AbstractPreferenceControllerTest { + private static final String KEY_PREF = "test_pref"; + @Mock private PreferenceScreen mScreen; @@ -47,9 +49,9 @@ public class AbstractPreferenceControllerTest { MockitoAnnotations.initMocks(this); mContext = RuntimeEnvironment.application; mPreference = new Preference(mContext); - mPreference.setKey(TestPrefController.KEY_PREF); - when(mScreen.findPreference(TestPrefController.KEY_PREF)).thenReturn(mPreference); - mTestPrefController = new TestPrefController(mContext); + mPreference.setKey(KEY_PREF); + when(mScreen.findPreference(KEY_PREF)).thenReturn(mPreference); + mTestPrefController = new TestPrefController(mContext, KEY_PREF); } @Test @@ -62,15 +64,24 @@ public class AbstractPreferenceControllerTest { } @Test + public void displayPref_noKey_shouldDoNothing() { + mTestPrefController.isAvailable = true; + + mTestPrefController.displayPreference(mScreen); + + assertThat(mPreference.isVisible()).isTrue(); + } + + @Test public void setVisible_prefIsVisible_shouldSetToVisible() { - mTestPrefController.setVisible(mScreen, TestPrefController.KEY_PREF, true /* visible */); + mTestPrefController.setVisible(mScreen, KEY_PREF, true /* visible */); assertThat(mPreference.isVisible()).isTrue(); } @Test public void setVisible_prefNotVisible_shouldSetToInvisible() { - mTestPrefController.setVisible(mScreen, TestPrefController.KEY_PREF, false /* visible */); + mTestPrefController.setVisible(mScreen, KEY_PREF, false /* visible */); assertThat(mPreference.isVisible()).isFalse(); } @@ -92,13 +103,14 @@ public class AbstractPreferenceControllerTest { } private static class TestPrefController extends AbstractPreferenceController { - private static final String KEY_PREF = "test_pref"; private static final CharSequence TEST_SUMMARY = "Test"; public boolean isAvailable; + private final String mPrefKey; - public TestPrefController(Context context) { + TestPrefController(Context context, String key) { super(context); + mPrefKey = key; } @Override @@ -113,7 +125,7 @@ public class AbstractPreferenceControllerTest { @Override public String getPreferenceKey() { - return KEY_PREF; + return mPrefKey; } @Override diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/BarChartPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/BarChartPreferenceTest.java new file mode 100644 index 000000000000..371c3d46dfb4 --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/BarChartPreferenceTest.java @@ -0,0 +1,211 @@ +/* + * 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.settingslib.widget; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.view.View; +import android.widget.TextView; + +import androidx.preference.PreferenceViewHolder; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +@RunWith(RobolectricTestRunner.class) +public class BarChartPreferenceTest { + + private Context mContext; + private View mBarChartView; + private Drawable mIcon; + private BarView mBarView1; + private BarView mBarView2; + private BarView mBarView3; + private BarView mBarView4; + private PreferenceViewHolder mHolder; + private BarChartPreference mPreference; + + @Before + public void setUp() { + mContext = RuntimeEnvironment.application; + mBarChartView = View.inflate(mContext, R.layout.settings_bar_chart, null /* parent */); + mHolder = PreferenceViewHolder.createInstanceForTests(mBarChartView); + mPreference = new BarChartPreference(mContext, null /* attrs */); + mPreference.setBarChartTitle(R.string.debug_app); + mPreference.setBarChartDetails(R.string.debug_app); + + + mIcon = mContext.getDrawable(R.drawable.ic_menu); + mBarView1 = (BarView) mBarChartView.findViewById(R.id.bar_view1); + mBarView2 = (BarView) mBarChartView.findViewById(R.id.bar_view2); + mBarView3 = (BarView) mBarChartView.findViewById(R.id.bar_view3); + mBarView4 = (BarView) mBarChartView.findViewById(R.id.bar_view4); + } + + @Test + public void setBarChartTitleRes_setTitleRes_showInBarChartTitle() { + final TextView titleView = (TextView) mBarChartView.findViewById(R.id.bar_chart_title); + + mPreference.setBarChartTitle(R.string.debug_app); + mPreference.onBindViewHolder(mHolder); + + assertThat(titleView.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(titleView.getText()).isEqualTo(mContext.getText(R.string.debug_app)); + } + + @Test + public void setBarChartDetailsRes_setDetailsRes_showInBarChartDetails() { + final TextView detailsView = (TextView) mBarChartView.findViewById(R.id.bar_chart_details); + + mPreference.setBarChartDetails(R.string.debug_app); + mPreference.onBindViewHolder(mHolder); + + assertThat(detailsView.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(detailsView.getText()).isEqualTo(mContext.getText(R.string.debug_app)); + } + + @Test + public void setBarChartDetailsClickListener_setClickListener_detailsViewAttachClickListener() { + final TextView detailsView = (TextView) mBarChartView.findViewById(R.id.bar_chart_details); + + mPreference.setBarChartDetailsClickListener(v -> { + }); + mPreference.onBindViewHolder(mHolder); + + assertThat(detailsView.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(detailsView.hasOnClickListeners()).isTrue(); + } + + @Test + public void setAllBarViewsInfo_setOneBarViewInfo_showOneBarView() { + final BarViewInfo[] barViewsInfo = new BarViewInfo[]{ + new BarViewInfo(mIcon, 10 /* barNumber */, R.string.debug_app) + }; + + mPreference.setAllBarViewsInfo(barViewsInfo); + mPreference.onBindViewHolder(mHolder); + + assertThat(mBarView1.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mBarView1.getTitle()).isEqualTo("10"); + + assertThat(mBarView2.getVisibility()).isEqualTo(View.GONE); + assertThat(mBarView3.getVisibility()).isEqualTo(View.GONE); + assertThat(mBarView4.getVisibility()).isEqualTo(View.GONE); + } + + @Test + public void setAllBarViewsInfo_setTwoBarViewsInfo_showTwoBarViews() { + final BarViewInfo[] barViewsInfo = new BarViewInfo[]{ + new BarViewInfo(mIcon, 20 /* barNumber */, R.string.debug_app), + new BarViewInfo(mIcon, 10 /* barNumber */, R.string.debug_app) + }; + + mPreference.setAllBarViewsInfo(barViewsInfo); + mPreference.onBindViewHolder(mHolder); + + assertThat(mBarView1.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mBarView1.getTitle()).isEqualTo("20"); + assertThat(mBarView2.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mBarView2.getTitle()).isEqualTo("10"); + + assertThat(mBarView3.getVisibility()).isEqualTo(View.GONE); + assertThat(mBarView4.getVisibility()).isEqualTo(View.GONE); + } + + @Test + public void setAllBarViewsInfo_setThreeBarViewsInfo_showThreeBarViews() { + final BarViewInfo[] barViewsInfo = new BarViewInfo[]{ + new BarViewInfo(mIcon, 20 /* barNumber */, R.string.debug_app), + new BarViewInfo(mIcon, 10 /* barNumber */, R.string.debug_app), + new BarViewInfo(mIcon, 5 /* barNumber */, R.string.debug_app) + }; + + mPreference.setAllBarViewsInfo(barViewsInfo); + mPreference.onBindViewHolder(mHolder); + + assertThat(mBarView1.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mBarView1.getTitle()).isEqualTo("20"); + assertThat(mBarView2.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mBarView2.getTitle()).isEqualTo("10"); + assertThat(mBarView3.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mBarView3.getTitle()).isEqualTo("5"); + + assertThat(mBarView4.getVisibility()).isEqualTo(View.GONE); + } + + @Test + public void setAllBarViewsInfo_setFourBarViewsInfo_showFourBarViews() { + final BarViewInfo[] barViewsInfo = new BarViewInfo[]{ + new BarViewInfo(mIcon, 20 /* barNumber */, R.string.debug_app), + new BarViewInfo(mIcon, 10 /* barNumber */, R.string.debug_app), + new BarViewInfo(mIcon, 5 /* barNumber */, R.string.debug_app), + new BarViewInfo(mIcon, 2 /* barNumber */, R.string.debug_app), + }; + + mPreference.setAllBarViewsInfo(barViewsInfo); + mPreference.onBindViewHolder(mHolder); + + assertThat(mBarView1.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mBarView1.getTitle()).isEqualTo("20"); + assertThat(mBarView2.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mBarView2.getTitle()).isEqualTo("10"); + assertThat(mBarView3.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mBarView3.getTitle()).isEqualTo("5"); + assertThat(mBarView4.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mBarView4.getTitle()).isEqualTo("2"); + } + + @Test + public void setAllBarViewsInfo_setFourBarViewsInfo_barViewWasSortedInDescending() { + final BarViewInfo[] barViewsInfo = new BarViewInfo[]{ + new BarViewInfo(mIcon, 30 /* barNumber */, R.string.debug_app), + new BarViewInfo(mIcon, 50 /* barNumber */, R.string.debug_app), + new BarViewInfo(mIcon, 5 /* barNumber */, R.string.debug_app), + new BarViewInfo(mIcon, 10 /* barNumber */, R.string.debug_app), + }; + + mPreference.setAllBarViewsInfo(barViewsInfo); + mPreference.onBindViewHolder(mHolder); + + assertThat(mBarView1.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mBarView1.getTitle()).isEqualTo("50"); + assertThat(mBarView2.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mBarView2.getTitle()).isEqualTo("30"); + assertThat(mBarView3.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mBarView3.getTitle()).isEqualTo("10"); + assertThat(mBarView4.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mBarView4.getTitle()).isEqualTo("5"); + } + + @Test + public void setAllBarViewsInfo_setValidSummaryRes_barViewShouldShowSummary() { + final BarViewInfo[] barViewsInfo = new BarViewInfo[]{ + new BarViewInfo(mIcon, 10 /* barNumber */, R.string.debug_app), + }; + + mPreference.setAllBarViewsInfo(barViewsInfo); + mPreference.onBindViewHolder(mHolder); + + assertThat(mBarView1.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mBarView1.getSummary()).isEqualTo(mContext.getText(R.string.debug_app)); + } +} diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index ad44b9a1ee9f..8be67d9a7a51 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -115,6 +115,8 @@ android_library { "mockito-target-inline-minus-junit4", "testables", "truth-prebuilt", + "dagger2-2.19", + "jsr330" ], libs: [ "android.test.runner", @@ -125,6 +127,7 @@ android_library { "--extra-packages", "com.android.keyguard:com.android.systemui", ], + annotation_processors: ["dagger2-compiler-2.19"], } android_app { diff --git a/packages/SystemUI/docs/dagger.md b/packages/SystemUI/docs/dagger.md new file mode 100644 index 000000000000..8bfa1c28d985 --- /dev/null +++ b/packages/SystemUI/docs/dagger.md @@ -0,0 +1,158 @@ +# Dagger 2 in SystemUI +*Dagger 2 is a dependency injection framework that compiles annotations to code +to create dependencies without reflection* + +## Recommended reading + +Go read about Dagger 2. + +TODO: Add some links. + +## State of the world + +Dagger 2 has been turned on for SystemUI and a early first pass has been taken +for converting everything in Dependency.java to use Dagger. Since a lot of +SystemUI depends on Dependency, stubs have been added to Dependency to proxy +any gets through to the instances provided by dagger, this will allow migration +of SystemUI through a number of CLs. + +### How it works in SystemUI + +For the classes that we're using in Dependency and are switching to dagger, the +equivalent dagger version is using @Singleton and only having one instance. +To have the single instance span all of SystemUI and be easily accessible for +other components, there is a single root Component that exists that generates +these. The component lives in SystemUIFactory and is called SystemUIRootComponent. + +```java +@Singleton +@Component(modules = {SystemUIFactory.class, DependencyProvider.class, ContextHolder.class}) +public interface SystemUIRootComponent { + @Singleton + Dependency.DependencyInjector createDependency(); +} +``` + +The root modules are what provides the global singleton dependencies across +SystemUI. ContextHolder is just a wrapper that provides a context. +SystemUIFactory @Provide dependencies that need to be overridden by SystemUI +variants (like other form factors). DependencyProvider provides or binds any +remaining depedencies required. + +### Adding injection to a new SystemUI object + +Anything that depends on any @Singleton provider from SystemUIRootComponent +should be declared as a Subcomponent of the root component, this requires +declaring your own interface for generating your own modules or just the +object you need injected. The subcomponent also needs to be added to +SystemUIRootComponent in SystemUIFactory so it can be acquired. + +```java +public interface SystemUIRootComponent { ++ @Singleton ++ Dependency.DependencyInjector createDependency(); +} + +public class Dependency extends SystemUI { + ... ++ @Subcomponent ++ public interface DependencyInjector { ++ Dependency createSystemUI(); ++ } +} +``` + +For objects that extend SystemUI and require injection, you can define an +injector that creates the injected object for you. This other class should +be referenced in @string/config_systemUIServiceComponents. + +```java +public static class DependencyCreator implements Injector { + @Override + public SystemUI apply(Context context) { + return SystemUIFactory.getInstance().getRootComponent() + .createDependency() + .createSystemUI(); + } +} +``` + +### Adding a new injectable object + +First tag the constructor with @Inject. Also tag it with @Singleton if only one +instance should be created. + +```java +@Singleton +public class SomethingController { + @Inject + public SomethingController(Context context, + @Named(MAIN_HANDLER_NAME) Handler mainHandler) { + // context and mainHandler will be automatically populated. + } +} +``` + +If you have an interface class and an implementation class, dagger needs to know +how to map it. The simplest way to do this is to add a provides method to +DependencyProvider. + +```java +public class DependencyProvider { + ... + @Singleton + @Provide + public SomethingController provideSomethingController(Context context, + @Named(MAIN_HANDLER_NAME) Handler mainHandler) { + return new SomethingControllerImpl(context, mainHandler); + } +} +``` + +If you need to access this from Dependency#get, then add an adapter to Dependency +that maps to the instance provided by Dagger. The changes should be similar +to the following diff. + +```java +public class Dependency { + ... + @Inject Lazy<SomethingController> mSomethingController; + ... + public void start() { + ... + mProviders.put(SomethingController.class, mSomethingController::get); + } +} +``` + +### Using injection with Fragments + +Fragments are created as part of the FragmentManager, so they need to be +setup so the manager knows how to create them. To do that, add a method +to com.android.systemui.fragments.FragmentService$FragmentCreator that +returns your fragment class. Thats all thats required, once the method +exists, FragmentService will automatically pick it up and use injection +whenever your fragment needs to be created. + +```java +public interface FragmentCreator { ++ NavigationBarFragment createNavigationBar(); +} +``` + +If you need to create your fragment (i.e. for the add or replace transaction), +then the FragmentHostManager can do this for you. + +```java +FragmentHostManager.get(view).create(NavigationBarFragment.class); +``` + +## TODO List + + - Eliminate usages of Depndency#get + - Add support for Fragments to handle injection automatically + - (this could be through dagger2-android or something custom) + - Reduce number of things with @Provide in DependencyProvider (many can be + @Inject instead) + - Migrate as many remaining DependencyProvider instances to @Bind + - Add links in above TODO diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags index ee94aedfe399..d57fe8a36d61 100644 --- a/packages/SystemUI/proguard.flags +++ b/packages/SystemUI/proguard.flags @@ -17,6 +17,7 @@ -keep class com.android.systemui.car.CarSystemUIFactory -keep class com.android.systemui.SystemUIFactory -keep class * extends com.android.systemui.SystemUI +-keep class * implements com.android.systemui.SystemUI$Injector -keepclasseswithmembers class * { public <init>(android.content.Context, android.util.AttributeSet); @@ -27,4 +28,7 @@ -keep class com.android.systemui.plugins.** { *; } +-keep class com.android.systemui.fragments.FragmentService$FragmentCreator { + *; +} -keep class androidx.core.app.CoreComponentFactory diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml index ffc7b3cca357..b9966cf33646 100644 --- a/packages/SystemUI/res-keyguard/values/styles.xml +++ b/packages/SystemUI/res-keyguard/values/styles.xml @@ -33,7 +33,7 @@ <item name="android:gravity">center_horizontal|center_vertical</item> <item name="android:background">@null</item> <item name="android:textSize">32sp</item> - <item name="android:fontFamily">@*android:string/config_headlineFontFamilyLight</item> + <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> <item name="android:textColor">?attr/wallpaperTextColor</item> <item name="android:paddingBottom">-16dp</item> </style> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 61efbd5c2248..889db66526b8 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -290,7 +290,7 @@ <!-- SystemUI Services: The classes of the stuff to start. --> <string-array name="config_systemUIServiceComponents" translatable="false"> - <item>com.android.systemui.Dependency</item> + <item>com.android.systemui.Dependency$DependencyCreator</item> <item>com.android.systemui.util.NotificationChannels</item> <item>com.android.systemui.statusbar.CommandQueue$CommandQueueStart</item> <item>com.android.systemui.keyguard.KeyguardViewMediator</item> @@ -318,7 +318,7 @@ <!-- SystemUI Services (per user): The classes of the stuff to start for each user. This is a subset of the config_systemUIServiceComponents --> <string-array name="config_systemUIServiceComponentsPerUser" translatable="false"> - <item>com.android.systemui.Dependency</item> + <item>com.android.systemui.Dependency$DependencyCreator</item> <item>com.android.systemui.util.NotificationChannels</item> </string-array> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginEnabler.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginEnabler.java index 74fd13f9564e..01b012d1fc84 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginEnabler.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginEnabler.java @@ -14,12 +14,40 @@ package com.android.systemui.shared.plugins; +import android.annotation.IntDef; import android.content.ComponentName; /** * Enables and disables plugins. */ public interface PluginEnabler { - void setEnabled(ComponentName component, boolean enabled); + + int ENABLED = 0; + int DISABLED_MANUALLY = 1; + int DISABLED_INVALID_VERSION = 1; + int DISABLED_FROM_EXPLICIT_CRASH = 2; + int DISABLED_FROM_SYSTEM_CRASH = 3; + + @IntDef({ENABLED, DISABLED_MANUALLY, DISABLED_INVALID_VERSION, DISABLED_FROM_EXPLICIT_CRASH, + DISABLED_FROM_SYSTEM_CRASH}) + @interface DisableReason { + } + + /** Enables plugin via the PackageManager. */ + void setEnabled(ComponentName component); + + /** Disables a plugin via the PackageManager and records the reason for disabling. */ + void setDisabled(ComponentName component, @DisableReason int reason); + + /** Returns true if the plugin is enabled in the PackageManager. */ boolean isEnabled(ComponentName component); + + /** + * Returns the reason that a plugin is disabled, (if it is). + * + * It should return {@link #ENABLED} if the plugin is turned on. + * It should return {@link #DISABLED_MANUALLY} if the plugin is off but the reason is unknown. + */ + @DisableReason + int getDisableReason(ComponentName componentName); } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstanceManager.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstanceManager.java index 8e7fadb5c7cb..523720d54eec 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstanceManager.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstanceManager.java @@ -136,7 +136,7 @@ public class PluginInstanceManager<T extends Plugin> { ArrayList<PluginInfo> plugins = new ArrayList<PluginInfo>(mPluginHandler.mPlugins); for (PluginInfo info : plugins) { if (className.startsWith(info.mPackage)) { - disable(info); + disable(info, PluginEnabler.DISABLED_FROM_EXPLICIT_CRASH); disableAny = true; } } @@ -146,12 +146,13 @@ public class PluginInstanceManager<T extends Plugin> { public boolean disableAll() { ArrayList<PluginInfo> plugins = new ArrayList<PluginInfo>(mPluginHandler.mPlugins); for (int i = 0; i < plugins.size(); i++) { - disable(plugins.get(i)); + disable(plugins.get(i), PluginEnabler.DISABLED_FROM_SYSTEM_CRASH); } return plugins.size() != 0; } - private void disable(PluginInfo info) { + private void disable(PluginInfo info, + @PluginEnabler.DisableReason int reason) { // Live by the sword, die by the sword. // Misbehaving plugins get disabled and won't come back until uninstall/reinstall. @@ -162,9 +163,9 @@ public class PluginInstanceManager<T extends Plugin> { // Don't disable whitelisted plugins as they are a part of the OS. return; } - Log.w(TAG, "Disabling plugin " + info.mPackage + "/" + info.mClass); - mManager.getPluginEnabler().setEnabled(new ComponentName(info.mPackage, info.mClass), - false); + ComponentName pluginComponent = new ComponentName(info.mPackage, info.mClass); + Log.w(TAG, "Disabling plugin " + pluginComponent.flattenToShortString()); + mManager.getPluginEnabler().setDisabled(pluginComponent, reason); } public <T> boolean dependsOn(Plugin p, Class<T> cls) { diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java index dc2a9bd5105b..10b5f1c64d85 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java @@ -184,6 +184,7 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage mListening = true; IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); filter.addAction(Intent.ACTION_PACKAGE_CHANGED); + filter.addAction(Intent.ACTION_PACKAGE_REPLACED); filter.addAction(Intent.ACTION_PACKAGE_REMOVED); filter.addAction(PLUGIN_CHANGED); filter.addAction(DISABLE_PLUGIN); @@ -214,12 +215,13 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage // Don't disable whitelisted plugins as they are a part of the OS. return; } - getPluginEnabler().setEnabled(component, false); + getPluginEnabler().setDisabled(component, PluginEnabler.DISABLED_INVALID_VERSION); mContext.getSystemService(NotificationManager.class).cancel(component.getClassName(), SystemMessage.NOTE_PLUGIN); } else { Uri data = intent.getData(); String pkg = data.getEncodedSchemeSpecificPart(); + ComponentName componentName = ComponentName.unflattenFromString(pkg); if (mOneShotPackages.contains(pkg)) { int icon = mContext.getResources().getIdentifier("tuner", "drawable", mContext.getPackageName()); @@ -256,6 +258,17 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage Log.v(TAG, "Reloading " + pkg); } } + if (Intent.ACTION_PACKAGE_REPLACED.equals(intent.getAction())) { + @PluginEnabler.DisableReason int disableReason = + getPluginEnabler().getDisableReason(componentName); + if (disableReason == PluginEnabler.DISABLED_FROM_EXPLICIT_CRASH + || disableReason == PluginEnabler.DISABLED_FROM_SYSTEM_CRASH + || disableReason == PluginEnabler.DISABLED_INVALID_VERSION) { + Log.i(TAG, "Re-enabling previously disabled plugin that has been " + + "updated: " + componentName.flattenToShortString()); + getPluginEnabler().setEnabled(componentName); + } + } if (!Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) { for (PluginInstanceManager manager : mPluginMap.values()) { manager.onPackageChange(pkg); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java index c41ef0ede89e..576660431d82 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java @@ -24,6 +24,7 @@ import android.animation.LayoutTransition; import android.animation.ObjectAnimator; import android.animation.PropertyValuesHolder; import android.annotation.ColorInt; +import android.annotation.StyleRes; import android.app.PendingIntent; import android.content.Context; import android.graphics.Color; @@ -444,9 +445,11 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe static class KeyguardSliceButton extends Button implements ConfigurationController.ConfigurationListener { + @StyleRes + private static int sStyleId = R.style.TextAppearance_Keyguard_Secondary; + public KeyguardSliceButton(Context context) { - super(context, null /* attrs */, 0 /* styleAttr */, - com.android.keyguard.R.style.TextAppearance_Keyguard_Secondary); + super(context, null /* attrs */, 0 /* styleAttr */, sStyleId); onDensityOrFontScaleChanged(); setEllipsize(TruncateAt.END); } @@ -469,6 +472,11 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe } @Override + public void onOverlayChanged() { + setTextAppearance(sStyleId); + } + + @Override public void setText(CharSequence text, BufferType type) { super.setText(text, type); updatePadding(); diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java index 38dadd4b961d..874cdccb8794 100644 --- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java +++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java @@ -325,6 +325,17 @@ public class BatteryMeterView extends LinearLayout implements .inflate(R.layout.battery_percentage_view, null); } + /** + * Updates percent view by removing old one and reinflating if necessary + */ + public void updatePercentView() { + if (mBatteryPercentView != null) { + removeView(mBatteryPercentView); + mBatteryPercentView = null; + } + updateShowPercent(); + } + private void updatePercentText() { if (mBatteryPercentView != null) { mBatteryPercentView.setText( diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java index 327ffcd24762..445d156d289c 100644 --- a/packages/SystemUI/src/com/android/systemui/Dependency.java +++ b/packages/SystemUI/src/com/android/systemui/Dependency.java @@ -14,20 +14,15 @@ package com.android.systemui; +import android.annotation.Nullable; import android.content.Context; import android.content.res.Configuration; -import android.hardware.SensorManager; import android.hardware.SensorPrivacyManager; import android.os.Handler; -import android.os.HandlerThread; import android.os.Looper; -import android.os.Process; -import android.os.ServiceManager; -import android.os.UserHandle; import android.util.ArrayMap; import android.util.DisplayMetrics; import android.view.IWindowManager; -import android.view.WindowManagerGlobal; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.ColorDisplayController; @@ -36,92 +31,87 @@ import com.android.internal.statusbar.IStatusBarService; import com.android.internal.util.Preconditions; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.systemui.appops.AppOpsController; -import com.android.systemui.appops.AppOpsControllerImpl; import com.android.systemui.assist.AssistManager; +import com.android.systemui.bubbles.BubbleController; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.fragments.FragmentService; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.PluginDependencyProvider; -import com.android.systemui.plugins.PluginInitializerImpl; import com.android.systemui.plugins.VolumeDialogController; import com.android.systemui.power.EnhancedEstimates; -import com.android.systemui.power.EnhancedEstimatesImpl; -import com.android.systemui.power.PowerNotificationWarnings; import com.android.systemui.power.PowerUI; import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.shared.plugins.PluginManager; -import com.android.systemui.shared.plugins.PluginManagerImpl; +import com.android.systemui.statusbar.AmbientPulseManager; import com.android.systemui.statusbar.DisplayNavigationBarController; +import com.android.systemui.statusbar.NotificationListener; +import com.android.systemui.statusbar.NotificationLockscreenUserManager; +import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationRemoteInputManager; +import com.android.systemui.statusbar.NotificationViewHierarchyManager; +import com.android.systemui.statusbar.SmartReplyController; +import com.android.systemui.statusbar.StatusBarStateController; import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.notification.NotificationData.KeyguardEnvironment; -import com.android.systemui.statusbar.phone.ConfigurationControllerImpl; -import com.android.systemui.statusbar.phone.DarkIconDispatcherImpl; -import com.android.systemui.statusbar.phone.KeyguardEnvironmentImpl; +import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.NotificationRowBinder; +import com.android.systemui.statusbar.notification.VisualStabilityManager; +import com.android.systemui.statusbar.notification.logging.NotificationLogger; +import com.android.systemui.statusbar.notification.row.NotificationBlockingHelperManager; +import com.android.systemui.statusbar.notification.row.NotificationGutsManager; +import com.android.systemui.statusbar.phone.KeyguardDismissUtil; import com.android.systemui.statusbar.phone.LightBarController; import com.android.systemui.statusbar.phone.LockscreenGestureLogger; import com.android.systemui.statusbar.phone.ManagedProfileController; -import com.android.systemui.statusbar.phone.ManagedProfileControllerImpl; +import com.android.systemui.statusbar.phone.NotificationGroupAlertTransferHelper; +import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.ShadeController; -import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.StatusBarIconController; -import com.android.systemui.statusbar.phone.StatusBarIconControllerImpl; -import com.android.systemui.statusbar.phone.StatusBarRemoteInputCallback; import com.android.systemui.statusbar.phone.StatusBarWindowController; import com.android.systemui.statusbar.policy.AccessibilityController; import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; import com.android.systemui.statusbar.policy.BatteryController; -import com.android.systemui.statusbar.policy.BatteryControllerImpl; import com.android.systemui.statusbar.policy.BluetoothController; -import com.android.systemui.statusbar.policy.BluetoothControllerImpl; import com.android.systemui.statusbar.policy.CastController; -import com.android.systemui.statusbar.policy.CastControllerImpl; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.DarkIconDispatcher; import com.android.systemui.statusbar.policy.DataSaverController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; -import com.android.systemui.statusbar.policy.DeviceProvisionedControllerImpl; import com.android.systemui.statusbar.policy.ExtensionController; -import com.android.systemui.statusbar.policy.ExtensionControllerImpl; import com.android.systemui.statusbar.policy.FlashlightController; -import com.android.systemui.statusbar.policy.FlashlightControllerImpl; import com.android.systemui.statusbar.policy.HotspotController; -import com.android.systemui.statusbar.policy.HotspotControllerImpl; import com.android.systemui.statusbar.policy.IconLogger; -import com.android.systemui.statusbar.policy.IconLoggerImpl; import com.android.systemui.statusbar.policy.KeyguardMonitor; -import com.android.systemui.statusbar.policy.KeyguardMonitorImpl; import com.android.systemui.statusbar.policy.LocationController; -import com.android.systemui.statusbar.policy.LocationControllerImpl; import com.android.systemui.statusbar.policy.NetworkController; -import com.android.systemui.statusbar.policy.NetworkControllerImpl; import com.android.systemui.statusbar.policy.NextAlarmController; -import com.android.systemui.statusbar.policy.NextAlarmControllerImpl; +import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler; import com.android.systemui.statusbar.policy.RotationLockController; -import com.android.systemui.statusbar.policy.RotationLockControllerImpl; import com.android.systemui.statusbar.policy.SecurityController; -import com.android.systemui.statusbar.policy.SecurityControllerImpl; +import com.android.systemui.statusbar.policy.SmartReplyConstants; import com.android.systemui.statusbar.policy.UserInfoController; -import com.android.systemui.statusbar.policy.UserInfoControllerImpl; import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.statusbar.policy.ZenModeController; -import com.android.systemui.statusbar.policy.ZenModeControllerImpl; import com.android.systemui.tuner.TunablePadding.TunablePaddingService; import com.android.systemui.tuner.TunerService; -import com.android.systemui.tuner.TunerServiceImpl; import com.android.systemui.util.AsyncSensorManager; import com.android.systemui.util.leak.GarbageMonitor; import com.android.systemui.util.leak.LeakDetector; import com.android.systemui.util.leak.LeakReporter; -import com.android.systemui.volume.VolumeDialogControllerImpl; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.HashMap; import java.util.function.Consumer; +import javax.inject.Inject; +import javax.inject.Named; + +import dagger.Lazy; +import dagger.Subcomponent; + /** * Class to handle ugly dependencies throughout sysui until we determine the * long-term dependency injection solution. @@ -143,235 +133,304 @@ public class Dependency extends SystemUI { /** * Key for getting a background Looper for background work. */ - public static final DependencyKey<Looper> BG_LOOPER = new DependencyKey<>("background_looper"); + public static final String BG_LOOPER_NAME = "background_looper"; + /** + * Key for getting a background Handler for background work. + */ + public static final String BG_HANDLER_NAME = "background_handler"; + /** + * Key for getting a Handler for receiving time tick broadcasts on. + */ + public static final String TIME_TICK_HANDLER_NAME = "time_tick_handler"; + /** + * Generic handler on the main thread. + */ + public static final String MAIN_HANDLER_NAME = "main_handler"; + + /** + * An email address to send memory leak reports to by default. + */ + public static final String LEAK_REPORT_EMAIL_NAME = "leak_report_email"; + + /** + * Key for getting a background Looper for background work. + */ + public static final DependencyKey<Looper> BG_LOOPER = new DependencyKey<>(BG_LOOPER_NAME); /** * Key for getting a background Handler for background work. */ - public static final DependencyKey<Handler> BG_HANDLER = new DependencyKey<>("background_handler"); + public static final DependencyKey<Handler> BG_HANDLER = new DependencyKey<>(BG_HANDLER_NAME); /** * Key for getting a Handler for receiving time tick broadcasts on. */ public static final DependencyKey<Handler> TIME_TICK_HANDLER = - new DependencyKey<>("time_tick_handler"); + new DependencyKey<>(TIME_TICK_HANDLER_NAME); /** * Generic handler on the main thread. */ - public static final DependencyKey<Handler> MAIN_HANDLER = new DependencyKey<>("main_handler"); + public static final DependencyKey<Handler> MAIN_HANDLER = + new DependencyKey<>(MAIN_HANDLER_NAME); /** * An email address to send memory leak reports to by default. */ - public static final DependencyKey<String> LEAK_REPORT_EMAIL - = new DependencyKey<>("leak_report_email"); + public static final DependencyKey<String> LEAK_REPORT_EMAIL = + new DependencyKey<>(LEAK_REPORT_EMAIL_NAME); private final ArrayMap<Object, Object> mDependencies = new ArrayMap<>(); private final ArrayMap<Object, DependencyProvider> mProviders = new ArrayMap<>(); + @Inject Lazy<ActivityStarter> mActivityStarter; + @Inject Lazy<ActivityStarterDelegate> mActivityStarterDelegate; + @Inject Lazy<AsyncSensorManager> mAsyncSensorManager; + @Inject Lazy<BluetoothController> mBluetoothController; + @Inject Lazy<LocationController> mLocationController; + @Inject Lazy<RotationLockController> mRotationLockController; + @Inject Lazy<NetworkController> mNetworkController; + @Inject Lazy<ZenModeController> mZenModeController; + @Inject Lazy<HotspotController> mHotspotController; + @Inject Lazy<CastController> mCastController; + @Inject Lazy<FlashlightController> mFlashlightController; + @Inject Lazy<UserSwitcherController> mUserSwitcherController; + @Inject Lazy<UserInfoController> mUserInfoController; + @Inject Lazy<KeyguardMonitor> mKeyguardMonitor; + @Inject Lazy<BatteryController> mBatteryController; + @Inject Lazy<ColorDisplayController> mColorDisplayController; + @Inject Lazy<ManagedProfileController> mManagedProfileController; + @Inject Lazy<NextAlarmController> mNextAlarmController; + @Inject Lazy<DataSaverController> mDataSaverController; + @Inject Lazy<AccessibilityController> mAccessibilityController; + @Inject Lazy<DeviceProvisionedController> mDeviceProvisionedController; + @Inject Lazy<PluginManager> mPluginManager; + @Inject Lazy<AssistManager> mAssistManager; + @Inject Lazy<SecurityController> mSecurityController; + @Inject Lazy<LeakDetector> mLeakDetector; + @Inject Lazy<LeakReporter> mLeakReporter; + @Inject Lazy<GarbageMonitor> mGarbageMonitor; + @Inject Lazy<IconLogger> mIconLogger; + @Inject Lazy<TunerService> mTunerService; + @Inject Lazy<StatusBarWindowController> mStatusBarWindowController; + @Inject Lazy<DarkIconDispatcher> mDarkIconDispatcher; + @Inject Lazy<ConfigurationController> mConfigurationController; + @Inject Lazy<StatusBarIconController> mStatusBarIconController; + @Inject Lazy<ScreenLifecycle> mScreenLifecycle; + @Inject Lazy<WakefulnessLifecycle> mWakefulnessLifecycle; + @Inject Lazy<FragmentService> mFragmentService; + @Inject Lazy<ExtensionController> mExtensionController; + @Inject Lazy<PluginDependencyProvider> mPluginDependencyProvider; + @Inject Lazy<LocalBluetoothManager> mLocalBluetoothManager; + @Inject Lazy<VolumeDialogController> mVolumeDialogController; + @Inject Lazy<MetricsLogger> mMetricsLogger; + @Inject Lazy<AccessibilityManagerWrapper> mAccessibilityManagerWrapper; + @Inject Lazy<SysuiColorExtractor> mSysuiColorExtractor; + @Inject Lazy<TunablePaddingService> mTunablePaddingService; + @Inject Lazy<ForegroundServiceController> mForegroundServiceController; + @Inject Lazy<UiOffloadThread> mUiOffloadThread; + @Inject Lazy<PowerUI.WarningsUI> mWarningsUI; + @Inject Lazy<LightBarController> mLightBarController; + @Inject Lazy<IWindowManager> mIWindowManager; + @Inject Lazy<OverviewProxyService> mOverviewProxyService; + @Inject Lazy<EnhancedEstimates> mEnhancedEstimates; + @Inject Lazy<VibratorHelper> mVibratorHelper; + @Inject Lazy<IStatusBarService> mIStatusBarService; + @Inject Lazy<DisplayMetrics> mDisplayMetrics; + @Inject Lazy<LockscreenGestureLogger> mLockscreenGestureLogger; + @Inject Lazy<KeyguardEnvironment> mKeyguardEnvironment; + @Inject Lazy<ShadeController> mShadeController; + @Inject Lazy<NotificationRemoteInputManager.Callback> mNotificationRemoteInputManagerCallback; + @Inject Lazy<InitController> mInitController; + @Inject Lazy<AppOpsController> mAppOpsController; + @Inject Lazy<DisplayNavigationBarController> mDisplayNavigationBarController; + @Inject Lazy<StatusBarStateController> mStatusBarStateController; + @Inject Lazy<NotificationLockscreenUserManager> mNotificationLockscreenUserManager; + @Inject Lazy<NotificationGroupAlertTransferHelper> mNotificationGroupAlertTransferHelper; + @Inject Lazy<NotificationGroupManager> mNotificationGroupManager; + @Inject Lazy<VisualStabilityManager> mVisualStabilityManager; + @Inject Lazy<NotificationGutsManager> mNotificationGutsManager; + @Inject Lazy<NotificationMediaManager> mNotificationMediaManager; + @Inject Lazy<AmbientPulseManager> mAmbientPulseManager; + @Inject Lazy<NotificationBlockingHelperManager> mNotificationBlockingHelperManager; + @Inject Lazy<NotificationRemoteInputManager> mNotificationRemoteInputManager; + @Inject Lazy<SmartReplyConstants> mSmartReplyConstants; + @Inject Lazy<NotificationListener> mNotificationListener; + @Inject Lazy<NotificationLogger> mNotificationLogger; + @Inject Lazy<NotificationViewHierarchyManager> mNotificationViewHierarchyManager; + @Inject Lazy<NotificationRowBinder> mNotificationRowBinder; + @Inject Lazy<KeyguardDismissUtil> mKeyguardDismissUtil; + @Inject Lazy<SmartReplyController> mSmartReplyController; + @Inject Lazy<RemoteInputQuickSettingsDisabler> mRemoteInputQuickSettingsDisabler; + @Inject Lazy<BubbleController> mBubbleController; + @Inject Lazy<NotificationEntryManager> mNotificationEntryManager; + @Inject Lazy<SensorPrivacyManager> mSensorPrivacyManager; + @Inject @Named(BG_LOOPER_NAME) Lazy<Looper> mBgLooper; + @Inject @Named(BG_HANDLER_NAME) Lazy<Handler> mBgHandler; + @Inject @Named(MAIN_HANDLER_NAME) Lazy<Handler> mMainHandler; + @Inject @Named(TIME_TICK_HANDLER_NAME) Lazy<Handler> mTimeTickHandler; + @Nullable + @Inject @Named(LEAK_REPORT_EMAIL_NAME) Lazy<String> mLeakReportEmail; + + @Inject + public Dependency() { + } + @Override public void start() { // TODO: Think about ways to push these creation rules out of Dependency to cut down // on imports. - mProviders.put(TIME_TICK_HANDLER, () -> { - HandlerThread thread = new HandlerThread("TimeTick"); - thread.start(); - return new Handler(thread.getLooper()); - }); - mProviders.put(BG_LOOPER, () -> { - HandlerThread thread = new HandlerThread("SysUiBg", - Process.THREAD_PRIORITY_BACKGROUND); - thread.start(); - return thread.getLooper(); - }); - mProviders.put(BG_HANDLER, () -> new Handler(getDependency(BG_LOOPER))); - mProviders.put(MAIN_HANDLER, () -> new Handler(Looper.getMainLooper())); - mProviders.put(ActivityStarter.class, () -> new ActivityStarterDelegate()); - mProviders.put(ActivityStarterDelegate.class, () -> - getDependency(ActivityStarter.class)); - - mProviders.put(AsyncSensorManager.class, () -> - new AsyncSensorManager(mContext.getSystemService(SensorManager.class), - getDependency(PluginManager.class))); + mProviders.put(TIME_TICK_HANDLER, mTimeTickHandler::get); + mProviders.put(BG_LOOPER, mBgLooper::get); + mProviders.put(BG_HANDLER, mBgHandler::get); + mProviders.put(MAIN_HANDLER, mMainHandler::get); + mProviders.put(ActivityStarter.class, mActivityStarter::get); + mProviders.put(ActivityStarterDelegate.class, mActivityStarterDelegate::get); - mProviders.put(SensorPrivacyManager.class, () -> - mContext.getSystemService(SensorPrivacyManager.class)); + mProviders.put(AsyncSensorManager.class, mAsyncSensorManager::get); - mProviders.put(BluetoothController.class, () -> - new BluetoothControllerImpl(mContext, getDependency(BG_LOOPER))); + mProviders.put(BluetoothController.class, mBluetoothController::get); + mProviders.put(SensorPrivacyManager.class, mSensorPrivacyManager::get); - mProviders.put(LocationController.class, () -> - new LocationControllerImpl(mContext, getDependency(BG_LOOPER))); + mProviders.put(LocationController.class, mLocationController::get); - mProviders.put(RotationLockController.class, () -> - new RotationLockControllerImpl(mContext)); + mProviders.put(RotationLockController.class, mRotationLockController::get); - mProviders.put(NetworkController.class, () -> - new NetworkControllerImpl(mContext, getDependency(BG_LOOPER), - getDependency(DeviceProvisionedController.class))); + mProviders.put(NetworkController.class, mNetworkController::get); - mProviders.put(ZenModeController.class, () -> - new ZenModeControllerImpl(mContext, getDependency(MAIN_HANDLER))); + mProviders.put(ZenModeController.class, mZenModeController::get); - mProviders.put(HotspotController.class, () -> - new HotspotControllerImpl(mContext)); + mProviders.put(HotspotController.class, mHotspotController::get); - mProviders.put(CastController.class, () -> - new CastControllerImpl(mContext)); + mProviders.put(CastController.class, mCastController::get); - mProviders.put(FlashlightController.class, () -> - new FlashlightControllerImpl(mContext)); + mProviders.put(FlashlightController.class, mFlashlightController::get); - mProviders.put(KeyguardMonitor.class, () -> - new KeyguardMonitorImpl(mContext)); + mProviders.put(KeyguardMonitor.class, mKeyguardMonitor::get); - mProviders.put(UserSwitcherController.class, () -> - new UserSwitcherController(mContext, getDependency(KeyguardMonitor.class), - getDependency(MAIN_HANDLER), getDependency(ActivityStarter.class))); + mProviders.put(UserSwitcherController.class, mUserSwitcherController::get); - mProviders.put(UserInfoController.class, () -> - new UserInfoControllerImpl(mContext)); + mProviders.put(UserInfoController.class, mUserInfoController::get); - mProviders.put(BatteryController.class, () -> - new BatteryControllerImpl(mContext)); + mProviders.put(BatteryController.class, mBatteryController::get); - mProviders.put(ColorDisplayController.class, () -> - new ColorDisplayController(mContext)); + mProviders.put(ColorDisplayController.class, mColorDisplayController::get); - mProviders.put(ManagedProfileController.class, () -> - new ManagedProfileControllerImpl(mContext)); + mProviders.put(ManagedProfileController.class, mManagedProfileController::get); - mProviders.put(NextAlarmController.class, () -> - new NextAlarmControllerImpl(mContext)); + mProviders.put(NextAlarmController.class, mNextAlarmController::get); - mProviders.put(DataSaverController.class, () -> - get(NetworkController.class).getDataSaverController()); + mProviders.put(DataSaverController.class, mDataSaverController::get); - mProviders.put(AccessibilityController.class, () -> - new AccessibilityController(mContext)); + mProviders.put(AccessibilityController.class, mAccessibilityController::get); - mProviders.put(DeviceProvisionedController.class, () -> - new DeviceProvisionedControllerImpl(mContext)); + mProviders.put(DeviceProvisionedController.class, mDeviceProvisionedController::get); - mProviders.put(PluginManager.class, () -> - new PluginManagerImpl(mContext, new PluginInitializerImpl())); + mProviders.put(PluginManager.class, mPluginManager::get); - mProviders.put(AssistManager.class, () -> - new AssistManager(getDependency(DeviceProvisionedController.class), mContext)); + mProviders.put(AssistManager.class, mAssistManager::get); - mProviders.put(SecurityController.class, () -> - new SecurityControllerImpl(mContext)); + mProviders.put(SecurityController.class, mSecurityController::get); - mProviders.put(LeakDetector.class, LeakDetector::create); + mProviders.put(LeakDetector.class, mLeakDetector::get); - mProviders.put(LEAK_REPORT_EMAIL, () -> null); + mProviders.put(LEAK_REPORT_EMAIL, mLeakReportEmail::get); - mProviders.put(LeakReporter.class, () -> new LeakReporter( - mContext, - getDependency(LeakDetector.class), - getDependency(LEAK_REPORT_EMAIL))); + mProviders.put(LeakReporter.class, mLeakReporter::get); - mProviders.put( - GarbageMonitor.class, - () -> - new GarbageMonitor( - mContext, - getDependency(BG_LOOPER), - getDependency(LeakDetector.class), - getDependency(LeakReporter.class))); + mProviders.put(GarbageMonitor.class, mGarbageMonitor::get); - mProviders.put(TunerService.class, () -> - new TunerServiceImpl(mContext)); + mProviders.put(TunerService.class, mTunerService::get); - mProviders.put(StatusBarWindowController.class, () -> - new StatusBarWindowController(mContext)); + mProviders.put(StatusBarWindowController.class, mStatusBarWindowController::get); - mProviders.put(DarkIconDispatcher.class, () -> - new DarkIconDispatcherImpl(mContext)); + mProviders.put(DarkIconDispatcher.class, mDarkIconDispatcher::get); - mProviders.put(ConfigurationController.class, () -> - new ConfigurationControllerImpl(mContext)); + mProviders.put(ConfigurationController.class, mConfigurationController::get); - mProviders.put(StatusBarIconController.class, () -> - new StatusBarIconControllerImpl(mContext)); + mProviders.put(StatusBarIconController.class, mStatusBarIconController::get); - mProviders.put(ScreenLifecycle.class, () -> - new ScreenLifecycle()); + mProviders.put(ScreenLifecycle.class, mScreenLifecycle::get); - mProviders.put(WakefulnessLifecycle.class, () -> - new WakefulnessLifecycle()); + mProviders.put(WakefulnessLifecycle.class, mWakefulnessLifecycle::get); - mProviders.put(FragmentService.class, () -> - new FragmentService()); + mProviders.put(FragmentService.class, mFragmentService::get); - mProviders.put(ExtensionController.class, () -> - new ExtensionControllerImpl(mContext)); + mProviders.put(ExtensionController.class, mExtensionController::get); - mProviders.put(PluginDependencyProvider.class, () -> - new PluginDependencyProvider(get(PluginManager.class))); + mProviders.put(PluginDependencyProvider.class, mPluginDependencyProvider::get); - mProviders.put(LocalBluetoothManager.class, () -> - LocalBluetoothManager.create(mContext, getDependency(BG_HANDLER), - UserHandle.ALL)); + mProviders.put(LocalBluetoothManager.class, mLocalBluetoothManager::get); - mProviders.put(VolumeDialogController.class, () -> - new VolumeDialogControllerImpl(mContext)); + mProviders.put(VolumeDialogController.class, mVolumeDialogController::get); - mProviders.put(MetricsLogger.class, () -> new MetricsLogger()); + mProviders.put(MetricsLogger.class, mMetricsLogger::get); - mProviders.put(AccessibilityManagerWrapper.class, - () -> new AccessibilityManagerWrapper(mContext)); + mProviders.put(AccessibilityManagerWrapper.class, mAccessibilityManagerWrapper::get); - // Creating a new instance will trigger color extraction. - // Thankfully this only happens once - during boot - and WallpaperManagerService - // loads colors from cache. - mProviders.put(SysuiColorExtractor.class, () -> new SysuiColorExtractor(mContext)); + mProviders.put(SysuiColorExtractor.class, mSysuiColorExtractor::get); - mProviders.put(TunablePaddingService.class, () -> new TunablePaddingService()); + mProviders.put(TunablePaddingService.class, mTunablePaddingService::get); - mProviders.put(ForegroundServiceController.class, - () -> new ForegroundServiceControllerImpl(mContext)); + mProviders.put(ForegroundServiceController.class, mForegroundServiceController::get); - mProviders.put(UiOffloadThread.class, UiOffloadThread::new); + mProviders.put(UiOffloadThread.class, mUiOffloadThread::get); - mProviders.put(PowerUI.WarningsUI.class, () -> new PowerNotificationWarnings(mContext)); + mProviders.put(PowerUI.WarningsUI.class, mWarningsUI::get); - mProviders.put(IconLogger.class, () -> new IconLoggerImpl(mContext, - getDependency(BG_LOOPER), getDependency(MetricsLogger.class))); + mProviders.put(IconLogger.class, mIconLogger::get); - mProviders.put(LightBarController.class, () -> new LightBarController(mContext)); + mProviders.put(LightBarController.class, mLightBarController::get); - mProviders.put(IWindowManager.class, () -> WindowManagerGlobal.getWindowManagerService()); + mProviders.put(IWindowManager.class, mIWindowManager::get); - mProviders.put(OverviewProxyService.class, () -> new OverviewProxyService(mContext)); + mProviders.put(OverviewProxyService.class, mOverviewProxyService::get); - mProviders.put(EnhancedEstimates.class, () -> new EnhancedEstimatesImpl()); + mProviders.put(EnhancedEstimates.class, mEnhancedEstimates::get); - mProviders.put(VibratorHelper.class, () -> new VibratorHelper(mContext)); + mProviders.put(VibratorHelper.class, mVibratorHelper::get); - mProviders.put(IStatusBarService.class, () -> IStatusBarService.Stub.asInterface( - ServiceManager.getService(Context.STATUS_BAR_SERVICE))); + mProviders.put(IStatusBarService.class, mIStatusBarService::get); - // Single instance of DisplayMetrics, gets updated by StatusBar, but can be used - // anywhere it is needed. - mProviders.put(DisplayMetrics.class, () -> new DisplayMetrics()); + mProviders.put(DisplayMetrics.class, mDisplayMetrics::get); - mProviders.put(LockscreenGestureLogger.class, () -> new LockscreenGestureLogger()); + mProviders.put(LockscreenGestureLogger.class, mLockscreenGestureLogger::get); - mProviders.put(KeyguardEnvironment.class, () -> new KeyguardEnvironmentImpl()); - mProviders.put(ShadeController.class, () -> - SysUiServiceProvider.getComponent(mContext, StatusBar.class)); + mProviders.put(KeyguardEnvironment.class, mKeyguardEnvironment::get); + mProviders.put(ShadeController.class, mShadeController::get); mProviders.put(NotificationRemoteInputManager.Callback.class, - () -> new StatusBarRemoteInputCallback(mContext)); - - mProviders.put(InitController.class, InitController::new); - - mProviders.put(AppOpsController.class, () -> - new AppOpsControllerImpl(mContext, getDependency(BG_LOOPER))); - - mProviders.put(DisplayNavigationBarController.class, () -> - new DisplayNavigationBarController(mContext, getDependency(MAIN_HANDLER))); - - // Put all dependencies above here so the factory can override them if it wants. - SystemUIFactory.getInstance().injectDependencies(mProviders, mContext); + mNotificationRemoteInputManagerCallback::get); + + mProviders.put(InitController.class, mInitController::get); + + mProviders.put(AppOpsController.class, mAppOpsController::get); + + mProviders.put(DisplayNavigationBarController.class, + mDisplayNavigationBarController::get); + + mProviders.put(StatusBarStateController.class, mStatusBarStateController::get); + mProviders.put(NotificationLockscreenUserManager.class, + mNotificationLockscreenUserManager::get); + mProviders.put(VisualStabilityManager.class, mVisualStabilityManager::get); + mProviders.put(NotificationGroupManager.class, mNotificationGroupManager::get); + mProviders.put(NotificationGroupAlertTransferHelper.class, + mNotificationGroupAlertTransferHelper::get); + mProviders.put(NotificationMediaManager.class, mNotificationMediaManager::get); + mProviders.put(NotificationGutsManager.class, mNotificationGutsManager::get); + mProviders.put(AmbientPulseManager.class, mAmbientPulseManager::get); + mProviders.put(NotificationBlockingHelperManager.class, + mNotificationBlockingHelperManager::get); + mProviders.put(NotificationRemoteInputManager.class, + mNotificationRemoteInputManager::get); + mProviders.put(SmartReplyConstants.class, mSmartReplyConstants::get); + mProviders.put(NotificationListener.class, mNotificationListener::get); + mProviders.put(NotificationLogger.class, mNotificationLogger::get); + mProviders.put(NotificationViewHierarchyManager.class, + mNotificationViewHierarchyManager::get); + mProviders.put(NotificationRowBinder.class, mNotificationRowBinder::get); + mProviders.put(KeyguardDismissUtil.class, mKeyguardDismissUtil::get); + mProviders.put(SmartReplyController.class, mSmartReplyController::get); + mProviders.put(RemoteInputQuickSettingsDisabler.class, + mRemoteInputQuickSettingsDisabler::get); + mProviders.put(BubbleController.class, mBubbleController::get); + mProviders.put(NotificationEntryManager.class, mNotificationEntryManager::get); sDependency = this; } @@ -484,4 +543,20 @@ public class Dependency extends SystemUI { return mDisplayName; } } + + @Subcomponent + public interface DependencyInjector { + void createSystemUI(Dependency dependency); + } + + public static class DependencyCreator implements Injector { + @Override + public SystemUI apply(Context context) { + Dependency dependency = new Dependency(); + SystemUIFactory.getInstance().getRootComponent() + .createDependency() + .createSystemUI(dependency); + return dependency; + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/DependencyProvider.java new file mode 100644 index 000000000000..76336bb8127f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/DependencyProvider.java @@ -0,0 +1,571 @@ +/* + * 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; + +import static com.android.systemui.Dependency.BG_HANDLER_NAME; +import static com.android.systemui.Dependency.BG_LOOPER_NAME; +import static com.android.systemui.Dependency.LEAK_REPORT_EMAIL_NAME; +import static com.android.systemui.Dependency.MAIN_HANDLER_NAME; +import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME; + +import android.annotation.Nullable; +import android.content.Context; +import android.hardware.SensorManager; +import android.hardware.SensorPrivacyManager; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Process; +import android.os.ServiceManager; +import android.os.UserHandle; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.IWindowManager; +import android.view.WindowManagerGlobal; + +import com.android.internal.app.ColorDisplayController; +import com.android.internal.logging.MetricsLogger; +import com.android.internal.statusbar.IStatusBarService; +import com.android.settingslib.bluetooth.LocalBluetoothManager; +import com.android.systemui.appops.AppOpsController; +import com.android.systemui.appops.AppOpsControllerImpl; +import com.android.systemui.colorextraction.SysuiColorExtractor; +import com.android.systemui.keyguard.ScreenLifecycle; +import com.android.systemui.keyguard.WakefulnessLifecycle; +import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.plugins.PluginDependencyProvider; +import com.android.systemui.plugins.PluginInitializerImpl; +import com.android.systemui.plugins.VolumeDialogController; +import com.android.systemui.power.PowerNotificationWarnings; +import com.android.systemui.power.PowerUI; +import com.android.systemui.recents.OverviewProxyService; +import com.android.systemui.shared.plugins.PluginManager; +import com.android.systemui.shared.plugins.PluginManagerImpl; +import com.android.systemui.statusbar.DisplayNavigationBarController; +import com.android.systemui.statusbar.NotificationRemoteInputManager; +import com.android.systemui.statusbar.VibratorHelper; +import com.android.systemui.statusbar.phone.ConfigurationControllerImpl; +import com.android.systemui.statusbar.phone.DarkIconDispatcherImpl; +import com.android.systemui.statusbar.phone.LightBarController; +import com.android.systemui.statusbar.phone.LockscreenGestureLogger; +import com.android.systemui.statusbar.phone.ManagedProfileController; +import com.android.systemui.statusbar.phone.ManagedProfileControllerImpl; +import com.android.systemui.statusbar.phone.ShadeController; +import com.android.systemui.statusbar.phone.StatusBar; +import com.android.systemui.statusbar.phone.StatusBarIconController; +import com.android.systemui.statusbar.phone.StatusBarIconControllerImpl; +import com.android.systemui.statusbar.phone.StatusBarRemoteInputCallback; +import com.android.systemui.statusbar.phone.StatusBarWindowController; +import com.android.systemui.statusbar.policy.AccessibilityController; +import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; +import com.android.systemui.statusbar.policy.BatteryController; +import com.android.systemui.statusbar.policy.BatteryControllerImpl; +import com.android.systemui.statusbar.policy.BluetoothController; +import com.android.systemui.statusbar.policy.BluetoothControllerImpl; +import com.android.systemui.statusbar.policy.CastController; +import com.android.systemui.statusbar.policy.CastControllerImpl; +import com.android.systemui.statusbar.policy.ConfigurationController; +import com.android.systemui.statusbar.policy.DarkIconDispatcher; +import com.android.systemui.statusbar.policy.DataSaverController; +import com.android.systemui.statusbar.policy.DeviceProvisionedController; +import com.android.systemui.statusbar.policy.DeviceProvisionedControllerImpl; +import com.android.systemui.statusbar.policy.ExtensionController; +import com.android.systemui.statusbar.policy.ExtensionControllerImpl; +import com.android.systemui.statusbar.policy.FlashlightController; +import com.android.systemui.statusbar.policy.FlashlightControllerImpl; +import com.android.systemui.statusbar.policy.HotspotController; +import com.android.systemui.statusbar.policy.HotspotControllerImpl; +import com.android.systemui.statusbar.policy.IconLogger; +import com.android.systemui.statusbar.policy.IconLoggerImpl; +import com.android.systemui.statusbar.policy.KeyguardMonitor; +import com.android.systemui.statusbar.policy.KeyguardMonitorImpl; +import com.android.systemui.statusbar.policy.LocationController; +import com.android.systemui.statusbar.policy.LocationControllerImpl; +import com.android.systemui.statusbar.policy.NetworkController; +import com.android.systemui.statusbar.policy.NetworkControllerImpl; +import com.android.systemui.statusbar.policy.NextAlarmController; +import com.android.systemui.statusbar.policy.NextAlarmControllerImpl; +import com.android.systemui.statusbar.policy.RotationLockController; +import com.android.systemui.statusbar.policy.RotationLockControllerImpl; +import com.android.systemui.statusbar.policy.SecurityController; +import com.android.systemui.statusbar.policy.SecurityControllerImpl; +import com.android.systemui.statusbar.policy.UserInfoController; +import com.android.systemui.statusbar.policy.UserInfoControllerImpl; +import com.android.systemui.statusbar.policy.UserSwitcherController; +import com.android.systemui.statusbar.policy.ZenModeController; +import com.android.systemui.statusbar.policy.ZenModeControllerImpl; +import com.android.systemui.tuner.TunablePadding; +import com.android.systemui.tuner.TunablePadding.TunablePaddingService; +import com.android.systemui.tuner.TunerService; +import com.android.systemui.tuner.TunerServiceImpl; +import com.android.systemui.util.AsyncSensorManager; +import com.android.systemui.util.leak.GarbageMonitor; +import com.android.systemui.util.leak.LeakDetector; +import com.android.systemui.util.leak.LeakReporter; +import com.android.systemui.volume.VolumeDialogControllerImpl; + +import javax.inject.Named; +import javax.inject.Singleton; + +import dagger.Module; +import dagger.Provides; + +/** + * Provides dependencies for the root component of sysui injection. + * See SystemUI/docs/dagger.md + */ +@Module +public class DependencyProvider { + + @Singleton + @Provides + @Named(TIME_TICK_HANDLER_NAME) + public Handler provideHandler() { + HandlerThread thread = new HandlerThread("TimeTick"); + thread.start(); + return new Handler(thread.getLooper()); + } + + @Singleton + @Provides + @Named(BG_LOOPER_NAME) + public Looper provideBgLooper() { + HandlerThread thread = new HandlerThread("SysUiBg", + Process.THREAD_PRIORITY_BACKGROUND); + thread.start(); + return thread.getLooper(); + } + + @Singleton + @Provides + @Named(BG_HANDLER_NAME) + public Handler provideBgHandler(@Named(BG_LOOPER_NAME) Looper bgLooper) { + return new Handler(bgLooper); + } + + @Singleton + @Provides + @Named(MAIN_HANDLER_NAME) + public Handler provideMainHandler() { + return new Handler(Looper.getMainLooper()); + } + + @Singleton + @Provides + public ActivityStarter provideActivityStarter() { + return new ActivityStarterDelegate(); + } + + @Singleton + @Provides + public InitController provideInitController() { + return new InitController(); + } + + @Singleton + @Provides + public ActivityStarterDelegate provideActivityStarterDelegate(ActivityStarter starter) { + return (ActivityStarterDelegate) starter; + } + + @Singleton + @Provides + public AsyncSensorManager provideAsyncSensorManager(Context context, PluginManager manager) { + return new AsyncSensorManager(context.getSystemService(SensorManager.class), + manager); + + } + + @Singleton + @Provides + public BluetoothController provideBluetoothController(Context context, + @Named(BG_LOOPER_NAME) Looper looper) { + return new BluetoothControllerImpl(context, looper); + + } + + @Singleton + @Provides + public LocationController provideLocationController(Context context, + @Named(BG_LOOPER_NAME) Looper bgLooper) { + return new LocationControllerImpl(context, bgLooper); + + } + + @Singleton + @Provides + public RotationLockController provideRotationLockController(Context context) { + return new RotationLockControllerImpl(context); + + } + + @Singleton + @Provides + public NetworkController provideNetworkController(Context context, + @Named(BG_LOOPER_NAME) Looper bgLooper, DeviceProvisionedController controller) { + return new NetworkControllerImpl(context, bgLooper, + controller); + + } + + @Singleton + @Provides + public ZenModeController provideZenModeController(Context context, + @Named(MAIN_HANDLER_NAME) Handler mainHandler) { + return new ZenModeControllerImpl(context, mainHandler); + + } + + @Singleton + @Provides + public HotspotController provideHotspotController(Context context) { + return new HotspotControllerImpl(context); + + } + + @Singleton + @Provides + public CastController provideCastController(Context context) { + return new CastControllerImpl(context); + + } + + @Singleton + @Provides + public FlashlightController provideFlashlightController(Context context) { + return new FlashlightControllerImpl(context); + + } + + @Singleton + @Provides + public KeyguardMonitor provideKeyguardMonitor(Context context) { + return new KeyguardMonitorImpl(context); + + } + + @Singleton + @Provides + public UserSwitcherController provideUserSwitcherController(Context context, + KeyguardMonitor keyguardMonitor, @Named(MAIN_HANDLER_NAME) Handler mainHandler, + ActivityStarter activityStarter) { + return new UserSwitcherController(context, keyguardMonitor, mainHandler, activityStarter); + } + + @Singleton + @Provides + public UserInfoController provideUserInfoContrller(Context context) { + return new UserInfoControllerImpl(context); + + } + + @Singleton + @Provides + public BatteryController provideBatteryController(Context context) { + return new BatteryControllerImpl(context); + + } + + @Singleton + @Provides + public ColorDisplayController provideColorDisplayController(Context context) { + return new ColorDisplayController(context); + + } + + @Singleton + @Provides + public ManagedProfileController provideManagedProfileController(Context context) { + return new ManagedProfileControllerImpl(context); + + } + + @Singleton + @Provides + public NextAlarmController provideNextAlarmController(Context context) { + return new NextAlarmControllerImpl(context); + + } + + @Singleton + @Provides + public DataSaverController provideDataSaverController(NetworkController networkController) { + return networkController.getDataSaverController(); + } + + @Singleton + @Provides + public AccessibilityController provideAccessibilityController(Context context) { + return new AccessibilityController(context); + + } + + @Singleton + @Provides + public DeviceProvisionedController provideDeviceProvisionedController(Context context) { + return new DeviceProvisionedControllerImpl(context); + + } + + @Singleton + @Provides + public PluginManager providePluginManager(Context context) { + return new PluginManagerImpl(context, new PluginInitializerImpl()); + + } + + @Singleton + @Provides + public SecurityController provideSecurityController(Context context) { + return new SecurityControllerImpl(context); + + } + + @Singleton + @Provides + public LeakDetector provideLeakDetector() { + return LeakDetector.create(); + + } + + @Singleton + @Provides + public LeakReporter provideLeakReporter(Context context, LeakDetector detector, + @Nullable @Named(LEAK_REPORT_EMAIL_NAME) String email) { + return new LeakReporter(context, detector, email); + } + + @Singleton + @Provides + public GarbageMonitor provideGarbageMonitor(Context context, + @Named(BG_LOOPER_NAME) Looper bgLooper, LeakDetector detector, LeakReporter reporter) { + return new GarbageMonitor(context, bgLooper, detector, reporter); + } + + @Singleton + @Provides + public TunerService provideTunerService(Context context) { + return new TunerServiceImpl(context); + + } + + @Singleton + @Provides + public StatusBarWindowController provideStatusBarWindowController(Context context) { + return new StatusBarWindowController(context); + + } + + @Singleton + @Provides + public DarkIconDispatcher provideDarkIconDispatcher(Context context) { + return new DarkIconDispatcherImpl(context); + } + + @Singleton + @Provides + public ConfigurationController provideConfigurationController(Context context) { + return new ConfigurationControllerImpl(context); + + } + + @Singleton + @Provides + public StatusBarIconController provideStatusBarIconController(Context context) { + return new StatusBarIconControllerImpl(context); + + } + + @Singleton + @Provides + public ScreenLifecycle provideScreenLifecycle() { + return new ScreenLifecycle(); + } + + @Singleton + @Provides + public WakefulnessLifecycle provideWakefulnessLifecycle() { + return new WakefulnessLifecycle(); + } + + @Singleton + @Provides + public ExtensionController provideExtensionController(Context context) { + return new ExtensionControllerImpl(context); + } + + @Singleton + @Provides + public PluginDependencyProvider providePluginDependency(PluginManager pluginManager) { + return new PluginDependencyProvider(pluginManager); + } + + @Singleton + @Provides + public LocalBluetoothManager provideLocalBluetoothController(Context context, + @Named(BG_HANDLER_NAME) Handler bgHandler) { + return LocalBluetoothManager.create(context, bgHandler, + UserHandle.ALL); + } + + @Singleton + @Provides + public VolumeDialogController provideVolumeDialogController(Context context) { + return new VolumeDialogControllerImpl(context); + + } + + @Singleton + @Provides + public MetricsLogger provideMetricsLogger() { + return new MetricsLogger(); + + } + + @Singleton + @Provides + public AccessibilityManagerWrapper provideAccessibilityManagerWrapper(Context context) { + return new AccessibilityManagerWrapper(context); + + } + + @Singleton + @Provides + // Creating a new instance will trigger color extraction. + // Thankfully this only happens once - during boot - and WallpaperManagerService + // loads colors from cache. + public SysuiColorExtractor provideSysuiColorExtractor(Context context) { + return new SysuiColorExtractor(context); + + } + + @Singleton + @Provides + public TunablePadding.TunablePaddingService provideTunablePaddingService() { + return new TunablePaddingService(); + + } + + @Singleton + @Provides + public ForegroundServiceController provideForegroundService(Context context) { + return new ForegroundServiceControllerImpl(context); + + } + + @Singleton + @Provides + public UiOffloadThread provideUiOffloadThread() { + Log.d("TestTest", "provideUiOffloadThread"); + return new UiOffloadThread(); + } + + @Singleton + @Provides + public PowerUI.WarningsUI provideWarningsUi(Context context) { + return new PowerNotificationWarnings(context); + } + + @Singleton + @Provides + public IconLogger provideIconLogger(Context context, @Named(BG_LOOPER_NAME) Looper bgLooper, + MetricsLogger logger) { + return new IconLoggerImpl(context, bgLooper, logger); + } + + @Singleton + @Provides + public LightBarController provideLightBarController(Context context) { + return new LightBarController(context); + } + + @Singleton + @Provides + public IWindowManager provideIWindowManager() { + return WindowManagerGlobal.getWindowManagerService(); + } + + @Singleton + @Provides + public OverviewProxyService provideOverviewProxyService(Context context) { + return new OverviewProxyService(context); + } + + @Singleton + @Provides + public VibratorHelper provideVibratorHelper(Context context) { + return new VibratorHelper(context); + + } + + @Singleton + @Provides + public IStatusBarService provideIStatusBarService() { + return IStatusBarService.Stub.asInterface( + ServiceManager.getService(Context.STATUS_BAR_SERVICE)); + } + + @Singleton + @Provides + // Single instance of DisplayMetrics, gets updated by StatusBar, but can be used +// anywhere it is needed. + public DisplayMetrics provideDisplayMetrics() { + return new DisplayMetrics(); + + } + + @Singleton + @Provides + public LockscreenGestureLogger provideLockscreenGestureLogger() { + return new LockscreenGestureLogger(); + } + + @Singleton + @Provides + public ShadeController provideShadeController(Context context) { + return SysUiServiceProvider.getComponent(context, StatusBar.class); + } + + @Singleton + @Provides + public NotificationRemoteInputManager.Callback provideNotificationRemoteInputManager( + Context context) { + return new StatusBarRemoteInputCallback(context); + + } + + @Singleton + @Provides + public AppOpsController provideAppOpsController(Context context, + @Named(BG_LOOPER_NAME) Looper bgLooper) { + return new AppOpsControllerImpl(context, bgLooper); + + } + + @Singleton + @Provides + public DisplayNavigationBarController provideDisplayNavigationBarController(Context context, + @Named(MAIN_HANDLER_NAME) Handler mainHandler) { + return new DisplayNavigationBarController(context, mainHandler); + } + + @Singleton + @Provides + public SensorPrivacyManager provideSensorPrivacyManager(Context context) { + return context.getSystemService(SensorPrivacyManager.class); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/SystemUI.java b/packages/SystemUI/src/com/android/systemui/SystemUI.java index 30fbef6cbefb..78a12467cd1c 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUI.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUI.java @@ -24,6 +24,7 @@ import android.os.Bundle; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.Map; +import java.util.function.Function; public abstract class SystemUI implements SysUiServiceProvider { public Context mContext; @@ -61,4 +62,7 @@ public abstract class SystemUI implements SysUiServiceProvider { n.addExtras(extras); } + + public interface Injector extends Function<Context, SystemUI> { + } } diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java index 92aa652131ba..1d8a21d13fa2 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java @@ -170,7 +170,11 @@ public class SystemUIApplication extends Application implements SysUiServiceProv Class cls; try { cls = Class.forName(clsName); - mServices[i] = (SystemUI) cls.newInstance(); + Object o = cls.newInstance(); + if (o instanceof SystemUI.Injector) { + o = ((SystemUI.Injector) o).apply(this); + } + mServices[i] = (SystemUI) o; } catch(ClassNotFoundException ex){ throw new RuntimeException(ex); } catch (IllegalAccessException ex) { diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java index 867c9175d308..1a2473e6b858 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java @@ -16,9 +16,11 @@ package com.android.systemui; +import static com.android.systemui.Dependency.LEAK_REPORT_EMAIL_NAME; + +import android.annotation.Nullable; import android.app.AlarmManager; import android.content.Context; -import android.util.ArrayMap; import android.util.Log; import android.view.ViewGroup; @@ -26,56 +28,56 @@ import com.android.internal.colorextraction.ColorExtractor.GradientColors; import com.android.internal.util.function.TriConsumer; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.ViewMediatorCallback; -import com.android.systemui.Dependency.DependencyProvider; -import com.android.systemui.bubbles.BubbleController; +import com.android.systemui.assist.AssistManager; import com.android.systemui.classifier.FalsingManager; +import com.android.systemui.fragments.FragmentService; import com.android.systemui.keyguard.DismissCallbackRegistry; +import com.android.systemui.power.EnhancedEstimates; +import com.android.systemui.power.EnhancedEstimatesImpl; import com.android.systemui.qs.QSTileHost; -import com.android.systemui.statusbar.AmbientPulseManager; import com.android.systemui.statusbar.KeyguardIndicationController; import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationLockscreenUserManagerImpl; -import com.android.systemui.statusbar.NotificationMediaManager; -import com.android.systemui.statusbar.NotificationRemoteInputManager; -import com.android.systemui.statusbar.NotificationViewHierarchyManager; import com.android.systemui.statusbar.ScrimView; -import com.android.systemui.statusbar.SmartReplyController; -import com.android.systemui.statusbar.StatusBarStateController; +import com.android.systemui.statusbar.notification.NotificationData; import com.android.systemui.statusbar.notification.NotificationEntryManager; -import com.android.systemui.statusbar.notification.VisualStabilityManager; -import com.android.systemui.statusbar.notification.logging.NotificationLogger; -import com.android.systemui.statusbar.notification.row.NotificationBlockingHelperManager; -import com.android.systemui.statusbar.notification.row.NotificationGutsManager; +import com.android.systemui.statusbar.notification.NotificationRowBinder; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.KeyguardBouncer; -import com.android.systemui.statusbar.phone.KeyguardDismissUtil; +import com.android.systemui.statusbar.phone.KeyguardEnvironmentImpl; import com.android.systemui.statusbar.phone.LockIcon; import com.android.systemui.statusbar.phone.LockscreenWallpaper; -import com.android.systemui.statusbar.phone.NotificationGroupAlertTransferHelper; -import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.NotificationIconAreaController; import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.phone.ScrimState; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; -import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler; -import com.android.systemui.statusbar.policy.SmartReplyConstants; +import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.volume.VolumeDialogComponent; import java.util.function.Consumer; +import javax.inject.Named; +import javax.inject.Singleton; + +import dagger.Component; +import dagger.Module; +import dagger.Provides; + /** * Class factory to provide customizable SystemUI components. */ +@Module public class SystemUIFactory { private static final String TAG = "SystemUIFactory"; static SystemUIFactory mFactory; + private SystemUIRootComponent mRootComponent; - public static SystemUIFactory getInstance() { - return mFactory; + public static <T extends SystemUIFactory> T getInstance() { + return (T) mFactory; } public static void createFromConfig(Context context) { @@ -88,6 +90,7 @@ public class SystemUIFactory { Class<?> cls = null; cls = context.getClassLoader().loadClass(clsName); mFactory = (SystemUIFactory) cls.newInstance(); + mFactory.init(context); } catch (Throwable t) { Log.w(TAG, "Error creating SystemUIFactory component: " + clsName, t); throw new RuntimeException(t); @@ -96,6 +99,18 @@ public class SystemUIFactory { public SystemUIFactory() {} + protected void init(Context context) { + mRootComponent = DaggerSystemUIFactory_SystemUIRootComponent.builder() + .systemUIFactory(this) + .dependencyProvider(new com.android.systemui.DependencyProvider()) + .contextHolder(new ContextHolder(context)) + .build(); + } + + public SystemUIRootComponent getRootComponent() { + return mRootComponent; + } + public StatusBarKeyguardViewManager createStatusBarKeyguardViewManager(Context context, ViewMediatorCallback viewMediatorCallback, LockPatternUtils lockPatternUtils) { return new StatusBarKeyguardViewManager(context, viewMediatorCallback, lockPatternUtils); @@ -137,33 +152,76 @@ public class SystemUIFactory { return new VolumeDialogComponent(systemUi, context); } - public void injectDependencies(ArrayMap<Object, DependencyProvider> providers, + @Singleton + @Provides + public NotificationData.KeyguardEnvironment provideKeyguardEnvironment(Context context) { + return new KeyguardEnvironmentImpl(); + } + + @Singleton + @Provides + public NotificationLockscreenUserManager provideNotificationLockscreenUserManager( Context context) { - providers.put(StatusBarStateController.class, StatusBarStateController::new); - providers.put(NotificationLockscreenUserManager.class, - () -> new NotificationLockscreenUserManagerImpl(context)); - providers.put(VisualStabilityManager.class, VisualStabilityManager::new); - providers.put(NotificationGroupManager.class, NotificationGroupManager::new); - providers.put(NotificationGroupAlertTransferHelper.class, - NotificationGroupAlertTransferHelper::new); - providers.put(NotificationMediaManager.class, () -> new NotificationMediaManager(context)); - providers.put(NotificationGutsManager.class, () -> new NotificationGutsManager(context)); - providers.put(AmbientPulseManager.class, () -> new AmbientPulseManager(context)); - providers.put(NotificationBlockingHelperManager.class, - () -> new NotificationBlockingHelperManager(context)); - providers.put(NotificationRemoteInputManager.class, - () -> new NotificationRemoteInputManager(context)); - providers.put(SmartReplyConstants.class, - () -> new SmartReplyConstants(Dependency.get(Dependency.MAIN_HANDLER), context)); - providers.put(NotificationListener.class, () -> new NotificationListener(context)); - providers.put(NotificationLogger.class, NotificationLogger::new); - providers.put(NotificationViewHierarchyManager.class, - () -> new NotificationViewHierarchyManager(context)); - providers.put(NotificationEntryManager.class, () -> new NotificationEntryManager(context)); - providers.put(KeyguardDismissUtil.class, KeyguardDismissUtil::new); - providers.put(SmartReplyController.class, () -> new SmartReplyController()); - providers.put(RemoteInputQuickSettingsDisabler.class, - () -> new RemoteInputQuickSettingsDisabler(context)); - providers.put(BubbleController.class, () -> new BubbleController(context)); + return new NotificationLockscreenUserManagerImpl(context); + } + + @Singleton + @Provides + public AssistManager provideAssistManager(DeviceProvisionedController controller, + Context context) { + return new AssistManager(controller, context); + } + + @Singleton + @Provides + public NotificationEntryManager provideNotificationEntryManager(Context context) { + return new NotificationEntryManager(context); + } + + @Singleton + @Provides + public EnhancedEstimates provideEnhancedEstimates(Context context) { + return new EnhancedEstimatesImpl(); + } + + @Singleton + @Provides + @Named(LEAK_REPORT_EMAIL_NAME) + @Nullable + public String provideLeakReportEmail() { + return null; + } + + @Singleton + @Provides + public NotificationListener provideNotificationListener(Context context) { + return new NotificationListener(context); + } + + @Module + protected static class ContextHolder { + private Context mContext; + + public ContextHolder(Context context) { + mContext = context; + } + + @Provides + public Context provideContext() { + return mContext; + } + } + + @Singleton + @Component(modules = {SystemUIFactory.class, DependencyProvider.class, ContextHolder.class}) + public interface SystemUIRootComponent { + @Singleton + Dependency.DependencyInjector createDependency(); + + /** + * FragmentCreator generates all Fragments that need injection. + */ + @Singleton + FragmentService.FragmentCreator createFragmentCreator(); } } diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java index 906a210b6b31..52d1260b4221 100644 --- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java @@ -39,25 +39,39 @@ import java.util.Set; * NotificationPresenter to be displayed to the user. */ public class AppOpsControllerImpl implements AppOpsController, - AppOpsManager.OnOpActiveChangedListener { + AppOpsManager.OnOpActiveChangedListener, + AppOpsManager.OnOpNotedListener { - private static final long LOCATION_TIME_DELAY_MS = 5000; + private static final long NOTED_OP_TIME_DELAY_MS = 5000; private static final String TAG = "AppOpsControllerImpl"; private static final boolean DEBUG = false; private final Context mContext; - protected final AppOpsManager mAppOps; - private final H mBGHandler; + private final AppOpsManager mAppOps; + private H mBGHandler; private final List<AppOpsController.Callback> mCallbacks = new ArrayList<>(); private final ArrayMap<Integer, Set<Callback>> mCallbacksByCode = new ArrayMap<>(); + @GuardedBy("mActiveItems") private final List<AppOpItem> mActiveItems = new ArrayList<>(); + @GuardedBy("mNotedItems") + private final List<AppOpItem> mNotedItems = new ArrayList<>(); + + protected static final int[] OPS; + protected static final String[] OPS_STRING = new String[] { + AppOpsManager.OPSTR_CAMERA, + AppOpsManager.OPSTR_SYSTEM_ALERT_WINDOW, + AppOpsManager.OPSTR_RECORD_AUDIO, + AppOpsManager.OPSTR_COARSE_LOCATION, + AppOpsManager.OPSTR_FINE_LOCATION}; - protected static final int[] OPS = new int[] {AppOpsManager.OP_CAMERA, - AppOpsManager.OP_SYSTEM_ALERT_WINDOW, - AppOpsManager.OP_RECORD_AUDIO, - AppOpsManager.OP_COARSE_LOCATION, - AppOpsManager.OP_FINE_LOCATION}; + static { + int numOps = OPS_STRING.length; + OPS = new int[numOps]; + for (int i = 0; i < numOps; i++) { + OPS[i] = AppOpsManager.strOpToOp(OPS_STRING[i]); + } + } public AppOpsControllerImpl(Context context, Looper bgLooper) { mContext = context; @@ -70,11 +84,18 @@ public class AppOpsControllerImpl implements AppOpsController, } @VisibleForTesting + protected void setBGHandler(H handler) { + mBGHandler = handler; + } + + @VisibleForTesting protected void setListening(boolean listening) { if (listening) { mAppOps.startWatchingActive(OPS, this); + mAppOps.startWatchingNoted(OPS_STRING, this); } else { mAppOps.stopWatchingActive(this); + mAppOps.stopWatchingNoted(this); } } @@ -124,10 +145,11 @@ public class AppOpsControllerImpl implements AppOpsController, if (mCallbacks.isEmpty()) setListening(false); } - private AppOpItem getAppOpItem(int code, int uid, String packageName) { - final int itemsQ = mActiveItems.size(); + private AppOpItem getAppOpItem(List<AppOpItem> appOpList, int code, int uid, + String packageName) { + final int itemsQ = appOpList.size(); for (int i = 0; i < itemsQ; i++) { - AppOpItem item = mActiveItems.get(i); + AppOpItem item = appOpList.get(i); if (item.getCode() == code && item.getUid() == uid && item.getPackageName().equals(packageName)) { return item; @@ -138,39 +160,59 @@ public class AppOpsControllerImpl implements AppOpsController, private boolean updateActives(int code, int uid, String packageName, boolean active) { synchronized (mActiveItems) { - AppOpItem item = getAppOpItem(code, uid, packageName); + AppOpItem item = getAppOpItem(mActiveItems, code, uid, packageName); if (item == null && active) { item = new AppOpItem(code, uid, packageName, System.currentTimeMillis()); mActiveItems.add(item); - if (code == AppOpsManager.OP_COARSE_LOCATION - || code == AppOpsManager.OP_FINE_LOCATION) { - mBGHandler.scheduleRemoval(item, LOCATION_TIME_DELAY_MS); - } if (DEBUG) Log.w(TAG, "Added item: " + item.toString()); return true; } else if (item != null && !active) { mActiveItems.remove(item); if (DEBUG) Log.w(TAG, "Removed item: " + item.toString()); return true; - } else if (item != null && active - && (code == AppOpsManager.OP_COARSE_LOCATION - || code == AppOpsManager.OP_FINE_LOCATION)) { - mBGHandler.scheduleRemoval(item, LOCATION_TIME_DELAY_MS); - return true; } return false; } } + private void removeNoted(int code, int uid, String packageName) { + AppOpItem item; + synchronized (mNotedItems) { + item = getAppOpItem(mNotedItems, code, uid, packageName); + if (item == null) return; + mNotedItems.remove(item); + if (DEBUG) Log.w(TAG, "Removed item: " + item.toString()); + } + notifySuscribers(code, uid, packageName, false); + } + + private void addNoted(int code, int uid, String packageName) { + AppOpItem item; + synchronized (mNotedItems) { + item = getAppOpItem(mNotedItems, code, uid, packageName); + if (item == null) { + item = new AppOpItem(code, uid, packageName, System.currentTimeMillis()); + mNotedItems.add(item); + if (DEBUG) Log.w(TAG, "Added item: " + item.toString()); + } + } + mBGHandler.scheduleRemoval(item, NOTED_OP_TIME_DELAY_MS); + } + /** * Returns a copy of the list containing all the active AppOps that the controller tracks. * * @return List of active AppOps information */ public List<AppOpItem> getActiveAppOps() { + ArrayList<AppOpItem> active; synchronized (mActiveItems) { - return new ArrayList<>(mActiveItems); + active = new ArrayList<>(mActiveItems); + } + synchronized (mNotedItems) { + active.addAll(mNotedItems); } + return active; } /** @@ -192,19 +234,45 @@ public class AppOpsControllerImpl implements AppOpsController, } } } + synchronized (mNotedItems) { + final int numNotedItems = mNotedItems.size(); + for (int i = 0; i < numNotedItems; i++) { + AppOpItem item = mNotedItems.get(i); + if (UserHandle.getUserId(item.getUid()) == userId) { + list.add(item); + } + } + } return list; } @Override public void onOpActiveChanged(int code, int uid, String packageName, boolean active) { if (updateActives(code, uid, packageName, active)) { + notifySuscribers(code, uid, packageName, active); + } + } + + @Override + public void onOpNoted(String code, int uid, String packageName, int result) { + if (DEBUG) { + Log.w(TAG, "Op: " + code + " with result " + AppOpsManager.MODE_NAMES[result]); + } + if (result != AppOpsManager.MODE_ALLOWED) return; + int op_code = AppOpsManager.strOpToOp(code); + addNoted(op_code, uid, packageName); + notifySuscribers(op_code, uid, packageName, true); + } + + private void notifySuscribers(int code, int uid, String packageName, boolean active) { + if (mCallbacksByCode.containsKey(code)) { for (Callback cb: mCallbacksByCode.get(code)) { cb.onActiveStateChanged(code, uid, packageName, active); } } } - private final class H extends Handler { + protected final class H extends Handler { H(Looper looper) { super(looper); } @@ -214,8 +282,7 @@ public class AppOpsControllerImpl implements AppOpsController, postDelayed(new Runnable() { @Override public void run() { - onOpActiveChanged(item.getCode(), item.getUid(), - item.getPackageName(), false); + removeNoted(item.getCode(), item.getUid(), item.getPackageName()); } }, item, timeToRemoval); } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java index 1e91ef3f10c7..c8595ebb5fca 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java @@ -42,12 +42,16 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.Map; +import javax.inject.Inject; +import javax.inject.Singleton; + /** * Bubbles are a special type of content that can "float" on top of other apps or System UI. * Bubbles can be expanded to show more content. * * The controller manages addition, removal, and visible state of bubbles on screen. */ +@Singleton public class BubbleController { private static final int MAX_BUBBLES = 5; // TODO: actually enforce this @@ -117,6 +121,7 @@ public class BubbleController { void onBubbleExpandChanged(boolean isExpanding, float amount); } + @Inject public BubbleController(Context context) { mContext = context; WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java index f8c3749dbf2f..974cd8804841 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java @@ -35,7 +35,7 @@ public class DozeLog { private static final int SIZE = Build.IS_DEBUGGABLE ? 400 : 50; static final SimpleDateFormat FORMAT = new SimpleDateFormat("MM-dd HH:mm:ss.SSS"); - private static final int REASONS = 8; + private static final int REASONS = 7; public static final int PULSE_REASON_NONE = -1; public static final int PULSE_REASON_INTENT = 0; diff --git a/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java index 60e39b21680b..30f6e1affe0a 100644 --- a/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java +++ b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java @@ -41,6 +41,8 @@ import com.android.systemui.util.leak.LeakDetector; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; @@ -186,6 +188,13 @@ public class FragmentHostManager { mFragments.dispatchDestroy(); } + /** + * Creates a fragment that requires injection. + */ + public <T> T create(Class<T> fragmentCls) { + return (T) mPlugins.instantiate(mContext, fragmentCls.getName(), null); + } + public interface FragmentListener { void onFragmentViewCreated(String tag, Fragment fragment); @@ -294,13 +303,36 @@ public class FragmentHostManager { Fragment instantiate(Context context, String className, Bundle arguments) { Context extensionContext = mExtensionLookup.get(className); if (extensionContext != null) { - Fragment f = Fragment.instantiate(extensionContext, className, arguments); + Fragment f = instantiateWithInjections(extensionContext, className, arguments); if (f instanceof Plugin) { ((Plugin) f).onCreate(mContext, extensionContext); } return f; } - return Fragment.instantiate(context, className, arguments); + return instantiateWithInjections(context, className, arguments); + } + + private Fragment instantiateWithInjections(Context context, String className, + Bundle args) { + Method method = mManager.getInjectionMap().get(className); + if (method != null) { + try { + Fragment f = (Fragment) method.invoke(mManager.getFragmentCreator()); + // Setup the args, taken from Fragment#instantiate. + if (args != null) { + args.setClassLoader(f.getClass().getClassLoader()); + f.setArguments(args); + } + return f; + } catch (IllegalAccessException e) { + throw new Fragment.InstantiationException("Unable to instantiate " + className, + e); + } catch (InvocationTargetException e) { + throw new Fragment.InstantiationException("Unable to instantiate " + className, + e); + } + } + return Fragment.instantiate(context, className, args); } } diff --git a/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java b/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java index bf7d629c5d7a..8dbaf0f681cf 100644 --- a/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java +++ b/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java @@ -14,6 +14,7 @@ package com.android.systemui.fragments; +import android.app.Fragment; import android.content.res.Configuration; import android.os.Handler; import android.util.ArrayMap; @@ -21,20 +22,56 @@ import android.view.View; import com.android.systemui.ConfigurationChangedReceiver; import com.android.systemui.Dumpable; +import com.android.systemui.SystemUIFactory; +import com.android.systemui.qs.QSFragment; +import com.android.systemui.statusbar.phone.NavigationBarFragment; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import dagger.Subcomponent; /** * Holds a map of root views to FragmentHostStates and generates them as needed. * Also dispatches the configuration changes to all current FragmentHostStates. */ +@Singleton public class FragmentService implements ConfigurationChangedReceiver, Dumpable { private static final String TAG = "FragmentService"; private final ArrayMap<View, FragmentHostState> mHosts = new ArrayMap<>(); + private final ArrayMap<String, Method> mInjectionMap = new ArrayMap<>(); private final Handler mHandler = new Handler(); + private final FragmentCreator mFragmentCreator; + + @Inject + public FragmentService(SystemUIFactory.SystemUIRootComponent rootComponent) { + mFragmentCreator = rootComponent.createFragmentCreator(); + initInjectionMap(); + } + + ArrayMap<String, Method> getInjectionMap() { + return mInjectionMap; + } + + FragmentCreator getFragmentCreator() { + return mFragmentCreator; + } + + private void initInjectionMap() { + for (Method method : FragmentCreator.class.getDeclaredMethods()) { + if (Fragment.class.isAssignableFrom(method.getReturnType()) + && (method.getModifiers() & Modifier.PUBLIC) != 0) { + mInjectionMap.put(method.getReturnType().getName(), method); + } + } + } public FragmentHostManager getFragmentHostManager(View view) { View root = view.getRootView(); @@ -74,6 +111,21 @@ public class FragmentService implements ConfigurationChangedReceiver, Dumpable { } } + /** + * The subcomponent of dagger that holds all fragments that need injection. + */ + @Subcomponent + public interface FragmentCreator { + /** + * Inject a NavigationBarFragment. + */ + NavigationBarFragment createNavigationBarFragment(); + /** + * Inject a QSFragment. + */ + QSFragment createQSFragment(); + } + private class FragmentHostState { private final View mView; diff --git a/packages/SystemUI/src/com/android/systemui/plugins/PluginEnablerImpl.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginEnablerImpl.java index e2417f7b6397..63374150adaa 100644 --- a/packages/SystemUI/src/com/android/systemui/plugins/PluginEnablerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginEnablerImpl.java @@ -16,28 +16,44 @@ package com.android.systemui.plugins; import android.content.ComponentName; import android.content.Context; +import android.content.SharedPreferences; import android.content.pm.PackageManager; import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.shared.plugins.PluginEnabler; public class PluginEnablerImpl implements PluginEnabler { + private static final String CRASH_DISABLED_PLUGINS_PREF_FILE = "auto_disabled_plugins_prefs"; - final private PackageManager mPm; + private PackageManager mPm; + private final SharedPreferences mAutoDisabledPrefs; public PluginEnablerImpl(Context context) { - this(context.getPackageManager()); + this(context, context.getPackageManager()); } - @VisibleForTesting public PluginEnablerImpl(PackageManager pm) { + @VisibleForTesting public PluginEnablerImpl(Context context, PackageManager pm) { + mAutoDisabledPrefs = context.getSharedPreferences( + CRASH_DISABLED_PLUGINS_PREF_FILE, Context.MODE_PRIVATE); mPm = pm; } @Override - public void setEnabled(ComponentName component, boolean enabled) { + public void setEnabled(ComponentName component) { + setDisabled(component, ENABLED); + } + + @Override + public void setDisabled(ComponentName component, @DisableReason int reason) { + boolean enabled = reason == ENABLED; final int desiredState = enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED : PackageManager.COMPONENT_ENABLED_STATE_DISABLED; mPm.setComponentEnabledSetting(component, desiredState, PackageManager.DONT_KILL_APP); + if (enabled) { + mAutoDisabledPrefs.edit().remove(component.flattenToString()).apply(); + } else { + mAutoDisabledPrefs.edit().putInt(component.flattenToString(), reason).apply(); + } } @Override @@ -45,4 +61,12 @@ public class PluginEnablerImpl implements PluginEnabler { return mPm.getComponentEnabledSetting(component) != PackageManager.COMPONENT_ENABLED_STATE_DISABLED; } + + @Override + public @DisableReason int getDisableReason(ComponentName componentName) { + if (isEnabled(componentName)) { + return ENABLED; + } + return mAutoDisabledPrefs.getInt(componentName.flattenToString(), DISABLED_MANUALLY); + } } diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt index 268462ea526e..a87d634451cd 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt @@ -115,7 +115,7 @@ class PrivacyItemController(val context: Context, val callback: Callback) { private fun updatePrivacyList() { privacyList = currentUserIds.flatMap { appOpsController.getActiveAppOpsForUser(it) } - .mapNotNull { toPrivacyItem(it) } + .mapNotNull { toPrivacyItem(it) }.distinct() } private fun toPrivacyItem(appOpItem: AppOpItem): PrivacyItem? { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java index 2acbea45a235..fa775c0429b6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java @@ -35,7 +35,6 @@ import android.widget.FrameLayout.LayoutParams; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; -import com.android.systemui.Dependency; import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.R.id; @@ -47,6 +46,8 @@ import com.android.systemui.statusbar.notification.stack.StackStateAnimator; import com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer; import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler; +import javax.inject.Inject; + public class QSFragment extends Fragment implements QS, CommandQueue.Callbacks { private static final String TAG = "QS"; private static final boolean DEBUG = false; @@ -74,8 +75,12 @@ public class QSFragment extends Fragment implements QS, CommandQueue.Callbacks { private float mLastQSExpansion = -1; private boolean mQsDisabled; - private RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler = - Dependency.get(RemoteInputQuickSettingsDisabler.class); + private final RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler; + + @Inject + public QSFragment(RemoteInputQuickSettingsDisabler remoteInputQsDisabler) { + mRemoteInputQuickSettingsDisabler = remoteInputQsDisabler; + } @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java index 9d2be39b28fc..4dcacd4bffdc 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java @@ -87,6 +87,8 @@ public class TileQueryHelper { if (current != null) { // The setting QS_TILES is not populated immediately upon Factory Reset possibleTiles.addAll(Arrays.asList(current.split(","))); + } else { + current = ""; } String[] stockSplit = stock.split(","); for (String spec : stockSplit) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java index 89066651084d..466c8082f0b9 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java @@ -183,6 +183,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener public void updateState(Tile tile) { mTile.setIcon(tile.getIcon()); mTile.setLabel(tile.getLabel()); + mTile.setSubtitle(tile.getSubtitle()); mTile.setContentDescription(tile.getContentDescription()); mTile.setState(tile.getState()); } @@ -322,6 +323,14 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener return null; }; state.label = mTile.getLabel(); + + CharSequence subtitle = mTile.getSubtitle(); + if (subtitle != null && subtitle.length() > 0) { + state.secondaryLabel = subtitle; + } else { + state.secondaryLabel = null; + } + if (mTile.getContentDescription() != null) { state.contentDescription = mTile.getContentDescription(); } else { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AmbientPulseManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/AmbientPulseManager.java index 8821679aadf1..9bfd4ee24ff0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/AmbientPulseManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/AmbientPulseManager.java @@ -28,17 +28,22 @@ import com.android.systemui.R; import com.android.systemui.statusbar.notification.NotificationData; import com.android.systemui.statusbar.notification.row.NotificationInflater.InflationFlag; +import javax.inject.Inject; +import javax.inject.Singleton; + /** * Manager which handles high priority notifications that should "pulse" in when the device is * dozing and/or in AOD. The pulse uses the notification's ambient view and pops in briefly * before automatically dismissing the alert. */ +@Singleton public class AmbientPulseManager extends AlertingNotificationManager { protected final ArraySet<OnAmbientChangedListener> mListeners = new ArraySet<>(); @VisibleForTesting protected long mExtensionTime; + @Inject public AmbientPulseManager(@NonNull final Context context) { Resources resources = context.getResources(); mAutoDismissNotificationDecay = resources.getInteger(R.integer.ambient_notification_decay); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java index f0455481b353..7d80860fca85 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java @@ -61,10 +61,14 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; +import javax.inject.Inject; +import javax.inject.Singleton; + /** * Handles tasks and state related to media notifications. For example, there is a 'current' media * notification, which this class keeps track of. */ +@Singleton public class NotificationMediaManager implements Dumpable { private static final String TAG = "NotificationMediaManager"; public static final boolean DEBUG_MEDIA = false; @@ -157,6 +161,7 @@ public class NotificationMediaManager implements Dumpable { return mEntryManager; } + @Inject public NotificationMediaManager(Context context) { mContext = context; mMediaSessionManager diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java index a5c0a2d3b4d4..ee0d1a2d5a51 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java @@ -16,6 +16,7 @@ package com.android.systemui.statusbar; import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.NotificationRowBinder; import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; @@ -28,7 +29,8 @@ import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow */ public interface NotificationPresenter extends ExpandableNotificationRow.OnExpandClickListener, ActivatableNotificationView.OnActivatedListener, - NotificationEntryManager.Callback { + NotificationEntryManager.Callback, + NotificationRowBinder.BindRowCallback { /** * Returns true if the presenter is not visible. For example, it may not be necessary to do * animations if this returns true. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java index 9391737fe23c..d1556fb33f92 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java @@ -59,12 +59,16 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.Set; +import javax.inject.Inject; +import javax.inject.Singleton; + /** * Class for handling remote input state over a set of notifications. This class handles things * like keeping notifications temporarily that were cancelled as a response to a remote input * interaction, keeping track of notifications to remove when NotificationPresenter is collapsed, * and handling clicks on remote views. */ +@Singleton public class NotificationRemoteInputManager implements Dumpable { public static final boolean ENABLE_REMOTE_INPUT = SystemProperties.getBoolean("debug.enable_remote_input", true); @@ -229,6 +233,7 @@ public class NotificationRemoteInputManager implements Dumpable { return mShadeController; } + @Inject public NotificationRemoteInputManager(Context context) { mContext = context; mBarService = IStatusBarService.Stub.asInterface( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java index 0702f1b9f1fe..25247470bd88 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java @@ -41,6 +41,9 @@ import java.util.HashMap; import java.util.List; import java.util.Stack; +import javax.inject.Inject; +import javax.inject.Singleton; + /** * NotificationViewHierarchyManager manages updating the view hierarchy of notification views based * on their group structure. For example, if a notification becomes bundled with another, @@ -48,6 +51,7 @@ import java.util.Stack; * tell NotificationListContainer which notifications to display, and inform it of changes to those * notifications that might affect their display. */ +@Singleton public class NotificationViewHierarchyManager { private static final String TAG = "NotificationViewHierarchyManager"; @@ -123,6 +127,7 @@ public class NotificationViewHierarchyManager { return mShadeController; } + @Inject public NotificationViewHierarchyManager(Context context) { Resources res = context.getResources(); mAlwaysExpandNonGroupedNotification = diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java b/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java index e31f90d5a92e..6f1548d2fb5f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java @@ -27,10 +27,14 @@ import com.android.systemui.statusbar.notification.NotificationEntryManager; import java.util.Set; +import javax.inject.Inject; +import javax.inject.Singleton; + /** * Handles when smart replies are added to a notification * and clicked upon. */ +@Singleton public class SmartReplyController { private IStatusBarService mBarService; private Set<String> mSendingKeys = new ArraySet<>(); @@ -38,7 +42,7 @@ public class SmartReplyController { private final NotificationEntryManager mEntryManager = Dependency.get(NotificationEntryManager.class); - + @Inject public SmartReplyController() { mBarService = Dependency.get(IStatusBarService.class); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateController.java index 3f84416ad575..087b65592b7a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateController.java @@ -35,9 +35,13 @@ import java.lang.annotation.Retention; import java.util.ArrayList; import java.util.Comparator; +import javax.inject.Inject; +import javax.inject.Singleton; + /** * Tracks and reports on {@link StatusBarState}. */ +@Singleton public class StatusBarStateController implements CallbackController<StateListener> { private static final String TAG = "SbStateController"; @@ -101,6 +105,10 @@ public class StatusBarStateController implements CallbackController<StateListene public static final int RANK_STACK_SCROLLER = 2; public static final int RANK_SHELF = 3; + @Inject + public StatusBarStateController() { + } + public int getState() { return mState; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java new file mode 100644 index 000000000000..49f1a8d6f248 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java @@ -0,0 +1,104 @@ +/* + * 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.notification; + +import android.app.Notification; +import android.os.SystemClock; +import android.service.notification.StatusBarNotification; +import android.util.Log; +import android.view.View; + +import com.android.systemui.DejankUtils; +import com.android.systemui.bubbles.BubbleController; +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.phone.ShadeController; + +/** + * Click handler for generic clicks on notifications. Clicks on specific areas (expansion caret, + * app ops icon, etc) are handled elsewhere. + */ +public final class NotificationClicker implements View.OnClickListener { + private static final String TAG = "NotificationClicker"; + + private final ShadeController mShadeController; + private final BubbleController mBubbleController; + private final NotificationActivityStarter mNotificationActivityStarter; + + public NotificationClicker(ShadeController shadeController, + BubbleController bubbleController, + NotificationActivityStarter notificationActivityStarter) { + mShadeController = shadeController; + mBubbleController = bubbleController; + mNotificationActivityStarter = notificationActivityStarter; + } + + @Override + public void onClick(final View v) { + if (!(v instanceof ExpandableNotificationRow)) { + Log.e(TAG, "NotificationClicker called on a view that is not a notification row."); + return; + } + + mShadeController.wakeUpIfDozing(SystemClock.uptimeMillis(), v); + + final ExpandableNotificationRow row = (ExpandableNotificationRow) v; + final StatusBarNotification sbn = row.getStatusBarNotification(); + if (sbn == null) { + Log.e(TAG, "NotificationClicker called on an unclickable notification,"); + return; + } + + // Check if the notification is displaying the menu, if so slide notification back + if (isMenuVisible(row)) { + row.animateTranslateNotification(0); + return; + } else if (row.isChildInGroup() && isMenuVisible(row.getNotificationParent())) { + row.getNotificationParent().animateTranslateNotification(0); + return; + } else if (row.isSummaryWithChildren() && row.areChildrenExpanded()) { + // We never want to open the app directly if the user clicks in between + // the notifications. + return; + } + + // Mark notification for one frame. + row.setJustClicked(true); + DejankUtils.postAfterTraversal(() -> row.setJustClicked(false)); + + // If it was a bubble we should close it + if (row.getEntry().isBubble()) { + mBubbleController.collapseStack(); + } + + mNotificationActivityStarter.onNotificationClicked(sbn, row); + } + + private boolean isMenuVisible(ExpandableNotificationRow row) { + return row.getProvider() != null && row.getProvider().isMenuVisible(); + } + + /** + * Attaches the click listener to the row if appropriate. + */ + public void register(ExpandableNotificationRow row, StatusBarNotification sbn) { + Notification notification = sbn.getNotification(); + if (notification.contentIntent != null || notification.fullScreenIntent != null) { + row.setOnClickListener(this); + } else { + row.setOnClickListener(null); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java index ae9f323c2ebf..ef7e0fe5791f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java @@ -105,6 +105,7 @@ public class NotificationData { public NotificationChannel channel; public long lastAudiblyAlertedMs; public boolean noisy; + public boolean ambient; public int importance; public StatusBarIconView icon; public StatusBarIconView expandedIcon; @@ -174,6 +175,7 @@ public class NotificationData { channel = ranking.getChannel(); lastAudiblyAlertedMs = ranking.getLastAudiblyAlertedMillis(); importance = ranking.getImportance(); + ambient = ranking.isAmbient(); snoozeCriteria = ranking.getSnoozeCriteria(); userSentiment = ranking.getUserSentiment(); systemGeneratedSmartActions = ranking.getSmartActions() == null diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java index aab3fc4c7a0e..535ea624dfc2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java @@ -16,7 +16,6 @@ package com.android.systemui.statusbar.notification; import static com.android.systemui.bubbles.BubbleController.DEBUG_DEMOTE_TO_NOTIF; -import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT; import static com.android.systemui.statusbar.NotificationRemoteInputManager.FORCE_REMOTE_INPUT_HISTORY; import static com.android.systemui.statusbar.StatusBarState.SHADE; import static com.android.systemui.statusbar.notification.row.NotificationInflater.FLAG_CONTENT_VIEW_AMBIENT; @@ -27,16 +26,12 @@ import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; import android.database.ContentObserver; -import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.PowerManager; import android.os.RemoteException; import android.os.ServiceManager; -import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings; import android.service.dreams.DreamService; @@ -49,27 +44,21 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.EventLog; import android.util.Log; -import android.view.View; -import android.view.ViewGroup; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.statusbar.NotificationVisibility; -import com.android.internal.util.NotificationMessagingUtil; -import com.android.systemui.DejankUtils; import com.android.systemui.Dependency; import com.android.systemui.Dumpable; import com.android.systemui.EventLogTags; import com.android.systemui.ForegroundServiceController; -import com.android.systemui.R; import com.android.systemui.UiOffloadThread; import com.android.systemui.bubbles.BubbleController; import com.android.systemui.statusbar.AlertingNotificationManager; import com.android.systemui.statusbar.AmbientPulseManager; import com.android.systemui.statusbar.NotificationLifetimeExtender; import com.android.systemui.statusbar.NotificationListener; -import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationPresenter; import com.android.systemui.statusbar.NotificationRemoteInputManager; @@ -81,11 +70,9 @@ import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.row.NotificationInflater; import com.android.systemui.statusbar.notification.row.NotificationInflater.InflationFlag; -import com.android.systemui.statusbar.notification.row.RowInflaterTask; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.ShadeController; -import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.util.leak.LeakDetector; @@ -102,9 +89,12 @@ import java.util.concurrent.TimeUnit; * It also handles tasks such as their inflation and their interaction with other * Notification.*Manager objects. */ -public class NotificationEntryManager implements Dumpable, NotificationInflater.InflationCallback, - ExpandableNotificationRow.ExpansionLogger, NotificationUpdateHandler, - VisualStabilityManager.Callback, BubbleController.BubbleDismissListener { +public class NotificationEntryManager implements + Dumpable, + NotificationInflater.InflationCallback, + NotificationUpdateHandler, + VisualStabilityManager.Callback, + BubbleController.BubbleDismissListener { private static final String TAG = "NotificationEntryMgr"; protected static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static final boolean ENABLE_HEADS_UP = true; @@ -112,10 +102,8 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. public static final long RECENTLY_ALERTED_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(30); - private final NotificationMessagingUtil mMessagingUtil; protected final Context mContext; protected final HashMap<String, NotificationData.Entry> mPendingNotifications = new HashMap<>(); - private final NotificationClicker mNotificationClicker = new NotificationClicker(); private final NotificationGroupManager mGroupManager = Dependency.get(NotificationGroupManager.class); @@ -126,7 +114,6 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. Dependency.get(DeviceProvisionedController.class); private final VisualStabilityManager mVisualStabilityManager = Dependency.get(VisualStabilityManager.class); - private final UiOffloadThread mUiOffloadThread = Dependency.get(UiOffloadThread.class); private final ForegroundServiceController mForegroundServiceController = Dependency.get(ForegroundServiceController.class); private final AmbientPulseManager mAmbientPulseManager = @@ -138,6 +125,7 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. private NotificationMediaManager mMediaManager; private NotificationListener mNotificationListener; private ShadeController mShadeController; + private NotificationRowBinder mNotificationRowBinder; private final Handler mDeferredNotificationViewUpdateHandler; private Runnable mUpdateNotificationViewsCallback; @@ -146,7 +134,6 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. protected IStatusBarService mBarService; private NotificationPresenter mPresenter; private Callback mCallback; - private NotificationActivityStarter mNotificationActivityStarter; protected PowerManager mPowerManager; private NotificationListenerService.RankingMap mLatestRankingMap; protected HeadsUpManager mHeadsUpManager; @@ -158,67 +145,9 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. @VisibleForTesting final ArrayList<NotificationLifetimeExtender> mNotificationLifetimeExtenders = new ArrayList<>(); - private ExpandableNotificationRow.OnAppOpsClickListener mOnAppOpsClickListener; private NotificationViewHierarchyManager.StatusBarStateListener mStatusBarStateListener; @Nullable private AlertTransferListener mAlertTransferListener; - private final class NotificationClicker implements View.OnClickListener { - - @Override - public void onClick(final View v) { - if (!(v instanceof ExpandableNotificationRow)) { - Log.e(TAG, "NotificationClicker called on a view that is not a notification row."); - return; - } - - getShadeController().wakeUpIfDozing(SystemClock.uptimeMillis(), v); - - final ExpandableNotificationRow row = (ExpandableNotificationRow) v; - final StatusBarNotification sbn = row.getStatusBarNotification(); - if (sbn == null) { - Log.e(TAG, "NotificationClicker called on an unclickable notification,"); - return; - } - - // Check if the notification is displaying the menu, if so slide notification back - if (isMenuVisible(row)) { - row.animateTranslateNotification(0); - return; - } else if (row.isChildInGroup() && isMenuVisible(row.getNotificationParent())) { - row.getNotificationParent().animateTranslateNotification(0); - return; - } else if (row.isSummaryWithChildren() && row.areChildrenExpanded()) { - // We never want to open the app directly if the user clicks in between - // the notifications. - return; - } - - // Mark notification for one frame. - row.setJustClicked(true); - DejankUtils.postAfterTraversal(() -> row.setJustClicked(false)); - - // If it was a bubble we should close it - if (row.getEntry().isBubble()) { - mBubbleController.collapseStack(); - } - - mNotificationActivityStarter.onNotificationClicked(sbn, row); - } - - private boolean isMenuVisible(ExpandableNotificationRow row) { - return row.getProvider() != null && row.getProvider().isMenuVisible(); - } - - public void register(ExpandableNotificationRow row, StatusBarNotification sbn) { - Notification notification = sbn.getNotification(); - if (notification.contentIntent != null || notification.fullScreenIntent != null) { - row.setOnClickListener(this); - } else { - row.setOnClickListener(null); - } - } - } - private final DeviceProvisionedController.DeviceProvisionedListener mDeviceProvisionedListener = new DeviceProvisionedController.DeviceProvisionedListener() { @@ -259,7 +188,6 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. ServiceManager.getService(Context.STATUS_BAR_SERVICE)); mDreamManager = IDreamManager.Stub.asInterface( ServiceManager.checkService(DreamService.DREAM_SERVICE)); - mMessagingUtil = new NotificationMessagingUtil(context); mBubbleController.setDismissListener(this /* bubbleEventListener */); mNotificationData = new NotificationData(); mDeferredNotificationViewUpdateHandler = new Handler(); @@ -300,6 +228,13 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. return mShadeController; } + private NotificationRowBinder getRowBinder() { + if (mNotificationRowBinder == null) { + mNotificationRowBinder = Dependency.get(NotificationRowBinder.class); + } + return mNotificationRowBinder; + } + public void setUpWithPresenter(NotificationPresenter presenter, NotificationListContainer listContainer, Callback callback, HeadsUpManager headsUpManager) { @@ -352,12 +287,18 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. mDeviceProvisionedController.addCallback(mDeviceProvisionedListener); mHeadsUpObserver.onChange(true); // set up - mOnAppOpsClickListener = mGutsManager::openGuts; - } - public void setNotificationActivityStarter( - NotificationActivityStarter notificationActivityStarter) { - mNotificationActivityStarter = notificationActivityStarter; + getRowBinder().setInterruptionStateProvider(new InterruptionStateProvider() { + @Override + public boolean shouldHeadsUp(NotificationData.Entry entry) { + return NotificationEntryManager.this.shouldHeadsUp(entry); + } + + @Override + public boolean shouldPulse(NotificationData.Entry entry) { + return NotificationEntryManager.this.shouldPulse(entry); + } + }); } public NotificationData getNotificationData() { @@ -373,18 +314,7 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. } public ExpandableNotificationRow.LongPressListener getNotificationLongClicker() { - return mGutsManager::openGuts; - } - - @Override - public void logNotificationExpansion(String key, boolean userAction, boolean expanded) { - mUiOffloadThread.submit(() -> { - try { - mBarService.onNotificationExpansionChanged(key, userAction, expanded); - } catch (RemoteException e) { - // Ignore. - } - }); + return getRowBinder().getNotificationLongClicker(); } @Override @@ -400,64 +330,6 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. return mNotificationData.shouldSuppressFullScreenIntent(entry); } - private void inflateViews(NotificationData.Entry entry, ViewGroup parent) { - PackageManager pmUser = StatusBar.getPackageManagerForUser(mContext, - entry.notification.getUser().getIdentifier()); - - final StatusBarNotification sbn = entry.notification; - if (entry.rowExists()) { - entry.reset(); - updateNotification(entry, pmUser, sbn, entry.getRow()); - } else { - new RowInflaterTask().inflate(mContext, parent, entry, - row -> { - bindRow(entry, pmUser, sbn, row); - updateNotification(entry, pmUser, sbn, row); - }); - } - } - - private void bindRow(NotificationData.Entry entry, PackageManager pmUser, - StatusBarNotification sbn, ExpandableNotificationRow row) { - row.setExpansionLogger(this, entry.notification.getKey()); - row.setGroupManager(mGroupManager); - row.setHeadsUpManager(mHeadsUpManager); - row.setOnExpandClickListener(mPresenter); - row.setInflationCallback(this); - row.setLongPressListener(getNotificationLongClicker()); - mListContainer.bindRow(row); - getRemoteInputManager().bindRow(row); - - // Get the app name. - // Note that Notification.Builder#bindHeaderAppName has similar logic - // but since this field is used in the guts, it must be accurate. - // Therefore we will only show the application label, or, failing that, the - // package name. No substitutions. - final String pkg = sbn.getPackageName(); - String appname = pkg; - try { - final ApplicationInfo info = pmUser.getApplicationInfo(pkg, - PackageManager.MATCH_UNINSTALLED_PACKAGES - | PackageManager.MATCH_DISABLED_COMPONENTS); - if (info != null) { - appname = String.valueOf(pmUser.getApplicationLabel(info)); - } - } catch (PackageManager.NameNotFoundException e) { - // Do nothing - } - row.setAppName(appname); - row.setOnDismissRunnable(() -> - performRemoveNotification(row.getStatusBarNotification())); - row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); - if (ENABLE_REMOTE_INPUT) { - row.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS); - } - - row.setAppOpsOnClickListener(mOnAppOpsClickListener); - - mCallback.onBindRow(entry, pmUser, sbn, row); - } - public void performRemoveNotification(StatusBarNotification n) { final int rank = mNotificationData.getRank(n.getKey()); final int count = mNotificationData.getActiveNotifications().size(); @@ -748,56 +620,11 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. } } - //TODO: This method associates a row with an entry, but eventually needs to not do that - protected void updateNotification(NotificationData.Entry entry, PackageManager pmUser, - StatusBarNotification sbn, ExpandableNotificationRow row) { - boolean isLowPriority = mNotificationData.isAmbient(sbn.getKey()); - boolean isUpdate = mNotificationData.get(entry.key) != null; - boolean wasLowPriority = row.isLowPriority(); - row.setIsLowPriority(isLowPriority); - row.setLowPriorityStateUpdated(isUpdate && (wasLowPriority != isLowPriority)); - // bind the click event to the content area - mNotificationClicker.register(row, sbn); - - // Extract target SDK version. - try { - ApplicationInfo info = pmUser.getApplicationInfo(sbn.getPackageName(), 0); - entry.targetSdk = info.targetSdkVersion; - } catch (PackageManager.NameNotFoundException ex) { - Log.e(TAG, "Failed looking up ApplicationInfo for " + sbn.getPackageName(), ex); - } - row.setLegacy(entry.targetSdk >= Build.VERSION_CODES.GINGERBREAD - && entry.targetSdk < Build.VERSION_CODES.LOLLIPOP); - entry.setIconTag(R.id.icon_is_pre_L, entry.targetSdk < Build.VERSION_CODES.LOLLIPOP); - entry.autoRedacted = entry.notification.getNotification().publicVersion == null; - - entry.setRow(row); - row.setOnActivatedListener(mPresenter); - - boolean useIncreasedCollapsedHeight = mMessagingUtil.isImportantMessaging(sbn, - mNotificationData.getImportance(sbn.getKey())); - boolean useIncreasedHeadsUp = useIncreasedCollapsedHeight - && !mPresenter.isPresenterFullyCollapsed(); - row.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight); - row.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp); - row.setEntry(entry); - - if (shouldHeadsUp(entry)) { - row.updateInflationFlag(FLAG_CONTENT_VIEW_HEADS_UP, true /* shouldInflate */); - } - if (shouldPulse(entry)) { - row.updateInflationFlag(FLAG_CONTENT_VIEW_AMBIENT, true /* shouldInflate */); - } - row.setNeedsRedaction( - Dependency.get(NotificationLockscreenUserManager.class).needsRedaction(entry)); - row.inflateViews(); - } - - private NotificationData.Entry createNotificationViews( + private NotificationData.Entry createNotificationEntry( StatusBarNotification sbn, NotificationListenerService.Ranking ranking) throws InflationException { if (DEBUG) { - Log.d(TAG, "createNotificationViews(notification=" + sbn + " " + ranking); + Log.d(TAG, "createNotificationEntry(notification=" + sbn + " " + ranking); } NotificationData.Entry entry = new NotificationData.Entry(sbn, ranking); @@ -808,7 +635,8 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. Dependency.get(LeakDetector.class).trackInstance(entry); entry.createIcons(mContext, sbn); // Construct the expanded view. - inflateViews(entry, mListContainer.getViewParentForNotification(entry)); + getRowBinder().inflateViews(entry, () -> performRemoveNotification(sbn), + mNotificationData.get(entry.key) != null); return entry; } @@ -822,7 +650,7 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. mNotificationData.updateRanking(rankingMap); NotificationListenerService.Ranking ranking = new NotificationListenerService.Ranking(); rankingMap.getRanking(key, ranking); - NotificationData.Entry shadeEntry = createNotificationViews(notification, ranking); + NotificationData.Entry shadeEntry = createNotificationEntry(notification, ranking); boolean isHeadsUped = shouldHeadsUp(shadeEntry); if (!isHeadsUped && notification.getNotification().fullScreenIntent != null) { if (shouldSuppressFullScreenIntent(shadeEntry)) { @@ -931,7 +759,8 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. mGroupManager.onEntryUpdated(entry, oldNotification); entry.updateIcons(mContext, notification); - inflateViews(entry, mListContainer.getViewParentForNotification(entry)); + getRowBinder().inflateViews(entry, () -> performRemoveNotification(notification), + mNotificationData.get(entry.key) != null); mForegroundServiceController.updateNotification(notification, mNotificationData.getImportance(key)); @@ -999,26 +828,12 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. // By comparing the old and new UI adjustments, reinflate the view accordingly. for (NotificationData.Entry entry : entries) { - NotificationUiAdjustment newAdjustment = - NotificationUiAdjustment.extractFromNotificationEntry(entry); - - if (NotificationUiAdjustment.needReinflate( - oldAdjustments.get(entry.key), newAdjustment)) { - if (entry.rowExists()) { - entry.reset(); - PackageManager pmUser = StatusBar.getPackageManagerForUser(mContext, - entry.notification.getUser().getIdentifier()); - updateNotification(entry, pmUser, entry.notification, entry.getRow()); - } else { - // Once the RowInflaterTask is done, it will pick up the updated entry, so - // no-op here. - } - } else if (oldImportances.containsKey(entry.key) - && entry.importance != oldImportances.get(entry.key)) { - if (entry.rowExists()) { - entry.getRow().onNotificationRankingUpdated(); - } - } + mNotificationRowBinder.onNotificationRankingUpdated( + entry, + oldImportances.get(entry.key), + oldAdjustments.get(entry.key), + NotificationUiAdjustment.extractFromNotificationEntry(entry), + mNotificationData.get(entry.key) != null); } updateNotifications(); @@ -1263,6 +1078,22 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. } /** + * Interface for retrieving heads-up and pulsing state for an entry. + */ + public interface InterruptionStateProvider { + /** + * Whether the provided entry should be marked as heads-up when inflated. + */ + boolean shouldHeadsUp(NotificationData.Entry entry); + + /** + * Whether the provided entry should be marked as pulsing (displayed in ambient) when + * inflated. + */ + boolean shouldPulse(NotificationData.Entry entry); + } + + /** * Callback for NotificationEntryManager. */ public interface Callback { @@ -1290,17 +1121,6 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. void onNotificationRemoved(String key, StatusBarNotification old); /** - * Called when a new notification and row is created. - * - * @param entry entry for the notification - * @param pmUser package manager for user - * @param sbn notification - * @param row row for the notification - */ - void onBindRow(NotificationData.Entry entry, PackageManager pmUser, - StatusBarNotification sbn, ExpandableNotificationRow row); - - /** * Removes a notification immediately. * * @param statusBarNotification notification that is being removed diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationRowBinder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationRowBinder.java new file mode 100644 index 000000000000..824bd813d563 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationRowBinder.java @@ -0,0 +1,294 @@ +/* + * 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.notification; + +import static com.android.internal.util.Preconditions.checkNotNull; +import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT; +import static com.android.systemui.statusbar.notification.row.NotificationInflater.FLAG_CONTENT_VIEW_AMBIENT; +import static com.android.systemui.statusbar.notification.row.NotificationInflater.FLAG_CONTENT_VIEW_HEADS_UP; + +import android.annotation.Nullable; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.os.Build; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.service.notification.StatusBarNotification; +import android.util.Log; +import android.view.ViewGroup; + +import com.android.internal.statusbar.IStatusBarService; +import com.android.internal.util.NotificationMessagingUtil; +import com.android.systemui.Dependency; +import com.android.systemui.R; +import com.android.systemui.UiOffloadThread; +import com.android.systemui.statusbar.NotificationLockscreenUserManager; +import com.android.systemui.statusbar.NotificationPresenter; +import com.android.systemui.statusbar.NotificationRemoteInputManager; +import com.android.systemui.statusbar.NotificationUiAdjustment; +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.row.NotificationGutsManager; +import com.android.systemui.statusbar.notification.row.NotificationInflater; +import com.android.systemui.statusbar.notification.row.RowInflaterTask; +import com.android.systemui.statusbar.notification.stack.NotificationListContainer; +import com.android.systemui.statusbar.phone.NotificationGroupManager; +import com.android.systemui.statusbar.phone.StatusBar; +import com.android.systemui.statusbar.policy.HeadsUpManager; + +import javax.inject.Inject; +import javax.inject.Singleton; + +/** Handles inflating and updating views for notifications. */ +@Singleton +public class NotificationRowBinder { + + private static final String TAG = "NotificationViewManager"; + + private final NotificationGroupManager mGroupManager = + Dependency.get(NotificationGroupManager.class); + private final NotificationGutsManager mGutsManager = + Dependency.get(NotificationGutsManager.class); + private final UiOffloadThread mUiOffloadThread = Dependency.get(UiOffloadThread.class); + + private final Context mContext; + private final IStatusBarService mBarService; + private final NotificationMessagingUtil mMessagingUtil; + private final ExpandableNotificationRow.ExpansionLogger mExpansionLogger = + this::logNotificationExpansion; + + private NotificationRemoteInputManager mRemoteInputManager; + private NotificationPresenter mPresenter; + private NotificationListContainer mListContainer; + private HeadsUpManager mHeadsUpManager; + private NotificationInflater.InflationCallback mInflationCallback; + private ExpandableNotificationRow.OnAppOpsClickListener mOnAppOpsClickListener; + private BindRowCallback mBindRowCallback; + private NotificationClicker mNotificationClicker; + private NotificationEntryManager.InterruptionStateProvider mInterruptionStateProvider; + + @Inject + public NotificationRowBinder(Context context) { + mContext = context; + mMessagingUtil = new NotificationMessagingUtil(context); + mBarService = IStatusBarService.Stub.asInterface( + ServiceManager.getService(Context.STATUS_BAR_SERVICE)); + } + + private NotificationRemoteInputManager getRemoteInputManager() { + if (mRemoteInputManager == null) { + mRemoteInputManager = Dependency.get(NotificationRemoteInputManager.class); + } + return mRemoteInputManager; + } + + /** + * Sets up late-bound dependencies for this component. + */ + public void setUpWithPresenter(NotificationPresenter presenter, + NotificationListContainer listContainer, + HeadsUpManager headsUpManager, + NotificationInflater.InflationCallback inflationCallback, + BindRowCallback bindRowCallback) { + mPresenter = presenter; + mListContainer = listContainer; + mHeadsUpManager = headsUpManager; + mInflationCallback = inflationCallback; + mBindRowCallback = bindRowCallback; + mOnAppOpsClickListener = mGutsManager::openGuts; + } + + public void setNotificationClicker(NotificationClicker clicker) { + mNotificationClicker = clicker; + } + + public void setInterruptionStateProvider( + NotificationEntryManager.InterruptionStateProvider interruptionStateProvider) { + mInterruptionStateProvider = interruptionStateProvider; + } + + /** + * Inflates the views for the given entry (possibly asynchronously). + */ + public void inflateViews(NotificationData.Entry entry, Runnable onDismissRunnable, + boolean isUpdate) { + ViewGroup parent = mListContainer.getViewParentForNotification(entry); + PackageManager pmUser = StatusBar.getPackageManagerForUser(mContext, + entry.notification.getUser().getIdentifier()); + + final StatusBarNotification sbn = entry.notification; + if (entry.rowExists()) { + entry.reset(); + updateNotification(entry, pmUser, sbn, entry.getRow(), isUpdate); + } else { + new RowInflaterTask().inflate(mContext, parent, entry, + row -> { + bindRow(entry, pmUser, sbn, row, onDismissRunnable); + updateNotification(entry, pmUser, sbn, row, isUpdate); + }); + } + } + + private void bindRow(NotificationData.Entry entry, PackageManager pmUser, + StatusBarNotification sbn, ExpandableNotificationRow row, + Runnable onDismissRunnable) { + row.setExpansionLogger(mExpansionLogger, entry.notification.getKey()); + row.setGroupManager(mGroupManager); + row.setHeadsUpManager(mHeadsUpManager); + row.setOnExpandClickListener(mPresenter); + row.setInflationCallback(mInflationCallback); + row.setLongPressListener(getNotificationLongClicker()); + mListContainer.bindRow(row); + getRemoteInputManager().bindRow(row); + + // Get the app name. + // Note that Notification.Builder#bindHeaderAppName has similar logic + // but since this field is used in the guts, it must be accurate. + // Therefore we will only show the application label, or, failing that, the + // package name. No substitutions. + final String pkg = sbn.getPackageName(); + String appname = pkg; + try { + final ApplicationInfo info = pmUser.getApplicationInfo(pkg, + PackageManager.MATCH_UNINSTALLED_PACKAGES + | PackageManager.MATCH_DISABLED_COMPONENTS); + if (info != null) { + appname = String.valueOf(pmUser.getApplicationLabel(info)); + } + } catch (PackageManager.NameNotFoundException e) { + // Do nothing + } + row.setAppName(appname); + row.setOnDismissRunnable(onDismissRunnable); + row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); + if (ENABLE_REMOTE_INPUT) { + row.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS); + } + + row.setAppOpsOnClickListener(mOnAppOpsClickListener); + + mBindRowCallback.onBindRow(entry, pmUser, sbn, row); + } + + /** + * Updates the views bound to an entry when the entry's ranking changes, either in-place or by + * reinflating them. + */ + public void onNotificationRankingUpdated( + NotificationData.Entry entry, + @Nullable Integer oldImportance, + NotificationUiAdjustment oldAdjustment, + NotificationUiAdjustment newAdjustment, + boolean isUpdate) { + if (NotificationUiAdjustment.needReinflate(oldAdjustment, newAdjustment)) { + if (entry.rowExists()) { + entry.reset(); + PackageManager pmUser = StatusBar.getPackageManagerForUser( + mContext, + entry.notification.getUser().getIdentifier()); + updateNotification(entry, pmUser, entry.notification, entry.getRow(), isUpdate); + } else { + // Once the RowInflaterTask is done, it will pick up the updated entry, so + // no-op here. + } + } else { + if (oldImportance != null && entry.importance != oldImportance) { + if (entry.rowExists()) { + entry.getRow().onNotificationRankingUpdated(); + } + } + } + } + + //TODO: This method associates a row with an entry, but eventually needs to not do that + private void updateNotification( + NotificationData.Entry entry, + PackageManager pmUser, + StatusBarNotification sbn, + ExpandableNotificationRow row, + boolean isUpdate) { + boolean isLowPriority = entry.ambient; + boolean wasLowPriority = row.isLowPriority(); + row.setIsLowPriority(isLowPriority); + row.setLowPriorityStateUpdated(isUpdate && (wasLowPriority != isLowPriority)); + // bind the click event to the content area + checkNotNull(mNotificationClicker).register(row, sbn); + + // Extract target SDK version. + try { + ApplicationInfo info = pmUser.getApplicationInfo(sbn.getPackageName(), 0); + entry.targetSdk = info.targetSdkVersion; + } catch (PackageManager.NameNotFoundException ex) { + Log.e(TAG, "Failed looking up ApplicationInfo for " + sbn.getPackageName(), ex); + } + row.setLegacy(entry.targetSdk >= Build.VERSION_CODES.GINGERBREAD + && entry.targetSdk < Build.VERSION_CODES.LOLLIPOP); + + // TODO: should updates to the entry be happening somewhere else? + entry.setIconTag(R.id.icon_is_pre_L, entry.targetSdk < Build.VERSION_CODES.LOLLIPOP); + entry.autoRedacted = entry.notification.getNotification().publicVersion == null; + + entry.setRow(row); + row.setOnActivatedListener(mPresenter); + + boolean useIncreasedCollapsedHeight = + mMessagingUtil.isImportantMessaging(sbn, entry.importance); + boolean useIncreasedHeadsUp = useIncreasedCollapsedHeight + && !mPresenter.isPresenterFullyCollapsed(); + row.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight); + row.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp); + row.setEntry(entry); + + if (mInterruptionStateProvider.shouldHeadsUp(entry)) { + row.updateInflationFlag(FLAG_CONTENT_VIEW_HEADS_UP, true /* shouldInflate */); + } + if (mInterruptionStateProvider.shouldPulse(entry)) { + row.updateInflationFlag(FLAG_CONTENT_VIEW_AMBIENT, true /* shouldInflate */); + } + row.setNeedsRedaction( + Dependency.get(NotificationLockscreenUserManager.class).needsRedaction(entry)); + row.inflateViews(); + } + + ExpandableNotificationRow.LongPressListener getNotificationLongClicker() { + return mGutsManager::openGuts; + } + + private void logNotificationExpansion(String key, boolean userAction, boolean expanded) { + mUiOffloadThread.submit(() -> { + try { + mBarService.onNotificationExpansionChanged(key, userAction, expanded); + } catch (RemoteException e) { + // Ignore. + } + }); + } + + /** Callback for when a row is bound to an entry. */ + public interface BindRowCallback { + /** + * Called when a new notification and row is created. + * + * @param entry entry for the notification + * @param pmUser package manager for user + * @param sbn notification + * @param row row for the notification + */ + void onBindRow(NotificationData.Entry entry, PackageManager pmUser, + StatusBarNotification sbn, ExpandableNotificationRow row); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java index fce79800193d..abb7b4161984 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java @@ -25,10 +25,14 @@ import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; import java.util.ArrayList; +import javax.inject.Inject; +import javax.inject.Singleton; + /** * A manager that ensures that notifications are visually stable. It will suppress reorderings * and reorder at the right time when they are out of view. */ +@Singleton public class VisualStabilityManager implements OnHeadsUpChangedListener { private final ArrayList<Callback> mCallbacks = new ArrayList<>(); @@ -42,6 +46,10 @@ public class VisualStabilityManager implements OnHeadsUpChangedListener { private ArraySet<View> mAddedChildren = new ArraySet<>(); private boolean mPulsing; + @Inject + public VisualStabilityManager() { + } + /** * Add a callback to invoke when reordering is allowed again. * @param callback diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java index 9f02e543b6e3..eb1fc30843b3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java @@ -40,10 +40,14 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import javax.inject.Inject; +import javax.inject.Singleton; + /** * Handles notification logging, in particular, logging which notifications are visible and which * are not. */ +@Singleton public class NotificationLogger implements StateListener { private static final String TAG = "NotificationLogger"; @@ -145,6 +149,7 @@ public class NotificationLogger implements StateListener { } }; + @Inject public NotificationLogger() { mBarService = IStatusBarService.Stub.asInterface( ServiceManager.getService(Context.STATUS_BAR_SERVICE)); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java index 1d79152bb1cc..8b0a6828205d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java @@ -800,7 +800,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView } @Override - public void performRemoveAnimation(long duration, long delay, + public long performRemoveAnimation(long duration, long delay, float translationDirection, boolean isHeadsUpAnimation, float endLocation, Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener) { enableAppearDrawing(true); @@ -812,6 +812,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView } else if (onFinishedRunnable != null) { onFinishedRunnable.run(); } + return 0; } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 91d08fffa642..a58c7cde32a7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -2612,6 +2612,29 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } @Override + public long performRemoveAnimation(long duration, long delay, float translationDirection, + boolean isHeadsUpAnimation, float endLocation, Runnable onFinishedRunnable, + AnimatorListenerAdapter animationListener) { + if (mMenuRow.isMenuVisible()) { + Animator anim = getTranslateViewAnimator(0f, null /* listener */); + if (anim != null) { + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + ExpandableNotificationRow.super.performRemoveAnimation( + duration, delay, translationDirection, isHeadsUpAnimation, + endLocation, onFinishedRunnable, animationListener); + } + }); + anim.start(); + return anim.getDuration(); + } + } + return super.performRemoveAnimation(duration, delay, translationDirection, + isHeadsUpAnimation, endLocation, onFinishedRunnable, animationListener); + } + + @Override protected void onAppearAnimationFinished(boolean wasAppearing) { super.onAppearAnimationFinished(wasAppearing); if (wasAppearing) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java index b1fa6a531438..d1a89b4e493f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java @@ -312,16 +312,19 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable { * @param duration The duration of the remove animation. * @param delay The delay of the animation * @param translationDirection The direction value from [-1 ... 1] indicating in which the - * animation should be performed. A value of -1 means that The - * remove animation should be performed upwards, - * such that the child appears to be going away to the top. 1 - * Should mean the opposite. + * animation should be performed. A value of -1 means that The + * remove animation should be performed upwards, + * such that the child appears to be going away to the top. 1 + * Should mean the opposite. * @param isHeadsUpAnimation Is this a headsUp animation. * @param endLocation The location where the horizonal heads up disappear animation should end. * @param onFinishedRunnable A runnable which should be run when the animation is finished. * @param animationListener An animation listener to add to the animation. + * + * @return The additional delay, in milliseconds, that this view needs to add before the + * animation starts. */ - public abstract void performRemoveAnimation(long duration, + public abstract long performRemoveAnimation(long duration, long delay, float translationDirection, boolean isHeadsUpAnimation, float endLocation, Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener); 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 16796dd86e18..607d96dcbd3f 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 @@ -34,10 +34,14 @@ import java.util.Collections; import java.util.HashSet; import java.util.Set; +import javax.inject.Inject; +import javax.inject.Singleton; + /** * Manager for the notification blocking helper - tracks and helps create the blocking helper * affordance. */ +@Singleton public class NotificationBlockingHelperManager { /** Enables debug logging and always makes the blocking helper show up after a dismiss. */ private static final boolean DEBUG = false; @@ -54,6 +58,7 @@ public class NotificationBlockingHelperManager { */ private boolean mIsShadeExpanded; + @Inject public NotificationBlockingHelperManager(Context context) { mContext = context; mNonBlockablePkgs = new HashSet<>(); 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 2e4552701456..ac4e5830d76b 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 @@ -57,10 +57,14 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController; import java.io.FileDescriptor; import java.io.PrintWriter; +import javax.inject.Inject; +import javax.inject.Singleton; + /** * Handles various NotificationGuts related tasks, such as binding guts to a row, opening and * closing guts, and keeping track of the currently exposed notification guts. */ +@Singleton public class NotificationGutsManager implements Dumpable, NotificationLifetimeExtender { private static final String TAG = "NotificationGutsManager"; @@ -91,6 +95,7 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx @VisibleForTesting protected String mKeyToRemoveOnGutsClosed; + @Inject public NotificationGutsManager(Context context) { mContext = context; mAccessibilityManager = (AccessibilityManager) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java index 1b40c06a5867..eaa2eaf21927 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java @@ -190,12 +190,13 @@ public abstract class StackScrollerDecorView extends ExpandableView { } @Override - public void performRemoveAnimation(long duration, long delay, + public long performRemoveAnimation(long duration, long delay, float translationDirection, boolean isHeadsUpAnimation, float endLocation, Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener) { // TODO: Use duration setContentVisible(false); + return 0; } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index dbe6e8ec764d..67a5cd934a6b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -16,10 +16,8 @@ package com.android.systemui.statusbar.notification.stack; -import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator - .ExpandAnimationParameters; -import static com.android.systemui.statusbar.notification.stack.StackStateAnimator - .ANIMATION_DURATION_SWIPE; +import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters; +import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_SWIPE; import static com.android.systemui.statusbar.phone.NotificationIconAreaController.LOW_PRIORITY; import android.animation.Animator; @@ -651,6 +649,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd < mSections[NUM_SECTIONS - 1].getCurrentBounds().bottom || mAmbientState.isDark())) { drawBackground(canvas); + } else if (mInHeadsUpPinnedMode || mHeadsUpAnimatingAway) { + drawHeadsUpBackground(canvas); } if (DEBUG) { @@ -749,6 +749,32 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd mCornerRadius, mCornerRadius, mBackgroundPaint); } + private void drawHeadsUpBackground(Canvas canvas) { + int left = mSidePaddings; + int right = getWidth() - mSidePaddings; + + float top = getHeight(); + float bottom = 0; + int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + View child = getChildAt(i); + if (child.getVisibility() != View.GONE + && child instanceof ExpandableNotificationRow) { + ExpandableNotificationRow row = (ExpandableNotificationRow) child; + if ((row.isPinned() || row.isHeadsUpAnimatingAway()) && row.getTranslation() < 0) { + top = Math.min(top, row.getTranslationY()); + bottom = Math.max(bottom, row.getTranslationY() + row.getActualHeight()); + } + } + } + + if (top < bottom) { + canvas.drawRoundRect( + left, top, right, bottom, + mCornerRadius, mCornerRadius, mBackgroundPaint); + } + } + @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) private void updateBackgroundDimming() { // No need to update the background color if it's not being drawn. @@ -2157,19 +2183,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd } return; } - NotificationSection firstSection = getFirstVisibleSection(); - int top = 0; - if (firstSection != null) { - ActivatableNotificationView firstView = firstSection.getFirstVisibleChild(); - // Round Y up to avoid seeing the background during animation - int finalTranslationY = (int) Math.ceil(ViewState.getFinalTranslationY(firstView)); - if (mAnimateNextBackgroundTop || firstSection.isTargetTop(finalTranslationY)) { - // we're ending up at the same location as we are now, lets just skip the animation - top = finalTranslationY; - } else { - top = (int) Math.ceil(firstView.getTranslationY()); - } - } + int top = getSectionTopOrFinalTop(getFirstVisibleSection(), mAnimateNextBackgroundTop); NotificationSection lastSection = getLastVisibleSection(); ActivatableNotificationView lastView = mShelf.hasItemsInStableShelf() && mShelf.getVisibility() != GONE @@ -2177,21 +2191,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd : lastSection == null ? null : lastSection.getLastVisibleChild(); int bottom; if (lastView != null) { - int finalTranslationY; - if (lastView == mShelf) { - finalTranslationY = (int) mShelf.getTranslationY(); - } else { - finalTranslationY = (int) ViewState.getFinalTranslationY(lastView); - } - int finalHeight = ExpandableViewState.getFinalActualHeight(lastView); - int finalBottom = finalTranslationY + finalHeight - lastView.getClipBottomAmount(); - if (mAnimateNextBackgroundBottom || lastSection.isTargetBottom(finalBottom)) { - // we're ending up at the same location as we are now, lets just skip the animation - bottom = finalBottom; - } else { - bottom = (int) (lastView.getTranslationY() + lastView.getActualHeight() - - lastView.getClipBottomAmount()); - } + bottom = getSectionBottomOrFinalBottom( + lastSection, lastView, mAnimateNextBackgroundBottom); } else { top = mTopPadding; bottom = top; @@ -2207,6 +2208,57 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd setSectionBoundsByPriority(left, right, top, bottom, mSections[0], mSections[1]); } + private int getSectionTopOrFinalTop( + @Nullable NotificationSection section, boolean alreadyAnimating) { + int top = 0; + if (section != null) { + ActivatableNotificationView firstView = section.getFirstVisibleChild(); + // Round Y up to avoid seeing the background during animation + int finalTranslationY = (int) Math.ceil(ViewState.getFinalTranslationY(firstView)); + if (alreadyAnimating || section.isTargetTop(finalTranslationY)) { + // we're ending up at the same location as we are now, let's just skip the animation + top = finalTranslationY; + } else { + top = (int) Math.ceil(firstView.getTranslationY()); + } + } + return top; + } + + private int getSectionBottomOrFinalBottom( + @Nullable NotificationSection section, boolean alreadyAnimating) { + return section == null ? 0 + : getSectionBottomOrFinalBottom( + section, section.getLastVisibleChild(), alreadyAnimating); + } + + private int getSectionBottomOrFinalBottom( + NotificationSection section, + ActivatableNotificationView lastView, + boolean alreadyAnimating) { + int bottom = 0; + if (lastView != null) { + float finalTranslationY; + if (lastView == mShelf) { + finalTranslationY = mShelf.getTranslationY(); + } else { + finalTranslationY = ViewState.getFinalTranslationY(lastView); + } + int finalHeight = ExpandableViewState.getFinalActualHeight(lastView); + // Round Y down to avoid seeing the background during animation + int finalBottom = (int) Math.floor( + finalTranslationY + finalHeight - lastView.getClipBottomAmount()); + if (alreadyAnimating || section.isTargetBottom(finalBottom)) { + // we're ending up at the same location as we are now, lets just skip the animation + bottom = finalBottom; + } else { + bottom = (int) (lastView.getTranslationY() + lastView.getActualHeight() + - lastView.getClipBottomAmount()); + } + } + return bottom; + } + private void setSectionBoundsByPriority(int left, int right, int top, int bottom, NotificationSection highPrioritySection, NotificationSection lowPrioritySection) { if (NotificationUtils.useNewInterruptionModel(mContext)) { @@ -2214,13 +2266,14 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd ActivatableNotificationView lastChildAboveGap = getLastHighPriorityChild(); ActivatableNotificationView firstChildBelowGap = getFirstLowPriorityChild(); if (lastChildAboveGap != null && firstChildBelowGap != null) { - int gapTop = - (int) Math.max(top, - Math.min(lastChildAboveGap.getTranslationY() - + lastChildAboveGap.getActualHeight(), - bottom)); - int gapBottom = (int) Math.max(top, - Math.min(firstChildBelowGap.getTranslationY(), bottom)); + int gapTop = getSectionBottomOrFinalBottom( + highPrioritySection, mAnimateNextSectionBoundsChange); + gapTop = Math.max(top, Math.min(gapTop, bottom)); + + int gapBottom = getSectionTopOrFinalTop( + lowPrioritySection, mAnimateNextSectionBoundsChange); + gapBottom = Math.max(top, Math.min(gapBottom, bottom)); + highPrioritySection.getBounds().set(left, top, right, gapTop); lowPrioritySection.getBounds().set(left, gapBottom, right, bottom); } else if (lastChildAboveGap != null) { @@ -5574,15 +5627,21 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd if (translatingParentView != null && row == translatingParentView) { mSwipeHelper.clearExposedMenuView(); mSwipeHelper.clearTranslatingParentView(); + if (row instanceof ExpandableNotificationRow) { + mHeadsUpManager.setMenuShown( + ((ExpandableNotificationRow) row).getEntry(), false); + + } } } @Override public void onMenuShown(View row) { if (row instanceof ExpandableNotificationRow) { + ExpandableNotificationRow notificationRow = (ExpandableNotificationRow) row; MetricsLogger.action(mContext, MetricsEvent.ACTION_REVEAL_GEAR, - ((ExpandableNotificationRow) row).getStatusBarNotification() - .getPackageName()); + notificationRow.getStatusBarNotification().getPackageName()); + mHeadsUpManager.setMenuShown(notificationRow.getEntry(), true); } mSwipeHelper.onMenuShown(row); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java index d6905478a043..19fce485b0c7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java @@ -406,13 +406,8 @@ public class StackStateAnimator { } changingView.performRemoveAnimation(ANIMATION_DURATION_APPEAR_DISAPPEAR, - 0 /* delay */, translationDirection, false /* isHeadsUpAppear */, - 0, new Runnable() { - @Override - public void run() { - removeTransientView(changingView); - } - }, null); + 0 /* delay */, translationDirection, false /* isHeadsUpAppear */, + 0, () -> removeTransientView(changingView), null); } else if (event.animationType == NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT) { if (Math.abs(changingView.getTranslation()) == changingView.getWidth() @@ -507,10 +502,11 @@ public class StackStateAnimator { // We need to add the global animation listener, since once no animations are // running anymore, the panel will instantly hide itself. We need to wait until // the animation is fully finished for this though. - changingView.performRemoveAnimation(ANIMATION_DURATION_HEADS_UP_DISAPPEAR - + ANIMATION_DELAY_HEADS_UP, extraDelay, 0.0f, - true /* isHeadsUpAppear */, targetLocation, endRunnable, - getGlobalAnimationFinishedListener()); + long removeAnimationDelay = changingView.performRemoveAnimation( + ANIMATION_DURATION_HEADS_UP_DISAPPEAR + ANIMATION_DELAY_HEADS_UP, + extraDelay, 0.0f, true /* isHeadsUpAppear */, targetLocation, + endRunnable, getGlobalAnimationFinishedListener()); + mAnimationProperties.delay += removeAnimationDelay; } else if (endRunnable != null) { endRunnable.run(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java index aa0b7b656e1c..f4cfd4197637 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java @@ -264,6 +264,17 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, } } + /** + * Sets whether an entry's menu row is exposed and therefore it should stick in the heads up + * area if it's pinned until it's hidden again. + */ + public void setMenuShown(@NonNull NotificationData.Entry entry, boolean menuShown) { + HeadsUpEntry headsUpEntry = getHeadsUpEntry(entry.key); + if (headsUpEntry instanceof HeadsUpEntryPhone && entry.isRowPinned()) { + ((HeadsUpEntryPhone) headsUpEntry).setMenuShownPinned(menuShown); + } + } + /////////////////////////////////////////////////////////////////////////////////////////////// // HeadsUpManager public methods overrides: @@ -469,6 +480,14 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, // HeadsUpEntryPhone: protected class HeadsUpEntryPhone extends HeadsUpManager.HeadsUpEntry { + + private boolean mMenuShownPinned; + + @Override + protected boolean isSticky() { + return super.isSticky() || mMenuShownPinned; + } + public void setEntry(@NonNull final NotificationData.Entry entry) { Runnable removeHeadsUpRunnable = () -> { if (!mVisualStabilityManager.isReorderingAllowed()) { @@ -510,6 +529,25 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, updateEntry(false /* updatePostTime */); } } + + public void setMenuShownPinned(boolean menuShownPinned) { + if (mMenuShownPinned == menuShownPinned) { + return; + } + + mMenuShownPinned = menuShownPinned; + if (menuShownPinned) { + removeAutoRemovalCallbacks(); + } else { + updateEntry(false /* updatePostTime */); + } + } + + @Override + public void reset() { + super.reset(); + mMenuShownPinned = false; + } } public interface AnimationStateHandler { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardDismissUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardDismissUtil.java index b3d0bf8abf62..e541e14b3d91 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardDismissUtil.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardDismissUtil.java @@ -20,15 +20,23 @@ import android.util.Log; import com.android.systemui.plugins.ActivityStarter.OnDismissAction; +import javax.inject.Inject; +import javax.inject.Singleton; + /** * Executes actions that require the screen to be unlocked. Delegates the actual handling to an * implementation passed via {@link #setDismissHandler}. */ +@Singleton public class KeyguardDismissUtil implements KeyguardDismissHandler { private static final String TAG = "KeyguardDismissUtil"; private volatile KeyguardDismissHandler mDismissHandler; + @Inject + public KeyguardDismissUtil() { + } + /** Sets the actual {@link DismissHandler} implementation. */ public void setDismissHandler(KeyguardDismissHandler dismissHandler) { mDismissHandler = dismissHandler; 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 96b753679796..5ba59b507fec 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java @@ -466,6 +466,14 @@ public class KeyguardStatusBarView extends RelativeLayout .onDensityOrFontScaleChanged(); } + @Override + public void onOverlayChanged() { + mCarrierLabel.setTextAppearance( + Utils.getThemeAttr(mContext, com.android.internal.R.attr.textAppearanceSmall)); + onThemeChanged(); + mBatteryView.updatePercentView(); + } + private void updateIconsAndTextColors() { @ColorInt int textColor = Utils.getColorAttrDefaultColor(mContext, R.attr.wallpaperTextColor); 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 55655d5b6240..2daff2cb18bc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java @@ -72,7 +72,6 @@ import androidx.annotation.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.util.LatencyTracker; -import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.SysUiServiceProvider; import com.android.systemui.assist.AssistManager; @@ -97,6 +96,8 @@ import java.util.List; import java.util.Locale; import java.util.function.Consumer; +import javax.inject.Inject; + /** * Fragment containing the NavigationBarFragment. Contains logic for what happens * on clicks and view states of the nav bar. @@ -111,11 +112,12 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback /** Allow some time inbetween the long press for back and recents. */ private static final int LOCK_TO_APP_GESTURE_TOLERENCE = 200; - private final DeviceProvisionedController mDeviceProvisionedController = - Dependency.get(DeviceProvisionedController.class); + private final AccessibilityManagerWrapper mAccessibilityManagerWrapper; + protected final AssistManager mAssistManager; + private final MetricsLogger mMetricsLogger; + private final DeviceProvisionedController mDeviceProvisionedController; protected NavigationBarView mNavigationBarView = null; - protected AssistManager mAssistManager; private int mNavigationBarWindowState = WINDOW_STATE_SHOWING; @@ -124,7 +126,6 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback private AccessibilityManager mAccessibilityManager; private MagnificationContentObserver mMagnificationObserver; private ContentResolver mContentResolver; - private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class); private int mDisabledFlags1; private int mDisabledFlags2; @@ -193,6 +194,17 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback } }; + @Inject + public NavigationBarFragment(AccessibilityManagerWrapper accessibilityManagerWrapper, + DeviceProvisionedController deviceProvisionedController, MetricsLogger metricsLogger, + AssistManager assistManager, OverviewProxyService overviewProxyService) { + mAccessibilityManagerWrapper = accessibilityManagerWrapper; + mDeviceProvisionedController = deviceProvisionedController; + mMetricsLogger = metricsLogger; + mAssistManager = assistManager; + mOverviewProxyService = overviewProxyService; + } + // ----- Fragment Lifecycle Callbacks ----- @Override @@ -205,8 +217,6 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback mDivider = SysUiServiceProvider.getComponent(getContext(), Divider.class); mWindowManager = getContext().getSystemService(WindowManager.class); mAccessibilityManager = getContext().getSystemService(AccessibilityManager.class); - Dependency.get(AccessibilityManagerWrapper.class).addCallback( - mAccessibilityListener); mContentResolver = getContext().getContentResolver(); mMagnificationObserver = new MagnificationContentObserver( getContext().getMainThreadHandler()); @@ -218,15 +228,13 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback mDisabledFlags1 = savedInstanceState.getInt(EXTRA_DISABLE_STATE, 0); mDisabledFlags2 = savedInstanceState.getInt(EXTRA_DISABLE2_STATE, 0); } - mAssistManager = Dependency.get(AssistManager.class); - mOverviewProxyService = Dependency.get(OverviewProxyService.class); + mAccessibilityManagerWrapper.addCallback(mAccessibilityListener); } @Override public void onDestroy() { super.onDestroy(); - Dependency.get(AccessibilityManagerWrapper.class).removeCallback( - mAccessibilityListener); + mAccessibilityManagerWrapper.removeCallback(mAccessibilityListener); mContentResolver.unregisterContentObserver(mMagnificationObserver); } @@ -892,7 +900,8 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback if (DEBUG) Log.v(TAG, "addNavigationBar: about to add " + navigationBarView); if (navigationBarView == null) return null; - final NavigationBarFragment fragment = new NavigationBarFragment(); + final NavigationBarFragment fragment = FragmentHostManager.get(navigationBarView) + .create(NavigationBarFragment.class); navigationBarView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { @Override public void onViewAttachedToWindow(View v) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java index dd81c4e20d6d..6732bbeba493 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java @@ -42,11 +42,15 @@ import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; import java.util.ArrayList; import java.util.Objects; +import javax.inject.Inject; +import javax.inject.Singleton; + /** * A helper class dealing with the alert interactions between {@link NotificationGroupManager}, * {@link HeadsUpManager}, {@link AmbientPulseManager}. In particular, this class deals with keeping * the correct notification in a group alerting based off the group suppression. */ +@Singleton public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedListener, OnAmbientChangedListener, StateListener { @@ -73,6 +77,7 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis private boolean mIsDozing; + @Inject public NotificationGroupAlertTransferHelper() { Dependency.get(StatusBarStateController.class).addCallback(this); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java index 8f4369a98b17..3c1c0765a3b8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java @@ -39,9 +39,13 @@ import java.util.HashMap; import java.util.Map; import java.util.Objects; +import javax.inject.Inject; +import javax.inject.Singleton; + /** * A class to handle notifications and their corresponding groups. */ +@Singleton public class NotificationGroupManager implements OnHeadsUpChangedListener, OnAmbientChangedListener, StateListener { @@ -54,6 +58,7 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, private AmbientPulseManager mAmbientPulseManager = Dependency.get(AmbientPulseManager.class); private boolean mIsUpdatingUnchangedGroup; + @Inject public NotificationGroupManager() { Dependency.get(StatusBarStateController.class).addCallback(this); } 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 75e5cbaff936..1b43f8fad9bd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -187,9 +187,11 @@ import com.android.systemui.statusbar.StatusBarStateController; import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.notification.ActivityLaunchAnimator; import com.android.systemui.statusbar.notification.NotificationActivityStarter; +import com.android.systemui.statusbar.notification.NotificationClicker; import com.android.systemui.statusbar.notification.NotificationData; import com.android.systemui.statusbar.notification.NotificationData.Entry; import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.NotificationRowBinder; import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; @@ -375,6 +377,7 @@ public class StatusBar extends SystemUI implements DemoMode, private NotificationGutsManager mGutsManager; protected NotificationLogger mNotificationLogger; protected NotificationEntryManager mEntryManager; + private NotificationRowBinder mNotificationRowBinder; protected NotificationViewHierarchyManager mViewHierarchyManager; protected ForegroundServiceController mForegroundServiceController; protected AppOpsController mAppOpsController; @@ -619,6 +622,7 @@ public class StatusBar extends SystemUI implements DemoMode, mGutsManager = Dependency.get(NotificationGutsManager.class); mMediaManager = Dependency.get(NotificationMediaManager.class); mEntryManager = Dependency.get(NotificationEntryManager.class); + mNotificationRowBinder = Dependency.get(NotificationRowBinder.class); mViewHierarchyManager = Dependency.get(NotificationViewHierarchyManager.class); mForegroundServiceController = Dependency.get(ForegroundServiceController.class); mAppOpsController = Dependency.get(AppOpsController.class); @@ -630,8 +634,6 @@ public class StatusBar extends SystemUI implements DemoMode, mBubbleController = Dependency.get(BubbleController.class); mBubbleController.setExpandListener(mBubbleExpandListener); - mGroupAlertTransferHelper.bind(mEntryManager, mGroupManager); - mColorExtractor.addOnColorsChangedListener(this); mStatusBarStateController.addCallback(this, StatusBarStateController.RANK_STATUS_BAR); @@ -1015,10 +1017,10 @@ public class StatusBar extends SystemUI implements DemoMode, } protected QS createDefaultQSFragment() { - return new QSFragment(); + return FragmentHostManager.get(mStatusBarWindow).create(QSFragment.class); } - protected void setUpPresenter() { + private void setUpPresenter() { // Set up the initial notification state. mActivityLaunchAnimator = new ActivityLaunchAnimator( mStatusBarWindow, this, mNotificationPanel, @@ -1036,7 +1038,10 @@ public class StatusBar extends SystemUI implements DemoMode, mNotificationActivityStarter = new StatusBarNotificationActivityStarter( mContext, mNotificationPanel, mPresenter, mHeadsUpManager, mActivityLaunchAnimator); mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter); - mEntryManager.setNotificationActivityStarter(mNotificationActivityStarter); + mNotificationRowBinder.setNotificationClicker(new NotificationClicker( + this, Dependency.get(BubbleController.class), mNotificationActivityStarter)); + + mGroupAlertTransferHelper.bind(mEntryManager, mGroupManager); } /** @@ -1162,6 +1167,10 @@ public class StatusBar extends SystemUI implements DemoMode, if (mBrightnessMirrorController != null) { mBrightnessMirrorController.onOverlayChanged(); } + // We need the new R.id.keyguard_indication_area before recreating + // mKeyguardIndicationController + mNotificationPanel.onThemeChanged(); + onThemeChanged(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java index 261f117b58b4..c8c9ebe59677 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java @@ -58,6 +58,7 @@ import com.android.systemui.statusbar.notification.AboveShelfObserver; import com.android.systemui.statusbar.notification.ActivityLaunchAnimator; import com.android.systemui.statusbar.notification.NotificationData.Entry; import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.NotificationRowBinder; import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; @@ -88,6 +89,8 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, Dependency.get(StatusBarStateController.class); private final NotificationEntryManager mEntryManager = Dependency.get(NotificationEntryManager.class); + private final NotificationRowBinder mNotificationRowBinder = + Dependency.get(NotificationRowBinder.class); private final NotificationMediaManager mMediaManager = Dependency.get(NotificationMediaManager.class); protected AmbientPulseManager mAmbientPulseManager = Dependency.get(AmbientPulseManager.class); @@ -168,6 +171,8 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, Dependency.get(InitController.class).addPostInitTask(() -> { mViewHierarchyManager.setUpWithPresenter(this, notifListContainer); mEntryManager.setUpWithPresenter(this, notifListContainer, this, mHeadsUpManager); + mNotificationRowBinder.setUpWithPresenter(this, notifListContainer, mHeadsUpManager, + mEntryManager, this); mLockscreenUserManager.setUpWithPresenter(this); mMediaManager.setUpWithPresenter(this); Dependency.get(NotificationGutsManager.class).setUpWithPresenter(this, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java index e72806438f54..a02c9d51fecf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java @@ -16,8 +16,7 @@ package com.android.systemui.statusbar.policy; -import static com.android.systemui.statusbar.notification.row.NotificationInflater - .FLAG_CONTENT_VIEW_HEADS_UP; +import static com.android.systemui.statusbar.notification.row.NotificationInflater.FLAG_CONTENT_VIEW_HEADS_UP; import android.annotation.NonNull; import android.annotation.Nullable; @@ -32,7 +31,6 @@ import com.android.internal.logging.MetricsLogger; import com.android.systemui.R; import com.android.systemui.statusbar.AlertingNotificationManager; import com.android.systemui.statusbar.notification.NotificationData; -import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationInflater.InflationFlag; import java.io.FileDescriptor; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisabler.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisabler.java index c2933e1516be..2a10db620096 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisabler.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisabler.java @@ -27,9 +27,13 @@ import com.android.systemui.qs.QSFragment; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.phone.StatusBar; +import javax.inject.Inject; +import javax.inject.Singleton; + /** * Let {@link RemoteInputView} to control the visibility of QuickSetting. */ +@Singleton public class RemoteInputQuickSettingsDisabler implements ConfigurationController.ConfigurationListener { @@ -39,6 +43,7 @@ public class RemoteInputQuickSettingsDisabler private int mLastOrientation; @VisibleForTesting CommandQueue mCommandQueue; + @Inject public RemoteInputQuickSettingsDisabler(Context context) { mContext = context; mCommandQueue = SysUiServiceProvider.getComponent(context, CommandQueue.class); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java index 71d6e5421b75..6193159ec0a5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.policy; +import static com.android.systemui.Dependency.MAIN_HANDLER_NAME; + import android.content.Context; import android.content.res.Resources; import android.database.ContentObserver; @@ -27,6 +29,11 @@ import android.util.Log; import com.android.systemui.R; +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; + +@Singleton public final class SmartReplyConstants extends ContentObserver { private static final String TAG = "SmartReplyConstants"; @@ -47,7 +54,8 @@ public final class SmartReplyConstants extends ContentObserver { private final Context mContext; private final KeyValueListParser mParser = new KeyValueListParser(','); - public SmartReplyConstants(Handler handler, Context context) { + @Inject + public SmartReplyConstants(@Named(MAIN_HANDLER_NAME) Handler handler, Context context) { super(handler); mContext = context; diff --git a/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java index dae1472c42af..0a29e04ce20f 100644 --- a/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java +++ b/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java @@ -184,7 +184,11 @@ public class PluginFragment extends PreferenceFragment { mInfo.services[i].name); if (mPluginEnabler.isEnabled(componentName) != isEnabled) { - mPluginEnabler.setEnabled(componentName, isEnabled); + if (isEnabled) { + mPluginEnabler.setEnabled(componentName); + } else { + mPluginEnabler.setDisabled(componentName, PluginEnabler.DISABLED_MANUALLY); + } shouldSendBroadcast = true; } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java b/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java index 0c8d137569f8..18bf75e3cfb1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java +++ b/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java @@ -29,9 +29,10 @@ public class TestableDependency extends Dependency { mComponents = ((SysuiTestableContext) context).getComponents(); } mContext = context; - if (SystemUIFactory.getInstance() == null) { - SystemUIFactory.createFromConfig(context); - } + SystemUIFactory.createFromConfig(context); + SystemUIFactory.getInstance().getRootComponent() + .createDependency() + .createSystemUI(this); start(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java index b699163c40e8..bb445483c966 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java @@ -17,8 +17,10 @@ package com.android.systemui.appops; import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; @@ -32,7 +34,6 @@ import android.testing.TestableLooper; import com.android.systemui.Dependency; import com.android.systemui.SysuiTestCase; -import com.android.systemui.statusbar.NotificationPresenter; import org.junit.Before; import org.junit.Test; @@ -48,9 +49,12 @@ public class AppOpsControllerTest extends SysuiTestCase { private static final int TEST_UID = 0; private static final int TEST_UID_OTHER = 500000; - @Mock private NotificationPresenter mPresenter; - @Mock private AppOpsManager mAppOpsManager; - @Mock private AppOpsController.Callback mCallback; + @Mock + private AppOpsManager mAppOpsManager; + @Mock + private AppOpsController.Callback mCallback; + @Mock + private AppOpsControllerImpl.H mMockHandler; private AppOpsControllerImpl mController; @@ -77,9 +81,13 @@ public class AppOpsControllerTest extends SysuiTestCase { @Test public void addCallback_includedCode() { - mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO}, mCallback); + mController.addCallback( + new int[]{AppOpsManager.OP_RECORD_AUDIO, AppOpsManager.OP_FINE_LOCATION}, + mCallback); mController.onOpActiveChanged( AppOpsManager.OP_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true); + mController.onOpNoted(AppOpsManager.OPSTR_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, + AppOpsManager.MODE_ALLOWED); verify(mCallback).onActiveStateChanged(AppOpsManager.OP_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true); } @@ -106,7 +114,7 @@ public class AppOpsControllerTest extends SysuiTestCase { @Test public void addCallback_notSameCode() { mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO}, mCallback); - mController.removeCallback(new int[]{AppOpsManager.OP_FINE_LOCATION}, mCallback); + mController.removeCallback(new int[]{AppOpsManager.OP_CAMERA}, mCallback); mController.onOpActiveChanged( AppOpsManager.OP_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true); verify(mCallback).onActiveStateChanged(AppOpsManager.OP_RECORD_AUDIO, @@ -128,17 +136,30 @@ public class AppOpsControllerTest extends SysuiTestCase { TEST_UID, TEST_PACKAGE_NAME, true); mController.onOpActiveChanged(AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, true); - assertEquals(2, mController.getActiveAppOps().size()); + mController.onOpNoted(AppOpsManager.OPSTR_FINE_LOCATION, + TEST_UID, TEST_PACKAGE_NAME, AppOpsManager.MODE_ALLOWED); + assertEquals(3, mController.getActiveAppOps().size()); } - @Test public void getActiveItemsForUser() { + @Test + public void getActiveItemsForUser() { mController.onOpActiveChanged(AppOpsManager.OP_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true); mController.onOpActiveChanged(AppOpsManager.OP_CAMERA, TEST_UID_OTHER, TEST_PACKAGE_NAME, true); - assertEquals(1, + mController.onOpNoted(AppOpsManager.OPSTR_FINE_LOCATION, + TEST_UID, TEST_PACKAGE_NAME, AppOpsManager.MODE_ALLOWED); + assertEquals(2, mController.getActiveAppOpsForUser(UserHandle.getUserId(TEST_UID)).size()); assertEquals(1, - mController.getActiveAppOpsForUser(UserHandle.getUserId(TEST_UID)).size()); + mController.getActiveAppOpsForUser(UserHandle.getUserId(TEST_UID_OTHER)).size()); + } + + @Test + public void opNotedScheduledForRemoval() { + mController.setBGHandler(mMockHandler); + mController.onOpNoted(AppOpsManager.OPSTR_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, + AppOpsManager.MODE_ALLOWED); + verify(mMockHandler).scheduleRemoval(any(AppOpItem.class), anyLong()); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt index 24bcca50d34a..563599b32d60 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt @@ -19,6 +19,7 @@ package com.android.systemui.privacy import android.app.ActivityManager import android.app.AppOpsManager import android.content.Intent +import android.content.pm.UserInfo import android.os.Handler import android.os.UserHandle import android.os.UserManager @@ -30,13 +31,16 @@ import com.android.systemui.Dependency import com.android.systemui.SysuiTestCase import com.android.systemui.appops.AppOpItem import com.android.systemui.appops.AppOpsController +import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatchers.any import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.anyList import org.mockito.ArgumentMatchers.eq +import org.mockito.Captor import org.mockito.Mock import org.mockito.Mockito.atLeastOnce import org.mockito.Mockito.doReturn @@ -52,8 +56,8 @@ class PrivacyItemControllerTest : SysuiTestCase() { companion object { val CURRENT_USER_ID = ActivityManager.getCurrentUser() - val OTHER_USER = UserHandle(CURRENT_USER_ID + 1) const val TAG = "PrivacyItemControllerTest" + fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture() } @Mock @@ -62,6 +66,8 @@ class PrivacyItemControllerTest : SysuiTestCase() { private lateinit var callback: PrivacyItemController.Callback @Mock private lateinit var userManager: UserManager + @Captor + private lateinit var argCaptor: ArgumentCaptor<List<PrivacyItem>> private lateinit var testableLooper: TestableLooper private lateinit var privacyItemController: PrivacyItemController @@ -76,8 +82,11 @@ class PrivacyItemControllerTest : SysuiTestCase() { mDependency.injectTestDependency(Dependency.MAIN_HANDLER, Handler(testableLooper.looper)) mContext.addMockSystemService(UserManager::class.java, userManager) - doReturn(listOf(AppOpItem(AppOpsManager.OP_CAMERA, 0, "", 0))) - .`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + doReturn(listOf(object : UserInfo() { + init { + id = CURRENT_USER_ID + } + })).`when`(userManager).getProfiles(anyInt()) privacyItemController = PrivacyItemController(mContext, callback) } @@ -100,6 +109,18 @@ class PrivacyItemControllerTest : SysuiTestCase() { } @Test + fun testDistinctItems() { + doReturn(listOf(AppOpItem(AppOpsManager.OP_CAMERA, CURRENT_USER_ID, "", 0), + AppOpItem(AppOpsManager.OP_CAMERA, CURRENT_USER_ID, "", 1))) + .`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + + privacyItemController.setListening(true) + testableLooper.processAllMessages() + verify(callback).privacyChanged(capture(argCaptor)) + assertEquals(1, argCaptor.value.size) + } + + @Test fun testRegisterReceiver_allUsers() { val spiedContext = spy(mContext) val itemController = PrivacyItemController(spiedContext, callback) diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java index bc7d9836d6f8..39afbac15c65 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java @@ -18,7 +18,9 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; +import android.app.Fragment; import android.content.Context; +import android.os.Bundle; import android.os.Looper; import android.support.test.filters.SmallTest; import android.testing.AndroidTestingRunner; @@ -35,6 +37,7 @@ import com.android.systemui.R; import com.android.systemui.SysuiBaseFragmentTest; import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.policy.Clock; +import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler; import com.android.systemui.statusbar.policy.UserSwitcherController; import org.junit.Before; @@ -52,6 +55,7 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { public QSFragmentTest() { super(QSFragment.class); + injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES); } @Before @@ -70,7 +74,6 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { mDependency.injectTestDependency(Dependency.BG_LOOPER, TestableLooper.get(this).getLooper()); mDependency.injectMockDependency(UserSwitcherController.class); - injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES); } @Test @@ -116,4 +119,9 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { assertTrue(qs.isListening()); assertTrue(qs.isExpanded()); } + + @Override + protected Fragment instantiate(Context context, String className, Bundle arguments) { + return new QSFragment(new RemoteInputQuickSettingsDisabler(context)); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java index 26fa20de4e86..c3a3e6339daa 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java @@ -218,4 +218,12 @@ public class TileQueryHelperTest extends SysuiTestCase { } assertFalse(specs.contains("other")); } + + @Test + public void testQueryTiles_nullSetting() { + Settings.Secure.putString(mContext.getContentResolver(), Settings.Secure.QS_TILES, null); + mContext.getOrCreateTestableResources().addOverride(R.string.quick_settings_tiles_stock, + STOCK_TILES); + mTileQueryHelper.queryTiles(mQSTileHost); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceManagerTest.java index 5cc3b3c7823b..458377017fb9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceManagerTest.java @@ -17,6 +17,7 @@ package com.android.systemui.shared.plugins; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertTrue; + import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.eq; @@ -26,22 +27,6 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; -import com.android.systemui.SysuiTestCase; -import com.android.systemui.plugins.Plugin; -import com.android.systemui.plugins.PluginEnablerImpl; -import com.android.systemui.plugins.PluginListener; -import com.android.systemui.shared.plugins.PluginInstanceManager.PluginInfo; -import com.android.systemui.shared.plugins.VersionInfo.InvalidVersionException; -import com.android.systemui.plugins.annotations.Requires; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Mockito; - import android.app.Activity; import android.app.NotificationManager; import android.content.BroadcastReceiver; @@ -60,6 +45,21 @@ import android.support.test.annotation.UiThreadTest; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; +import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.plugins.Plugin; +import com.android.systemui.plugins.PluginListener; +import com.android.systemui.plugins.annotations.Requires; +import com.android.systemui.shared.plugins.PluginInstanceManager.PluginInfo; +import com.android.systemui.shared.plugins.VersionInfo.InvalidVersionException; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; + import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -79,6 +79,9 @@ public class PluginInstanceManagerTest extends SysuiTestCase { private PluginInstanceManager mPluginInstanceManager; private PluginManagerImpl mMockManager; private VersionInfo mMockVersionInfo; + private PluginEnabler mMockEnabler; + ComponentName mTestPluginComponentName = + new ComponentName(WHITELISTED_PACKAGE, TestPlugin.class.getName()); @Before public void setup() throws Exception { @@ -88,9 +91,9 @@ public class PluginInstanceManagerTest extends SysuiTestCase { mMockPm = mock(PackageManager.class); mMockListener = mock(PluginListener.class); mMockManager = mock(PluginManagerImpl.class); - when(mMockManager.getClassLoader(any(), any())) - .thenReturn(getClass().getClassLoader()); - when(mMockManager.getPluginEnabler()).thenReturn(new PluginEnablerImpl(mMockPm)); + when(mMockManager.getClassLoader(any(), any())).thenReturn(getClass().getClassLoader()); + mMockEnabler = mock(PluginEnabler.class); + when(mMockManager.getPluginEnabler()).thenReturn(mMockEnabler); mMockVersionInfo = mock(VersionInfo.class); mPluginInstanceManager = new PluginInstanceManager(mContextWrapper, mMockPm, "myAction", mMockListener, true, mHandlerThread.getLooper(), mMockVersionInfo, @@ -230,18 +233,13 @@ public class PluginInstanceManagerTest extends SysuiTestCase { // Start with an unrelated class. boolean result = mPluginInstanceManager.checkAndDisable(Activity.class.getName()); assertFalse(result); - verify(mMockPm, never()).setComponentEnabledSetting( - ArgumentCaptor.forClass(ComponentName.class).capture(), - ArgumentCaptor.forClass(int.class).capture(), - ArgumentCaptor.forClass(int.class).capture()); + verify(mMockEnabler, never()).setDisabled(any(ComponentName.class), anyInt()); // Now hand it a real class and make sure it disables the plugin. result = mPluginInstanceManager.checkAndDisable(TestPlugin.class.getName()); assertTrue(result); - verify(mMockPm).setComponentEnabledSetting( - ArgumentCaptor.forClass(ComponentName.class).capture(), - ArgumentCaptor.forClass(int.class).capture(), - ArgumentCaptor.forClass(int.class).capture()); + verify(mMockEnabler).setDisabled( + mTestPluginComponentName, PluginEnabler.DISABLED_FROM_EXPLICIT_CRASH); } @Test @@ -250,10 +248,8 @@ public class PluginInstanceManagerTest extends SysuiTestCase { mPluginInstanceManager.disableAll(); - verify(mMockPm).setComponentEnabledSetting( - ArgumentCaptor.forClass(ComponentName.class).capture(), - ArgumentCaptor.forClass(int.class).capture(), - ArgumentCaptor.forClass(int.class).capture()); + verify(mMockEnabler).setDisabled( + mTestPluginComponentName, PluginEnabler.DISABLED_FROM_SYSTEM_CRASH); } @Test @@ -275,8 +271,8 @@ public class PluginInstanceManagerTest extends SysuiTestCase { List<ResolveInfo> list = new ArrayList<>(); ResolveInfo info = new ResolveInfo(); info.serviceInfo = mock(ServiceInfo.class); - info.serviceInfo.packageName = "com.android.systemui"; - info.serviceInfo.name = TestPlugin.class.getName(); + info.serviceInfo.packageName = mTestPluginComponentName.getPackageName(); + info.serviceInfo.name = mTestPluginComponentName.getClassName(); when(info.serviceInfo.loadLabel(any())).thenReturn("Test Plugin"); list.add(info); when(mMockPm.queryIntentServices(any(), Mockito.anyInt())).thenReturn(list); @@ -288,6 +284,7 @@ public class PluginInstanceManagerTest extends SysuiTestCase { ApplicationInfo appInfo = getContext().getApplicationInfo(); when(mMockPm.getApplicationInfo(Mockito.anyString(), Mockito.anyInt())).thenReturn( appInfo); + when(mMockEnabler.isEnabled(mTestPluginComponentName)).thenReturn(true); } private void createPlugin() throws Exception { diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginManagerTest.java index ff1bc8abf5d1..76e68f1df724 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginManagerTest.java @@ -27,7 +27,6 @@ import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.net.Uri; -import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -82,17 +81,18 @@ public class PluginManagerTest extends SysuiTestCase { .thenReturn(mMockPluginInstance); mMockPackageManager = mock(PackageManager.class); - mPluginManager = new PluginManagerImpl(getContext(), mMockFactory, true, + mPluginManager = new PluginManagerImpl( + getContext(), mMockFactory, true, mMockExceptionHandler, new PluginInitializerImpl() { - @Override - public String[] getWhitelistedPlugins(Context context) { - return new String[0]; - } - - @Override - public PluginEnabler getPluginEnabler(Context context) { - return new PluginEnablerImpl(mMockPackageManager); - } + @Override + public String[] getWhitelistedPlugins(Context context) { + return new String[0]; + } + + @Override + public PluginEnabler getPluginEnabler(Context context) { + return new PluginEnablerImpl(context, mMockPackageManager); + } }); resetExceptionHandler(); mMockListener = mock(PluginListener.class); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java index 904e5b99b0ab..8fe91cd5e82a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java @@ -104,6 +104,8 @@ public class NotificationEntryManagerTest extends SysuiTestCase { @Mock private ExpandableNotificationRow mRow; @Mock private NotificationListContainer mListContainer; @Mock private NotificationEntryManager.Callback mCallback; + @Mock + private NotificationRowBinder.BindRowCallback mBindCallback; @Mock private HeadsUpManager mHeadsUpManager; @Mock private NotificationListenerService.RankingMap mRankingMap; @Mock private RemoteInputController mRemoteInputController; @@ -232,6 +234,11 @@ public class NotificationEntryManagerTest extends SysuiTestCase { Dependency.get(InitController.class).executePostInitTasks(); mEntryManager.setUpWithPresenter(mPresenter, mListContainer, mCallback, mHeadsUpManager); + NotificationRowBinder notificationRowBinder = Dependency.get(NotificationRowBinder.class); + notificationRowBinder.setUpWithPresenter( + mPresenter, mListContainer, mHeadsUpManager, mEntryManager, mBindCallback); + notificationRowBinder.setNotificationClicker(mock(NotificationClicker.class)); + setUserSentiment(mEntry.key, NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL); } @@ -243,7 +250,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase { doAnswer(invocation -> { mCountDownLatch.countDown(); return null; - }).when(mCallback).onBindRow(any(), any(), any(), any()); + }).when(mBindCallback).onBindRow(any(), any(), any(), any()); // Post on main thread, otherwise we will be stuck waiting here for the inflation finished // callback forever, since it won't execute until the tests ends. @@ -260,7 +267,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase { // Row inflation: ArgumentCaptor<NotificationData.Entry> entryCaptor = ArgumentCaptor.forClass( NotificationData.Entry.class); - verify(mCallback).onBindRow(entryCaptor.capture(), any(), eq(mSbn), any()); + verify(mBindCallback).onBindRow(entryCaptor.capture(), any(), eq(mSbn), any()); NotificationData.Entry entry = entryCaptor.getValue(); verify(mRemoteInputManager).bindRow(entry.getRow()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java index 9e2db913aba5..728723b4094c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java @@ -14,10 +14,13 @@ package com.android.systemui.statusbar.phone; +import static org.junit.Assert.assertNotNull; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import android.app.Fragment; import android.content.Context; +import android.os.Bundle; import android.os.Looper; import android.support.test.filters.SmallTest; import android.testing.AndroidTestingRunner; @@ -27,13 +30,17 @@ import android.view.Display; import android.view.WindowManager; import android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener; +import com.android.internal.logging.MetricsLogger; import com.android.systemui.Dependency; import com.android.systemui.SysuiBaseFragmentTest; +import com.android.systemui.assist.AssistManager; import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.recents.Recents; import com.android.systemui.stackdivider.Divider; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; +import com.android.systemui.statusbar.policy.DeviceProvisionedController; +import com.android.systemui.statusbar.policy.DeviceProvisionedControllerImpl; import org.junit.Before; import org.junit.Test; @@ -44,6 +51,23 @@ import org.junit.runner.RunWith; @SmallTest public class NavigationBarFragmentTest extends SysuiBaseFragmentTest { + private OverviewProxyService mOverviewProxyService = + mDependency.injectMockDependency(OverviewProxyService.class); + private AccessibilityManagerWrapper mAccessibilityWrapper = + new AccessibilityManagerWrapper(mContext) { + Tracker mTracker = mLeakCheck.getTracker("accessibility_manager"); + + @Override + public void addCallback(AccessibilityServicesStateChangeListener listener) { + mTracker.getLeakInfo(listener).addAllocation(new Throwable()); + } + + @Override + public void removeCallback(AccessibilityServicesStateChangeListener listener) { + mTracker.getLeakInfo(listener).clearAllocations(); + } + }; + public NavigationBarFragmentTest() { super(NavigationBarFragment.class); } @@ -54,32 +78,19 @@ public class NavigationBarFragmentTest extends SysuiBaseFragmentTest { @Before public void setup() { - mDependency.injectTestDependency(Dependency.BG_LOOPER, Looper.getMainLooper()); mSysuiContext.putComponent(CommandQueue.class, mock(CommandQueue.class)); mSysuiContext.putComponent(StatusBar.class, mock(StatusBar.class)); mSysuiContext.putComponent(Recents.class, mock(Recents.class)); mSysuiContext.putComponent(Divider.class, mock(Divider.class)); injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES); - mDependency.injectMockDependency(OverviewProxyService.class); WindowManager windowManager = mock(WindowManager.class); Display defaultDisplay = mContext.getSystemService(WindowManager.class).getDefaultDisplay(); when(windowManager.getDefaultDisplay()).thenReturn( defaultDisplay); mContext.addMockSystemService(Context.WINDOW_SERVICE, windowManager); - Tracker tracker = mLeakCheck.getTracker("accessibility_manager"); - AccessibilityManagerWrapper wrapper = new AccessibilityManagerWrapper(mContext) { - @Override - public void addCallback(AccessibilityServicesStateChangeListener listener) { - tracker.getLeakInfo(listener).addAllocation(new Throwable()); - } - - @Override - public void removeCallback(AccessibilityServicesStateChangeListener listener) { - tracker.getLeakInfo(listener).clearAllocations(); - } - }; - mDependency.injectTestDependency(AccessibilityManagerWrapper.class, wrapper); + mDependency.injectTestDependency(Dependency.BG_LOOPER, Looper.getMainLooper()); + mDependency.injectTestDependency(AccessibilityManagerWrapper.class, mAccessibilityWrapper); } @Test @@ -91,4 +102,15 @@ public class NavigationBarFragmentTest extends SysuiBaseFragmentTest { navigationBarFragment.onHomeLongClick(navigationBarFragment.getView()); } + @Override + protected Fragment instantiate(Context context, String className, Bundle arguments) { + DeviceProvisionedController deviceProvisionedController = + new DeviceProvisionedControllerImpl(context); + assertNotNull(mAccessibilityWrapper); + return new NavigationBarFragment(mAccessibilityWrapper, + deviceProvisionedController, + new MetricsLogger(), + new AssistManager(deviceProvisionedController, mContext), + mOverviewProxyService); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java index c207feff26f3..e5620a5520d5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java @@ -176,6 +176,7 @@ public class StatusBarTest extends SysuiTestCase { mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger); mDependency.injectTestDependency(NotificationLogger.class, mNotificationLogger); mNotificationLogger = new NotificationLogger(); + DozeLog.traceDozing(mContext, false /* dozing */); IPowerManager powerManagerService = mock(IPowerManager.class); mPowerManager = new PowerManager(mContext, powerManagerService, diff --git a/packages/overlays/AccentColorBlackOverlay/Android.mk b/packages/overlays/AccentColorBlackOverlay/Android.mk index d316fbd8907c..b81ae5bbe3fa 100644 --- a/packages/overlays/AccentColorBlackOverlay/Android.mk +++ b/packages/overlays/AccentColorBlackOverlay/Android.mk @@ -19,6 +19,7 @@ include $(CLEAR_VARS) LOCAL_RRO_THEME := AccentColorBlack LOCAL_CERTIFICATE := platform +LOCAL_PRODUCT_MODULE := true LOCAL_SRC_FILES := $(call all-subdir-java-files) diff --git a/packages/overlays/AccentColorGreenOverlay/Android.mk b/packages/overlays/AccentColorGreenOverlay/Android.mk index afc42873a4d6..db92157c8fdf 100644 --- a/packages/overlays/AccentColorGreenOverlay/Android.mk +++ b/packages/overlays/AccentColorGreenOverlay/Android.mk @@ -19,6 +19,7 @@ include $(CLEAR_VARS) LOCAL_RRO_THEME := AccentColorGreen LOCAL_CERTIFICATE := platform +LOCAL_PRODUCT_MODULE := true LOCAL_SRC_FILES := $(call all-subdir-java-files) diff --git a/packages/overlays/AccentColorPurpleOverlay/Android.mk b/packages/overlays/AccentColorPurpleOverlay/Android.mk index 336616921d71..d7dc4978e2ca 100644 --- a/packages/overlays/AccentColorPurpleOverlay/Android.mk +++ b/packages/overlays/AccentColorPurpleOverlay/Android.mk @@ -19,6 +19,7 @@ include $(CLEAR_VARS) LOCAL_RRO_THEME := AccentColorPurple LOCAL_CERTIFICATE := platform +LOCAL_PRODUCT_MODULE := true LOCAL_SRC_FILES := $(call all-subdir-java-files) diff --git a/packages/overlays/CleanSpec.mk b/packages/overlays/CleanSpec.mk new file mode 100644 index 000000000000..16fbaa202aa1 --- /dev/null +++ b/packages/overlays/CleanSpec.mk @@ -0,0 +1,53 @@ +# 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. +# + +# If you don't need to do a full clean build but would like to touch +# a file or delete some intermediate files, add a clean step to the end +# of the list. These steps will only be run once, if they haven't been +# run before. +# +# E.g.: +# $(call add-clean-step, touch -c external/sqlite/sqlite3.h) +# $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libz_intermediates) +# +# Always use "touch -c" and "rm -f" or "rm -rf" to gracefully deal with +# files that are missing or have been moved. +# +# Use $(PRODUCT_OUT) to get to the "out/target/product/blah/" directory. +# Use $(OUT_DIR) to refer to the "out" directory. +# +# If you need to re-do something that's already mentioned, just copy +# the command and add it to the bottom of the list. E.g., if a change +# that you made last week required touching a file and a change you +# made today requires touching the same file, just copy the old +# touch step and add it to the end of the list. +# +# ************************************************ +# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST +# ************************************************ + +# For example: +#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/AndroidTests_intermediates) +#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/core_intermediates) +#$(call add-clean-step, find $(OUT_DIR) -type f -name "IGTalkSession*" -print0 | xargs -0 rm -f) +#$(call add-clean-step, rm -rf $(PRODUCT_OUT)/data/*) + +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/vendor/overlay/AccentColor*) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/vendor/overlay/DisplayCutout*) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/vendor/overlay/IconShape*) + +# ************************************************ +# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST +# ************************************************ diff --git a/packages/overlays/DisplayCutoutEmulationCornerOverlay/Android.mk b/packages/overlays/DisplayCutoutEmulationCornerOverlay/Android.mk index 74c43b40616f..bf2b6312d68f 100644 --- a/packages/overlays/DisplayCutoutEmulationCornerOverlay/Android.mk +++ b/packages/overlays/DisplayCutoutEmulationCornerOverlay/Android.mk @@ -4,6 +4,8 @@ include $(CLEAR_VARS) LOCAL_RRO_THEME := DisplayCutoutEmulationCorner LOCAL_CERTIFICATE := platform +LOCAL_PRODUCT_MODULE := true + LOCAL_SRC_FILES := $(call all-subdir-java-files) LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res diff --git a/packages/overlays/DisplayCutoutEmulationDoubleOverlay/Android.mk b/packages/overlays/DisplayCutoutEmulationDoubleOverlay/Android.mk index d83b30a8785a..70429064ec2b 100644 --- a/packages/overlays/DisplayCutoutEmulationDoubleOverlay/Android.mk +++ b/packages/overlays/DisplayCutoutEmulationDoubleOverlay/Android.mk @@ -4,6 +4,8 @@ include $(CLEAR_VARS) LOCAL_RRO_THEME := DisplayCutoutEmulationDouble LOCAL_CERTIFICATE := platform +LOCAL_PRODUCT_MODULE := true + LOCAL_SRC_FILES := $(call all-subdir-java-files) LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res diff --git a/packages/overlays/DisplayCutoutEmulationNarrowOverlay/Android.mk b/packages/overlays/DisplayCutoutEmulationNarrowOverlay/Android.mk index f5afad24676f..ae69e1137e60 100644 --- a/packages/overlays/DisplayCutoutEmulationNarrowOverlay/Android.mk +++ b/packages/overlays/DisplayCutoutEmulationNarrowOverlay/Android.mk @@ -4,6 +4,8 @@ include $(CLEAR_VARS) LOCAL_RRO_THEME := DisplayCutoutEmulationNarrow LOCAL_CERTIFICATE := platform +LOCAL_PRODUCT_MODULE := true + LOCAL_SRC_FILES := $(call all-subdir-java-files) LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res diff --git a/packages/overlays/DisplayCutoutEmulationTallOverlay/Android.mk b/packages/overlays/DisplayCutoutEmulationTallOverlay/Android.mk index f1f8c27d94f1..7dcadfbd4708 100644 --- a/packages/overlays/DisplayCutoutEmulationTallOverlay/Android.mk +++ b/packages/overlays/DisplayCutoutEmulationTallOverlay/Android.mk @@ -4,6 +4,8 @@ include $(CLEAR_VARS) LOCAL_RRO_THEME := DisplayCutoutEmulationTall LOCAL_CERTIFICATE := platform +LOCAL_PRODUCT_MODULE := true + LOCAL_SRC_FILES := $(call all-subdir-java-files) LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res diff --git a/packages/overlays/DisplayCutoutEmulationWideOverlay/Android.mk b/packages/overlays/DisplayCutoutEmulationWideOverlay/Android.mk index d149d8ecf4df..3f7be73aa5fa 100644 --- a/packages/overlays/DisplayCutoutEmulationWideOverlay/Android.mk +++ b/packages/overlays/DisplayCutoutEmulationWideOverlay/Android.mk @@ -4,6 +4,8 @@ include $(CLEAR_VARS) LOCAL_RRO_THEME := DisplayCutoutEmulationWide LOCAL_CERTIFICATE := platform +LOCAL_PRODUCT_MODULE := true + LOCAL_SRC_FILES := $(call all-subdir-java-files) LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res diff --git a/packages/overlays/IconShapeRoundedRectOverlay/Android.mk b/packages/overlays/IconShapeRoundedRectOverlay/Android.mk index a734a6b46947..08428d192fae 100644 --- a/packages/overlays/IconShapeRoundedRectOverlay/Android.mk +++ b/packages/overlays/IconShapeRoundedRectOverlay/Android.mk @@ -19,6 +19,7 @@ include $(CLEAR_VARS) LOCAL_RRO_THEME := IconShapeRoundedRect LOCAL_CERTIFICATE := platform +LOCAL_PRODUCT_MODULE := true LOCAL_SRC_FILES := $(call all-subdir-java-files) diff --git a/packages/overlays/IconShapeSquareOverlay/Android.mk b/packages/overlays/IconShapeSquareOverlay/Android.mk index 217da9feb534..ceb745ae1429 100644 --- a/packages/overlays/IconShapeSquareOverlay/Android.mk +++ b/packages/overlays/IconShapeSquareOverlay/Android.mk @@ -19,6 +19,7 @@ include $(CLEAR_VARS) LOCAL_RRO_THEME := IconShapeSquare LOCAL_CERTIFICATE := platform +LOCAL_PRODUCT_MODULE := true LOCAL_SRC_FILES := $(call all-subdir-java-files) diff --git a/packages/overlays/IconShapeSquircleOverlay/Android.mk b/packages/overlays/IconShapeSquircleOverlay/Android.mk index fd3bfa06de83..34edc3b78b09 100644 --- a/packages/overlays/IconShapeSquircleOverlay/Android.mk +++ b/packages/overlays/IconShapeSquircleOverlay/Android.mk @@ -19,6 +19,7 @@ include $(CLEAR_VARS) LOCAL_RRO_THEME := IconShapeSquircle LOCAL_CERTIFICATE := platform +LOCAL_PRODUCT_MODULE := true LOCAL_SRC_FILES := $(call all-subdir-java-files) diff --git a/packages/overlays/IconShapeTeardropOverlay/Android.mk b/packages/overlays/IconShapeTeardropOverlay/Android.mk index ea43423f93ba..834a1c357c61 100644 --- a/packages/overlays/IconShapeTeardropOverlay/Android.mk +++ b/packages/overlays/IconShapeTeardropOverlay/Android.mk @@ -19,6 +19,7 @@ include $(CLEAR_VARS) LOCAL_RRO_THEME := IconShapeTeardrop LOCAL_CERTIFICATE := platform +LOCAL_PRODUCT_MODULE := true LOCAL_SRC_FILES := $(call all-subdir-java-files) diff --git a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java index fc7265db1be2..681a9941faea 100644 --- a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java +++ b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java @@ -38,8 +38,8 @@ import android.view.autofill.AutofillManager; import android.view.autofill.AutofillValue; import android.view.autofill.IAutoFillManagerClient; +import com.android.internal.infra.AbstractSinglePendingRequestRemoteService; import com.android.internal.os.IResultReceiver; -import com.android.server.infra.AbstractSinglePendingRequestRemoteService; final class RemoteAugmentedAutofillService extends AbstractSinglePendingRequestRemoteService<RemoteAugmentedAutofillService, diff --git a/services/autofill/java/com/android/server/autofill/RemoteFillService.java b/services/autofill/java/com/android/server/autofill/RemoteFillService.java index 417ea9c84393..e5529aff7c62 100644 --- a/services/autofill/java/com/android/server/autofill/RemoteFillService.java +++ b/services/autofill/java/com/android/server/autofill/RemoteFillService.java @@ -39,7 +39,7 @@ import android.service.autofill.SaveRequest; import android.text.format.DateUtils; import android.util.Slog; -import com.android.server.infra.AbstractSinglePendingRequestRemoteService; +import com.android.internal.infra.AbstractSinglePendingRequestRemoteService; final class RemoteFillService extends AbstractSinglePendingRequestRemoteService<RemoteFillService, IAutoFillService> { diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java index a533640b526b..a9f4e46395f3 100644 --- a/services/backup/java/com/android/server/backup/BackupManagerService.java +++ b/services/backup/java/com/android/server/backup/BackupManagerService.java @@ -117,7 +117,7 @@ public class BackupManagerService { * @param userId User id on which the backup operation is being requested. * @param message A message to include in the exception if it is thrown. */ - private void enforceCallingPermissionOnUserId(int userId, String message) { + private void enforceCallingPermissionOnUserId(@UserIdInt int userId, String message) { if (Binder.getCallingUserHandle().getIdentifier() != userId) { mContext.enforceCallingOrSelfPermission( Manifest.permission.INTERACT_ACROSS_USERS_FULL, message); @@ -146,7 +146,7 @@ public class BackupManagerService { protected void startServiceForUser(int userId) { UserBackupManagerService userBackupManagerService = UserBackupManagerService.createAndInitializeService( - mContext, mTrampoline, mBackupThread, mTransportWhitelist); + userId, mContext, mTrampoline, mBackupThread, mTransportWhitelist); startServiceForUser(userId, userBackupManagerService); } @@ -170,9 +170,14 @@ public class BackupManagerService { * @param userId The id of the user to retrieve its instance of {@link * UserBackupManagerService}. * @param caller A {@link String} identifying the caller for logging purposes. + * @throws SecurityException if {@code userId} is different from the calling user id and the + * caller does NOT have the android.permission.INTERACT_ACROSS_USERS_FULL permission. */ @Nullable - private UserBackupManagerService getServiceForUser(@UserIdInt int userId, String caller) { + @VisibleForTesting + UserBackupManagerService getServiceForUserIfCallerHasPermission( + @UserIdInt int userId, String caller) { + enforceCallingPermissionOnUserId(userId, caller); UserBackupManagerService userBackupManagerService = mServiceUsers.get(userId); if (userBackupManagerService == null) { Slog.w(TAG, "Called " + caller + " for unknown user: " + userId); @@ -196,9 +201,9 @@ public class BackupManagerService { * backup for their app {@code packageName}. Only used for apps participating in key-value * backup. */ - public void dataChanged(String packageName) { + public void dataChanged(@UserIdInt int userId, String packageName) { UserBackupManagerService userBackupManagerService = - getServiceForUser(UserHandle.USER_SYSTEM, "dataChanged()"); + getServiceForUserIfCallerHasPermission(userId, "dataChanged()"); if (userBackupManagerService != null) { userBackupManagerService.dataChanged(packageName); @@ -209,9 +214,9 @@ public class BackupManagerService { * Callback: a requested backup agent has been instantiated. This should only be called from the * {@link ActivityManager}. */ - public void agentConnected(String packageName, IBinder agentBinder) { + public void agentConnected(@UserIdInt int userId, String packageName, IBinder agentBinder) { UserBackupManagerService userBackupManagerService = - getServiceForUser(UserHandle.USER_SYSTEM, "agentConnected()"); + getServiceForUserIfCallerHasPermission(userId, "agentConnected()"); if (userBackupManagerService != null) { userBackupManagerService.agentConnected(packageName, agentBinder); @@ -222,9 +227,9 @@ public class BackupManagerService { * Callback: a backup agent has failed to come up, or has unexpectedly quit. This should only be * called from the {@link ActivityManager}. */ - public void agentDisconnected(String packageName) { + public void agentDisconnected(@UserIdInt int userId, String packageName) { UserBackupManagerService userBackupManagerService = - getServiceForUser(UserHandle.USER_SYSTEM, "agentDisconnected()"); + getServiceForUserIfCallerHasPermission(userId, "agentDisconnected()"); if (userBackupManagerService != null) { userBackupManagerService.agentDisconnected(packageName); @@ -235,9 +240,9 @@ public class BackupManagerService { * Used by a currently-active backup agent to notify the service that it has completed its given * outstanding asynchronous backup/restore operation. */ - public void opComplete(int token, long result) { + public void opComplete(@UserIdInt int userId, int token, long result) { UserBackupManagerService userBackupManagerService = - getServiceForUser(UserHandle.USER_SYSTEM, "opComplete()"); + getServiceForUserIfCallerHasPermission(userId, "opComplete()"); if (userBackupManagerService != null) { userBackupManagerService.opComplete(token, result); @@ -249,9 +254,10 @@ public class BackupManagerService { // --------------------------------------------- /** Run an initialize operation for the given transports {@code transportNames}. */ - public void initializeTransports(String[] transportNames, IBackupObserver observer) { + public void initializeTransports( + @UserIdInt int userId, String[] transportNames, IBackupObserver observer) { UserBackupManagerService userBackupManagerService = - getServiceForUser(UserHandle.USER_SYSTEM, "initializeTransports()"); + getServiceForUserIfCallerHasPermission(userId, "initializeTransports()"); if (userBackupManagerService != null) { userBackupManagerService.initializeTransports(transportNames, observer); @@ -262,9 +268,9 @@ public class BackupManagerService { * Clear the given package {@code packageName}'s backup data from the transport {@code * transportName}. */ - public void clearBackupData(String transportName, String packageName) { + public void clearBackupData(@UserIdInt int userId, String transportName, String packageName) { UserBackupManagerService userBackupManagerService = - getServiceForUser(UserHandle.USER_SYSTEM, "clearBackupData()"); + getServiceForUserIfCallerHasPermission(userId, "clearBackupData()"); if (userBackupManagerService != null) { userBackupManagerService.clearBackupData(transportName, packageName); @@ -273,9 +279,9 @@ public class BackupManagerService { /** Return the name of the currently active transport. */ @Nullable - public String getCurrentTransport() { + public String getCurrentTransport(@UserIdInt int userId) { UserBackupManagerService userBackupManagerService = - getServiceForUser(UserHandle.USER_SYSTEM, "getCurrentTransport()"); + getServiceForUserIfCallerHasPermission(userId, "getCurrentTransport()"); return userBackupManagerService == null ? null @@ -287,9 +293,9 @@ public class BackupManagerService { * null} if no transport selected or if the transport selected is not registered. */ @Nullable - public ComponentName getCurrentTransportComponent() { + public ComponentName getCurrentTransportComponent(@UserIdInt int userId) { UserBackupManagerService userBackupManagerService = - getServiceForUser(UserHandle.USER_SYSTEM, "getCurrentTransportComponent()"); + getServiceForUserIfCallerHasPermission(userId, "getCurrentTransportComponent()"); return userBackupManagerService == null ? null @@ -298,9 +304,9 @@ public class BackupManagerService { /** Report all known, available backup transports by name. */ @Nullable - public String[] listAllTransports() { + public String[] listAllTransports(@UserIdInt int userId) { UserBackupManagerService userBackupManagerService = - getServiceForUser(UserHandle.USER_SYSTEM, "listAllTransports()"); + getServiceForUserIfCallerHasPermission(userId, "listAllTransports()"); return userBackupManagerService == null ? null @@ -309,9 +315,9 @@ public class BackupManagerService { /** Report all known, available backup transports by {@link ComponentName}. */ @Nullable - public ComponentName[] listAllTransportComponents() { + public ComponentName[] listAllTransportComponents(@UserIdInt int userId) { UserBackupManagerService userBackupManagerService = - getServiceForUser(UserHandle.USER_SYSTEM, "listAllTransportComponents()"); + getServiceForUserIfCallerHasPermission(userId, "listAllTransportComponents()"); return userBackupManagerService == null ? null @@ -321,12 +327,14 @@ public class BackupManagerService { /** Report all system whitelisted transports. */ @Nullable public String[] getTransportWhitelist() { - UserBackupManagerService userBackupManagerService = - getServiceForUser(UserHandle.USER_SYSTEM, "getTransportWhitelist()"); - - return userBackupManagerService == null - ? null - : userBackupManagerService.getTransportWhitelist(); + // No permission check, intentionally. + String[] whitelistedTransports = new String[mTransportWhitelist.size()]; + int i = 0; + for (ComponentName component : mTransportWhitelist) { + whitelistedTransports[i] = component.flattenToShortString(); + i++; + } + return whitelistedTransports; } /** @@ -353,6 +361,7 @@ public class BackupManagerService { * {@code transportComponent} or if the caller does NOT have BACKUP permission. */ public void updateTransportAttributes( + @UserIdInt int userId, ComponentName transportComponent, String name, @Nullable Intent configurationIntent, @@ -360,7 +369,7 @@ public class BackupManagerService { @Nullable Intent dataManagementIntent, String dataManagementLabel) { UserBackupManagerService userBackupManagerService = - getServiceForUser(UserHandle.USER_SYSTEM, "updateTransportAttributes()"); + getServiceForUserIfCallerHasPermission(userId, "updateTransportAttributes()"); if (userBackupManagerService != null) { userBackupManagerService.updateTransportAttributes( @@ -381,9 +390,9 @@ public class BackupManagerService { */ @Deprecated @Nullable - public String selectBackupTransport(String transportName) { + public String selectBackupTransport(@UserIdInt int userId, String transportName) { UserBackupManagerService userBackupManagerService = - getServiceForUser(UserHandle.USER_SYSTEM, "selectBackupTransport()"); + getServiceForUserIfCallerHasPermission(userId, "selectBackupTransport()"); return userBackupManagerService == null ? null @@ -395,9 +404,11 @@ public class BackupManagerService { * with the result upon completion. */ public void selectBackupTransportAsync( - ComponentName transportComponent, ISelectBackupTransportCallback listener) { + @UserIdInt int userId, + ComponentName transportComponent, + ISelectBackupTransportCallback listener) { UserBackupManagerService userBackupManagerService = - getServiceForUser(UserHandle.USER_SYSTEM, "selectBackupTransportAsync()"); + getServiceForUserIfCallerHasPermission(userId, "selectBackupTransportAsync()"); if (userBackupManagerService != null) { userBackupManagerService.selectBackupTransportAsync(transportComponent, listener); @@ -410,9 +421,9 @@ public class BackupManagerService { * returns {@code null}. */ @Nullable - public Intent getConfigurationIntent(String transportName) { + public Intent getConfigurationIntent(@UserIdInt int userId, String transportName) { UserBackupManagerService userBackupManagerService = - getServiceForUser(UserHandle.USER_SYSTEM, "getConfigurationIntent()"); + getServiceForUserIfCallerHasPermission(userId, "getConfigurationIntent()"); return userBackupManagerService == null ? null @@ -429,9 +440,9 @@ public class BackupManagerService { * @return The current destination string or null if the transport is not registered. */ @Nullable - public String getDestinationString(String transportName) { + public String getDestinationString(@UserIdInt int userId, String transportName) { UserBackupManagerService userBackupManagerService = - getServiceForUser(UserHandle.USER_SYSTEM, "getDestinationString()"); + getServiceForUserIfCallerHasPermission(userId, "getDestinationString()"); return userBackupManagerService == null ? null @@ -440,9 +451,9 @@ public class BackupManagerService { /** Supply the manage-data intent for the given transport. */ @Nullable - public Intent getDataManagementIntent(String transportName) { + public Intent getDataManagementIntent(@UserIdInt int userId, String transportName) { UserBackupManagerService userBackupManagerService = - getServiceForUser(UserHandle.USER_SYSTEM, "getDataManagementIntent()"); + getServiceForUserIfCallerHasPermission(userId, "getDataManagementIntent()"); return userBackupManagerService == null ? null @@ -454,9 +465,9 @@ public class BackupManagerService { * transport. */ @Nullable - public String getDataManagementLabel(String transportName) { + public String getDataManagementLabel(@UserIdInt int userId, String transportName) { UserBackupManagerService userBackupManagerService = - getServiceForUser(UserHandle.USER_SYSTEM, "getDataManagementLabel()"); + getServiceForUserIfCallerHasPermission(userId, "getDataManagementLabel()"); return userBackupManagerService == null ? null @@ -469,9 +480,8 @@ public class BackupManagerService { /** Enable/disable the backup service. This is user-configurable via backup settings. */ public void setBackupEnabled(@UserIdInt int userId, boolean enable) { - enforceCallingPermissionOnUserId(userId, "setBackupEnabled"); UserBackupManagerService userBackupManagerService = - getServiceForUser(userId, "setBackupEnabled()"); + getServiceForUserIfCallerHasPermission(userId, "setBackupEnabled()"); if (userBackupManagerService != null) { userBackupManagerService.setBackupEnabled(enable); @@ -479,32 +489,21 @@ public class BackupManagerService { } /** Enable/disable automatic restore of app data at install time. */ - public void setAutoRestore(boolean autoRestore) { + public void setAutoRestore(@UserIdInt int userId, boolean autoRestore) { UserBackupManagerService userBackupManagerService = - getServiceForUser(UserHandle.USER_SYSTEM, "setAutoRestore()"); + getServiceForUserIfCallerHasPermission(userId, "setAutoRestore()"); if (userBackupManagerService != null) { userBackupManagerService.setAutoRestore(autoRestore); } } - /** Mark the backup service as having been provisioned (device has gone through SUW). */ - public void setBackupProvisioned(boolean provisioned) { - UserBackupManagerService userBackupManagerService = - getServiceForUser(UserHandle.USER_SYSTEM, "setBackupProvisioned()"); - - if (userBackupManagerService != null) { - userBackupManagerService.setBackupProvisioned(provisioned); - } - } - /** * Return {@code true} if the backup mechanism is currently enabled, else returns {@code false}. */ public boolean isBackupEnabled(@UserIdInt int userId) { - enforceCallingPermissionOnUserId(userId, "isBackupEnabled"); UserBackupManagerService userBackupManagerService = - getServiceForUser(userId, "isBackupEnabled()"); + getServiceForUserIfCallerHasPermission(userId, "isBackupEnabled()"); return userBackupManagerService != null && userBackupManagerService.isBackupEnabled(); } @@ -514,9 +513,9 @@ public class BackupManagerService { // --------------------------------------------- /** Checks if the given package {@code packageName} is eligible for backup. */ - public boolean isAppEligibleForBackup(String packageName) { + public boolean isAppEligibleForBackup(@UserIdInt int userId, String packageName) { UserBackupManagerService userBackupManagerService = - getServiceForUser(UserHandle.USER_SYSTEM, "isAppEligibleForBackup()"); + getServiceForUserIfCallerHasPermission(userId, "isAppEligibleForBackup()"); return userBackupManagerService != null && userBackupManagerService.isAppEligibleForBackup(packageName); @@ -526,9 +525,9 @@ public class BackupManagerService { * Returns from the inputted packages {@code packages}, the ones that are eligible for backup. */ @Nullable - public String[] filterAppsEligibleForBackup(String[] packages) { + public String[] filterAppsEligibleForBackup(@UserIdInt int userId, String[] packages) { UserBackupManagerService userBackupManagerService = - getServiceForUser(UserHandle.USER_SYSTEM, "filterAppsEligibleForBackup()"); + getServiceForUserIfCallerHasPermission(userId, "filterAppsEligibleForBackup()"); return userBackupManagerService == null ? null @@ -540,9 +539,8 @@ public class BackupManagerService { * they have pending updates. */ public void backupNow(@UserIdInt int userId) { - enforceCallingPermissionOnUserId(userId, "backupNow"); UserBackupManagerService userBackupManagerService = - getServiceForUser(userId, "backupNow()"); + getServiceForUserIfCallerHasPermission(userId, "backupNow()"); if (userBackupManagerService != null) { userBackupManagerService.backupNow(); @@ -559,9 +557,8 @@ public class BackupManagerService { IBackupObserver observer, IBackupManagerMonitor monitor, int flags) { - enforceCallingPermissionOnUserId(userId, "requestBackup"); UserBackupManagerService userBackupManagerService = - getServiceForUser(userId, "requestBackup()"); + getServiceForUserIfCallerHasPermission(userId, "requestBackup()"); return userBackupManagerService == null ? BackupManager.ERROR_BACKUP_NOT_ALLOWED @@ -570,9 +567,8 @@ public class BackupManagerService { /** Cancel all running backup operations. */ public void cancelBackups(@UserIdInt int userId) { - enforceCallingPermissionOnUserId(userId, "cancelBackups"); UserBackupManagerService userBackupManagerService = - getServiceForUser(userId, "cancelBackups()"); + getServiceForUserIfCallerHasPermission(userId, "cancelBackups()"); if (userBackupManagerService != null) { userBackupManagerService.cancelBackups(); @@ -589,7 +585,7 @@ public class BackupManagerService { */ public boolean beginFullBackup(FullBackupJob scheduledJob) { UserBackupManagerService userBackupManagerService = - getServiceForUser(UserHandle.USER_SYSTEM, "beginFullBackup()"); + getServiceForUserIfCallerHasPermission(UserHandle.USER_SYSTEM, "beginFullBackup()"); return userBackupManagerService != null && userBackupManagerService.beginFullBackup(scheduledJob); @@ -601,7 +597,7 @@ public class BackupManagerService { */ public void endFullBackup() { UserBackupManagerService userBackupManagerService = - getServiceForUser(UserHandle.USER_SYSTEM, "endFullBackup()"); + getServiceForUserIfCallerHasPermission(UserHandle.USER_SYSTEM, "endFullBackup()"); if (userBackupManagerService != null) { userBackupManagerService.endFullBackup(); @@ -611,9 +607,9 @@ public class BackupManagerService { /** * Run a full backup pass for the given packages {@code packageNames}. Used by 'adb shell bmgr'. */ - public void fullTransportBackup(String[] packageNames) { + public void fullTransportBackup(@UserIdInt int userId, String[] packageNames) { UserBackupManagerService userBackupManagerService = - getServiceForUser(UserHandle.USER_SYSTEM, "fullTransportBackup()"); + getServiceForUserIfCallerHasPermission(userId, "fullTransportBackup()"); if (userBackupManagerService != null) { userBackupManagerService.fullTransportBackup(packageNames); @@ -628,9 +624,9 @@ public class BackupManagerService { * Used to run a restore pass for an application that is being installed. This should only be * called from the {@link PackageManager}. */ - public void restoreAtInstall(String packageName, int token) { + public void restoreAtInstall(@UserIdInt int userId, String packageName, int token) { UserBackupManagerService userBackupManagerService = - getServiceForUser(UserHandle.USER_SYSTEM, "restoreAtInstall()"); + getServiceForUserIfCallerHasPermission(userId, "restoreAtInstall()"); if (userBackupManagerService != null) { userBackupManagerService.restoreAtInstall(packageName, token); @@ -642,9 +638,10 @@ public class BackupManagerService { * {@code transportName}. */ @Nullable - public IRestoreSession beginRestoreSession(String packageName, String transportName) { + public IRestoreSession beginRestoreSession( + @UserIdInt int userId, String packageName, String transportName) { UserBackupManagerService userBackupManagerService = - getServiceForUser(UserHandle.USER_SYSTEM, "beginRestoreSession()"); + getServiceForUserIfCallerHasPermission(userId, "beginRestoreSession()"); return userBackupManagerService == null ? null @@ -655,9 +652,9 @@ public class BackupManagerService { * Get the restore-set token for the best-available restore set for this {@code packageName}: * the active set if possible, else the ancestral one. Returns zero if none available. */ - public long getAvailableRestoreToken(String packageName) { + public long getAvailableRestoreToken(@UserIdInt int userId, String packageName) { UserBackupManagerService userBackupManagerService = - getServiceForUser(UserHandle.USER_SYSTEM, "getAvailableRestoreToken()"); + getServiceForUserIfCallerHasPermission(userId, "getAvailableRestoreToken()"); return userBackupManagerService == null ? 0 @@ -671,7 +668,8 @@ public class BackupManagerService { /** Sets the backup password used when running adb backup. */ public boolean setBackupPassword(String currentPassword, String newPassword) { UserBackupManagerService userBackupManagerService = - getServiceForUser(UserHandle.USER_SYSTEM, "setBackupPassword()"); + getServiceForUserIfCallerHasPermission( + UserHandle.USER_SYSTEM, "setBackupPassword()"); return userBackupManagerService != null && userBackupManagerService.setBackupPassword(currentPassword, newPassword); @@ -680,7 +678,8 @@ public class BackupManagerService { /** Returns {@code true} if adb backup was run with a password, else returns {@code false}. */ public boolean hasBackupPassword() { UserBackupManagerService userBackupManagerService = - getServiceForUser(UserHandle.USER_SYSTEM, "hasBackupPassword()"); + getServiceForUserIfCallerHasPermission( + UserHandle.USER_SYSTEM, "hasBackupPassword()"); return userBackupManagerService != null && userBackupManagerService.hasBackupPassword(); } @@ -703,9 +702,8 @@ public class BackupManagerService { boolean doCompress, boolean doKeyValue, String[] packageNames) { - enforceCallingPermissionOnUserId(userId, "adbBackup"); UserBackupManagerService userBackupManagerService = - getServiceForUser(userId, "adbBackup()"); + getServiceForUserIfCallerHasPermission(userId, "adbBackup()"); if (userBackupManagerService != null) { userBackupManagerService.adbBackup( @@ -728,9 +726,8 @@ public class BackupManagerService { * requires on-screen confirmation by the user. */ public void adbRestore(@UserIdInt int userId, ParcelFileDescriptor fd) { - enforceCallingPermissionOnUserId(userId, "setBackupEnabled"); UserBackupManagerService userBackupManagerService = - getServiceForUser(userId, "adbRestore()"); + getServiceForUserIfCallerHasPermission(userId, "adbRestore()"); if (userBackupManagerService != null) { userBackupManagerService.adbRestore(fd); @@ -742,13 +739,14 @@ public class BackupManagerService { * to require a user-facing disclosure about the operation. */ public void acknowledgeAdbBackupOrRestore( + @UserIdInt int userId, int token, boolean allow, String currentPassword, String encryptionPassword, IFullBackupRestoreObserver observer) { UserBackupManagerService userBackupManagerService = - getServiceForUser(UserHandle.USER_SYSTEM, "acknowledgeAdbBackupOrRestore()"); + getServiceForUserIfCallerHasPermission(userId, "acknowledgeAdbBackupOrRestore()"); if (userBackupManagerService != null) { userBackupManagerService.acknowledgeAdbBackupOrRestore( @@ -763,7 +761,7 @@ public class BackupManagerService { /** Prints service state for 'dumpsys backup'. */ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { UserBackupManagerService userBackupManagerService = - getServiceForUser(UserHandle.USER_SYSTEM, "dump()"); + getServiceForUserIfCallerHasPermission(UserHandle.USER_SYSTEM, "dump()"); if (userBackupManagerService != null) { userBackupManagerService.dump(fd, pw, args); diff --git a/services/backup/java/com/android/server/backup/Trampoline.java b/services/backup/java/com/android/server/backup/Trampoline.java index 59b72f9262b6..d40368025c0c 100644 --- a/services/backup/java/com/android/server/backup/Trampoline.java +++ b/services/backup/java/com/android/server/backup/Trampoline.java @@ -284,53 +284,81 @@ public class Trampoline extends IBackupManager.Stub { } @Override + public void dataChangedForUser(int userId, String packageName) throws RemoteException { + BackupManagerService svc = mService; + if (svc != null) { + svc.dataChanged(userId, packageName); + } + } + + @Override public void dataChanged(String packageName) throws RemoteException { + dataChangedForUser(binderGetCallingUserId(), packageName); + } + + @Override + public void initializeTransportsForUser( + int userId, String[] transportNames, IBackupObserver observer) throws RemoteException { BackupManagerService svc = mService; if (svc != null) { - svc.dataChanged(packageName); + svc.initializeTransports(userId, transportNames, observer); } } @Override - public void initializeTransports(String[] transportNames, IBackupObserver observer) + public void clearBackupDataForUser(int userId, String transportName, String packageName) throws RemoteException { BackupManagerService svc = mService; if (svc != null) { - svc.initializeTransports(transportNames, observer); + svc.clearBackupData(userId, transportName, packageName); } } @Override public void clearBackupData(String transportName, String packageName) throws RemoteException { + clearBackupDataForUser(binderGetCallingUserId(), transportName, packageName); + } + + @Override + public void agentConnectedForUser(int userId, String packageName, IBinder agent) + throws RemoteException { BackupManagerService svc = mService; if (svc != null) { - svc.clearBackupData(transportName, packageName); + svc.agentConnected(userId, packageName, agent); } } @Override public void agentConnected(String packageName, IBinder agent) throws RemoteException { + agentConnectedForUser(binderGetCallingUserId(), packageName, agent); + } + + @Override + public void agentDisconnectedForUser(int userId, String packageName) throws RemoteException { BackupManagerService svc = mService; if (svc != null) { - svc.agentConnected(packageName, agent); + svc.agentDisconnected(userId, packageName); } } @Override public void agentDisconnected(String packageName) throws RemoteException { + agentDisconnectedForUser(binderGetCallingUserId(), packageName); + } + + @Override + public void restoreAtInstallForUser(int userId, String packageName, int token) + throws RemoteException { BackupManagerService svc = mService; if (svc != null) { - svc.agentDisconnected(packageName); + svc.restoreAtInstall(userId, packageName, token); } } @Override public void restoreAtInstall(String packageName, int token) throws RemoteException { - BackupManagerService svc = mService; - if (svc != null) { - svc.restoreAtInstall(packageName, token); - } + restoreAtInstallForUser(binderGetCallingUserId(), packageName, token); } @Override @@ -348,19 +376,23 @@ public class Trampoline extends IBackupManager.Stub { } @Override - public void setAutoRestore(boolean doAutoRestore) throws RemoteException { + public void setAutoRestoreForUser(int userId, boolean doAutoRestore) throws RemoteException { BackupManagerService svc = mService; if (svc != null) { - svc.setAutoRestore(doAutoRestore); + svc.setAutoRestore(userId, doAutoRestore); } } @Override + public void setAutoRestore(boolean doAutoRestore) throws RemoteException { + setAutoRestoreForUser(binderGetCallingUserId(), doAutoRestore); + } + + @Override public void setBackupProvisioned(boolean isProvisioned) throws RemoteException { - BackupManagerService svc = mService; - if (svc != null) { - svc.setBackupProvisioned(isProvisioned); - } + /* + * This is now a no-op; provisioning is simply the device's own setup state. + */ } @Override @@ -411,10 +443,11 @@ public class Trampoline extends IBackupManager.Stub { } @Override - public void fullTransportBackup(String[] packageNames) throws RemoteException { + public void fullTransportBackupForUser(int userId, String[] packageNames) + throws RemoteException { BackupManagerService svc = mService; if (svc != null) { - svc.fullTransportBackup(packageNames); + svc.fullTransportBackup(userId, packageNames); } } @@ -427,20 +460,40 @@ public class Trampoline extends IBackupManager.Stub { } @Override - public void acknowledgeFullBackupOrRestore(int token, boolean allow, String curPassword, - String encryptionPassword, IFullBackupRestoreObserver observer) - throws RemoteException { + public void acknowledgeFullBackupOrRestoreForUser( + int userId, + int token, + boolean allow, + String curPassword, + String encryptionPassword, + IFullBackupRestoreObserver observer) + throws RemoteException { BackupManagerService svc = mService; if (svc != null) { - svc.acknowledgeAdbBackupOrRestore(token, allow, + svc.acknowledgeAdbBackupOrRestore(userId, token, allow, curPassword, encryptionPassword, observer); } } @Override - public String getCurrentTransport() throws RemoteException { + public void acknowledgeFullBackupOrRestore(int token, boolean allow, String curPassword, + String encryptionPassword, IFullBackupRestoreObserver observer) + throws RemoteException { BackupManagerService svc = mService; - return (svc != null) ? svc.getCurrentTransport() : null; + acknowledgeFullBackupOrRestoreForUser( + binderGetCallingUserId(), token, allow, curPassword, encryptionPassword, observer); + } + + + @Override + public String getCurrentTransportForUser(int userId) throws RemoteException { + BackupManagerService svc = mService; + return (svc != null) ? svc.getCurrentTransport(userId) : null; + } + + @Override + public String getCurrentTransport() throws RemoteException { + return getCurrentTransportForUser(binderGetCallingUserId()); } /** @@ -449,21 +502,26 @@ public class Trampoline extends IBackupManager.Stub { */ @Override @Nullable - public ComponentName getCurrentTransportComponent() { + public ComponentName getCurrentTransportComponentForUser(int userId) { BackupManagerService svc = mService; - return (svc != null) ? svc.getCurrentTransportComponent() : null; + return (svc != null) ? svc.getCurrentTransportComponent(userId) : null; } @Override - public String[] listAllTransports() throws RemoteException { + public String[] listAllTransportsForUser(int userId) throws RemoteException { BackupManagerService svc = mService; - return (svc != null) ? svc.listAllTransports() : null; + return (svc != null) ? svc.listAllTransports(userId) : null; } @Override - public ComponentName[] listAllTransportComponents() throws RemoteException { + public String[] listAllTransports() throws RemoteException { + return listAllTransportsForUser(binderGetCallingUserId()); + } + + @Override + public ComponentName[] listAllTransportComponentsForUser(int userId) throws RemoteException { BackupManagerService svc = mService; - return (svc != null) ? svc.listAllTransportComponents() : null; + return (svc != null) ? svc.listAllTransportComponents(userId) : null; } @Override @@ -473,7 +531,8 @@ public class Trampoline extends IBackupManager.Stub { } @Override - public void updateTransportAttributes( + public void updateTransportAttributesForUser( + int userId, ComponentName transportComponent, String name, @Nullable Intent configurationIntent, @@ -483,6 +542,7 @@ public class Trampoline extends IBackupManager.Stub { BackupManagerService svc = mService; if (svc != null) { svc.updateTransportAttributes( + userId, transportComponent, name, configurationIntent, @@ -493,17 +553,23 @@ public class Trampoline extends IBackupManager.Stub { } @Override - public String selectBackupTransport(String transport) throws RemoteException { + public String selectBackupTransportForUser(int userId, String transport) + throws RemoteException { BackupManagerService svc = mService; - return (svc != null) ? svc.selectBackupTransport(transport) : null; + return (svc != null) ? svc.selectBackupTransport(userId, transport) : null; + } + + @Override + public String selectBackupTransport(String transport) throws RemoteException { + return selectBackupTransportForUser(binderGetCallingUserId(), transport); } @Override - public void selectBackupTransportAsync(ComponentName transport, + public void selectBackupTransportAsyncForUser(int userId, ComponentName transport, ISelectBackupTransportCallback listener) throws RemoteException { BackupManagerService svc = mService; if (svc != null) { - svc.selectBackupTransportAsync(transport, listener); + svc.selectBackupTransportAsync(userId, transport, listener); } else { if (listener != null) { try { @@ -516,60 +582,86 @@ public class Trampoline extends IBackupManager.Stub { } @Override - public Intent getConfigurationIntent(String transport) throws RemoteException { + public Intent getConfigurationIntentForUser(int userId, String transport) + throws RemoteException { BackupManagerService svc = mService; - return (svc != null) ? svc.getConfigurationIntent(transport) : null; + return (svc != null) ? svc.getConfigurationIntent(userId, transport) : null; } @Override - public String getDestinationString(String transport) throws RemoteException { + public Intent getConfigurationIntent(String transport) + throws RemoteException { + return getConfigurationIntentForUser(binderGetCallingUserId(), transport); + } + + @Override + public String getDestinationStringForUser(int userId, String transport) throws RemoteException { BackupManagerService svc = mService; - return (svc != null) ? svc.getDestinationString(transport) : null; + return (svc != null) ? svc.getDestinationString(userId, transport) : null; + } + + @Override + public String getDestinationString(String transport) throws RemoteException { + return getDestinationStringForUser(binderGetCallingUserId(), transport); } @Override - public Intent getDataManagementIntent(String transport) throws RemoteException { + public Intent getDataManagementIntentForUser(int userId, String transport) + throws RemoteException { BackupManagerService svc = mService; - return (svc != null) ? svc.getDataManagementIntent(transport) : null; + return (svc != null) ? svc.getDataManagementIntent(userId, transport) : null; + } + + @Override + public Intent getDataManagementIntent(String transport) + throws RemoteException { + return getDataManagementIntentForUser(binderGetCallingUserId(), transport); } @Override - public String getDataManagementLabel(String transport) throws RemoteException { + public String getDataManagementLabelForUser(int userId, String transport) + throws RemoteException { BackupManagerService svc = mService; - return (svc != null) ? svc.getDataManagementLabel(transport) : null; + return (svc != null) ? svc.getDataManagementLabel(userId, transport) : null; } @Override - public IRestoreSession beginRestoreSession(String packageName, String transportID) + public String getDataManagementLabel(String transport) throws RemoteException { + return getDataManagementLabelForUser(binderGetCallingUserId(), transport); + } + + @Override + public IRestoreSession beginRestoreSessionForUser( + int userId, String packageName, String transportID) throws RemoteException { BackupManagerService svc = mService; - return (svc != null) ? svc.beginRestoreSession(packageName, transportID) : null; + return (svc != null) ? svc.beginRestoreSession(userId, packageName, transportID) : null; } @Override public void opComplete(int token, long result) throws RemoteException { BackupManagerService svc = mService; if (svc != null) { - svc.opComplete(token, result); + svc.opComplete(binderGetCallingUserId(), token, result); } } @Override - public long getAvailableRestoreToken(String packageName) { + public long getAvailableRestoreTokenForUser(int userId, String packageName) { BackupManagerService svc = mService; - return (svc != null) ? svc.getAvailableRestoreToken(packageName) : 0; + return (svc != null) ? svc.getAvailableRestoreToken(userId, packageName) : 0; } @Override - public boolean isAppEligibleForBackup(String packageName) { + public boolean isAppEligibleForBackupForUser(int userId, String packageName) { BackupManagerService svc = mService; - return (svc != null) ? svc.isAppEligibleForBackup(packageName) : false; + return (svc != null) ? svc.isAppEligibleForBackup(userId, packageName) : false; } @Override - public String[] filterAppsEligibleForBackup(String[] packages) { + public String[] filterAppsEligibleForBackupForUser(int userId, String[] packages) { BackupManagerService svc = mService; - return (svc != null) ? svc.filterAppsEligibleForBackup(packages) : null; + return (svc != null) ? svc.filterAppsEligibleForBackup(userId, packages) : null; } @Override diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java index 796ef406d333..2e414438c6ff 100644 --- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java +++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java @@ -251,6 +251,7 @@ public class UserBackupManagerService { private static final long BUSY_BACKOFF_MIN_MILLIS = 1000 * 60 * 60; // one hour private static final int BUSY_BACKOFF_FUZZ = 1000 * 60 * 60 * 2; // two hours + private final @UserIdInt int mUserId; private final BackupAgentTimeoutParameters mAgentTimeoutParameters; private final TransportManager mTransportManager; @@ -372,10 +373,11 @@ public class UserBackupManagerService { * Creates an instance of {@link UserBackupManagerService} and initializes state for it. This * includes setting up the directories where we keep our bookkeeping and transport management. * - * @see #createAndInitializeService(Context, Trampoline, HandlerThread, File, File, + * @see #createAndInitializeService(int, Context, Trampoline, HandlerThread, File, File, * TransportManager) */ static UserBackupManagerService createAndInitializeService( + @UserIdInt int userId, Context context, Trampoline trampoline, HandlerThread backupThread, @@ -399,12 +401,13 @@ public class UserBackupManagerService { File dataDir = new File(Environment.getDownloadCacheDirectory(), BACKUP_STAGING_DIR); return createAndInitializeService( - context, trampoline, backupThread, baseStateDir, dataDir, transportManager); + userId, context, trampoline, backupThread, baseStateDir, dataDir, transportManager); } /** * Creates an instance of {@link UserBackupManagerService}. * + * @param userId The user which this service is for. * @param context The system server context. * @param trampoline A reference to the proxy to {@link BackupManagerService}. * @param backupThread The thread running backup/restore operations for the user. @@ -415,6 +418,7 @@ public class UserBackupManagerService { */ @VisibleForTesting public static UserBackupManagerService createAndInitializeService( + @UserIdInt int userId, Context context, Trampoline trampoline, HandlerThread backupThread, @@ -422,16 +426,18 @@ public class UserBackupManagerService { File dataDir, TransportManager transportManager) { return new UserBackupManagerService( - context, trampoline, backupThread, baseStateDir, dataDir, transportManager); + userId, context, trampoline, backupThread, baseStateDir, dataDir, transportManager); } private UserBackupManagerService( + @UserIdInt int userId, Context context, Trampoline parent, HandlerThread backupThread, File baseStateDir, File dataDir, TransportManager transportManager) { + mUserId = userId; mContext = checkNotNull(context, "context cannot be null"); mPackageManager = context.getPackageManager(); mPackageManagerBinder = AppGlobals.getPackageManager(); @@ -2805,15 +2811,6 @@ public class UserBackupManagerService { } } - /** Mark the backup service as having been provisioned. */ - public void setBackupProvisioned(boolean available) { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, - "setBackupProvisioned"); - /* - * This is now a no-op; provisioning is simply the device's own setup state. - */ - } - /** Report whether the backup mechanism is currently enabled. */ public boolean isBackupEnabled() { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, @@ -2863,19 +2860,6 @@ public class UserBackupManagerService { return mTransportManager.getRegisteredTransportComponents(); } - /** Report all system whitelisted transports. */ - public String[] getTransportWhitelist() { - // No permission check, intentionally. - Set<ComponentName> whitelistedComponents = mTransportManager.getTransportWhitelist(); - String[] whitelistedTransports = new String[whitelistedComponents.size()]; - int i = 0; - for (ComponentName component : whitelistedComponents) { - whitelistedTransports[i] = component.flattenToShortString(); - i++; - } - return whitelistedTransports; - } - /** * Update the attributes of the transport identified by {@code transportComponent}. If the * specified transport has not been bound at least once (for registration), this call will be diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java index 10e713d9dabe..e8820ae4e32a 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java @@ -25,6 +25,7 @@ import android.annotation.UserIdInt; import android.app.ActivityManagerInternal; import android.content.ComponentName; import android.content.Context; +import android.os.Binder; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; @@ -34,7 +35,6 @@ import android.os.UserHandle; import android.os.UserManager; import android.util.Slog; import android.view.contentcapture.ContentCaptureContext; -import android.view.contentcapture.ContentCaptureEvent; import android.view.contentcapture.IContentCaptureManager; import com.android.internal.annotations.GuardedBy; @@ -47,7 +47,6 @@ import com.android.server.infra.AbstractMasterSystemService; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.List; /** * A service used to observe the contents of the screen. @@ -182,30 +181,18 @@ public final class ContentCaptureManagerService extends synchronized (mLock) { final ContentCapturePerUserService service = getServiceForUserLocked(userId); service.startSessionLocked(activityToken, componentName, taskId, displayId, - sessionId, clientContext, flags, mAllowInstantService, result); + sessionId, Binder.getCallingUid(), clientContext, flags, + mAllowInstantService, result); } } @Override - public void sendEvents(@UserIdInt int userId, @NonNull String sessionId, - @NonNull List<ContentCaptureEvent> events) { + public void finishSession(@UserIdInt int userId, @NonNull String sessionId) { Preconditions.checkNotNull(sessionId); - Preconditions.checkNotNull(events); synchronized (mLock) { final ContentCapturePerUserService service = getServiceForUserLocked(userId); - service.sendEventsLocked(sessionId, events); - } - } - - @Override - public void finishSession(@UserIdInt int userId, @NonNull String sessionId, - @Nullable List<ContentCaptureEvent> events) { - Preconditions.checkNotNull(sessionId); - - synchronized (mLock) { - final ContentCapturePerUserService service = getServiceForUserLocked(userId); - service.finishSessionLocked(sessionId, events); + service.finishSessionLocked(sessionId); } } diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java index 81309122d0ca..f21b0d810411 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java @@ -16,6 +16,8 @@ package com.android.server.contentcapture; +import static android.service.contentcapture.ContentCaptureService.setClientState; + import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_CONTENT; import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_DATA; import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_STRUCTURE; @@ -38,7 +40,6 @@ import android.service.contentcapture.SnapshotData; import android.util.ArrayMap; import android.util.Slog; import android.view.contentcapture.ContentCaptureContext; -import android.view.contentcapture.ContentCaptureEvent; import android.view.contentcapture.ContentCaptureSession; import com.android.internal.annotations.GuardedBy; @@ -48,7 +49,6 @@ import com.android.server.infra.FrameworkResourcesServiceNameResolver; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.List; /** * Per-user instance of {@link ContentCaptureManagerService}. @@ -114,10 +114,10 @@ final class ContentCapturePerUserService @GuardedBy("mLock") public void startSessionLocked(@NonNull IBinder activityToken, @NonNull ComponentName componentName, int taskId, int displayId, - @NonNull String sessionId, @Nullable ContentCaptureContext clientContext, - int flags, boolean bindInstantServiceAllowed, @NonNull IResultReceiver resultReceiver) { + @NonNull String sessionId, int uid, @Nullable ContentCaptureContext clientContext, + int flags, boolean bindInstantServiceAllowed, @NonNull IResultReceiver clientReceiver) { if (!isEnabledLocked()) { - sendToClient(resultReceiver, ContentCaptureSession.STATE_DISABLED); + setClientState(clientReceiver, ContentCaptureSession.STATE_DISABLED, /* binder=*/ null); return; } final ComponentName serviceComponentName = getServiceComponentName(); @@ -131,35 +131,30 @@ final class ContentCapturePerUserService return; } - ContentCaptureServerSession session = mSessions.get(sessionId); - if (session != null) { - if (mMaster.debug) { - Slog.d(TAG, "startSession(): reusing session " + sessionId + " for " - + componentName); - } - // TODO(b/111276913): check if local ids match and decide what to do if they don't - // TODO(b/111276913): should we call session.notifySessionStartedLocked() again?? - // if not, move notifySessionStartedLocked() into session constructor - sendToClient(resultReceiver, ContentCaptureSession.STATE_ACTIVE); + final ContentCaptureServerSession existingSession = mSessions.get(sessionId); + if (existingSession != null) { + Slog.w(TAG, "startSession(id=" + existingSession + ", token=" + activityToken + + ": ignoring because it already exists for " + existingSession.mActivityToken); + setClientState(clientReceiver, ContentCaptureSession.STATE_DISABLED_DUPLICATED_ID, + /* binder=*/ null); return; } - session = new ContentCaptureServerSession(getContext(), mUserId, mLock, activityToken, - this, serviceComponentName, componentName, taskId, displayId, sessionId, - clientContext, flags, bindInstantServiceAllowed, mMaster.verbose); + final ContentCaptureServerSession newSession = new ContentCaptureServerSession(getContext(), + mUserId, mLock, activityToken, this, serviceComponentName, componentName, taskId, + displayId, sessionId, uid, clientContext, flags, bindInstantServiceAllowed, + mMaster.verbose); if (mMaster.verbose) { - Slog.v(TAG, "startSession(): new session for " + componentName + " and id " - + sessionId); + Slog.v(TAG, "startSession(): new session for " + + ComponentName.flattenToShortString(componentName) + " and id " + sessionId); } - mSessions.put(sessionId, session); - session.notifySessionStartedLocked(); - sendToClient(resultReceiver, ContentCaptureSession.STATE_ACTIVE); + mSessions.put(sessionId, newSession); + newSession.notifySessionStartedLocked(clientReceiver); } // TODO(b/111276913): log metrics @GuardedBy("mLock") - public void finishSessionLocked(@NonNull String sessionId, - @Nullable List<ContentCaptureEvent> events) { + public void finishSessionLocked(@NonNull String sessionId) { if (!isEnabledLocked()) { return; } @@ -171,41 +166,8 @@ final class ContentCapturePerUserService } return; } - if (events != null && !events.isEmpty()) { - // TODO(b/111276913): for now we're sending the events and the onDestroy() in 2 separate - // calls because it's not clear yet whether we'll change the manager to send events - // to the service directly (i.e., without passing through system server). Once we - // decide, we might need to split IContentCaptureManager.onSessionLifecycle() in 2 - // methods, one for start and another for finish (and passing the events to finish), - // otherwise the service might receive the 2 calls out of order. - session.sendEventsLocked(events); - } - if (mMaster.verbose) { - Slog.v(TAG, "finishSession(" + (events == null ? 0 : events.size()) + " events): " - + session); - } - session.removeSelfLocked(true); - } - - // TODO(b/111276913): need to figure out why some events are sent before session is started; - // probably because ContentCaptureManager is not buffering them until it gets the session back - @GuardedBy("mLock") - public void sendEventsLocked(@NonNull String sessionId, - @NonNull List<ContentCaptureEvent> events) { - if (!isEnabledLocked()) { - return; - } - final ContentCaptureServerSession session = mSessions.get(sessionId); - if (session == null) { - if (mMaster.verbose) { - Slog.v(TAG, "sendEvents(): no session for " + sessionId); - } - return; - } - if (mMaster.verbose) { - Slog.v(TAG, "sendEvents(): id=" + sessionId + ", events=" + events.size()); - } - session.sendEventsLocked(events); + if (mMaster.verbose) Slog.v(TAG, "finishSession(): id=" + sessionId); + session.removeSelfLocked(/* notifyRemoteService= */ true); } @GuardedBy("mLock") @@ -308,12 +270,4 @@ final class ContentCapturePerUserService } return null; } - - private static void sendToClient(@NonNull IResultReceiver resultReceiver, int value) { - try { - resultReceiver.send(value, null); - } catch (RemoteException e) { - Slog.w(TAG, "Error async reporting result to client: " + e); - } - } } diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java index 181a2daa2467..ba98b95082f7 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java @@ -24,15 +24,14 @@ import android.service.contentcapture.ContentCaptureService; import android.service.contentcapture.SnapshotData; import android.util.Slog; import android.view.contentcapture.ContentCaptureContext; -import android.view.contentcapture.ContentCaptureEvent; import android.view.contentcapture.ContentCaptureSessionId; import com.android.internal.annotations.GuardedBy; +import com.android.internal.os.IResultReceiver; import com.android.internal.util.Preconditions; import com.android.server.contentcapture.RemoteContentCaptureService.ContentCaptureServiceCallbacks; import java.io.PrintWriter; -import java.util.List; final class ContentCaptureServerSession implements ContentCaptureServiceCallbacks { @@ -43,18 +42,28 @@ final class ContentCaptureServerSession implements ContentCaptureServiceCallback private final ContentCapturePerUserService mService; private final RemoteContentCaptureService mRemoteService; private final ContentCaptureContext mContentCaptureContext; + + /** + * Canonical session id. + */ private final String mId; + /** + * UID of the app whose contents is being captured. + */ + private final int mUid; + ContentCaptureServerSession(@NonNull Context context, int userId, @NonNull Object lock, @NonNull IBinder activityToken, @NonNull ContentCapturePerUserService service, @NonNull ComponentName serviceComponentName, @NonNull ComponentName appComponentName, - int taskId, int displayId, @NonNull String sessionId, + int taskId, int displayId, @NonNull String sessionId, int uid, @Nullable ContentCaptureContext clientContext, int flags, boolean bindInstantServiceAllowed, boolean verbose) { mLock = lock; mActivityToken = activityToken; mService = service; mId = Preconditions.checkNotNull(sessionId); + mUid = uid; mRemoteService = new RemoteContentCaptureService(context, ContentCaptureService.SERVICE_INTERFACE, serviceComponentName, userId, this, bindInstantServiceAllowed, verbose); @@ -73,15 +82,8 @@ final class ContentCaptureServerSession implements ContentCaptureServiceCallback * Notifies the {@link ContentCaptureService} that the service started. */ @GuardedBy("mLock") - public void notifySessionStartedLocked() { - mRemoteService.onSessionLifecycleRequest(mContentCaptureContext, mId); - } - - /** - * Notifies the {@link ContentCaptureService} of a batch of events. - */ - public void sendEventsLocked(@NonNull List<ContentCaptureEvent> events) { - mRemoteService.onContentCaptureEventsRequest(mId, events); + public void notifySessionStartedLocked(@NonNull IResultReceiver clientReceiver) { + mRemoteService.onSessionStarted(mContentCaptureContext, mId, mUid, clientReceiver); } /** @@ -118,11 +120,11 @@ final class ContentCaptureServerSession implements ContentCaptureServiceCallback @GuardedBy("mLock") public void destroyLocked(boolean notifyRemoteService) { if (mService.isVerbose()) { - Slog.v(TAG, "destroyLocked(notifyRemoteService=" + notifyRemoteService + ")"); + Slog.v(TAG, "destroy(notifyRemoteService=" + notifyRemoteService + ")"); } // TODO(b/111276913): must call client to set session as FINISHED_BY_SERVER if (notifyRemoteService) { - mRemoteService.onSessionLifecycleRequest(/* context= */ null, mId); + mRemoteService.onSessionFinished(mId); } } @@ -133,13 +135,14 @@ final class ContentCaptureServerSession implements ContentCaptureServiceCallback Slog.d(TAG, "onServiceDied() for " + mId); } synchronized (mLock) { - removeSelfLocked(/* notifyRemoteService= */ false); + removeSelfLocked(/* notifyRemoteService= */ true); } } @GuardedBy("mLock") public void dumpLocked(@NonNull String prefix, @NonNull PrintWriter pw) { pw.print(prefix); pw.print("id: "); pw.print(mId); pw.println(); + pw.print(prefix); pw.print("uid: "); pw.print(mUid); pw.println(); pw.print(prefix); pw.print("context: "); mContentCaptureContext.dump(pw); pw.println(); pw.print(prefix); pw.print("activity token: "); pw.println(mActivityToken); pw.print(prefix); pw.print("has autofill callback: "); diff --git a/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java b/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java index b4edf7e60bc4..942ee11aa481 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java +++ b/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java @@ -20,16 +20,13 @@ import android.annotation.Nullable; import android.content.ComponentName; import android.content.Context; import android.os.IBinder; -import android.service.contentcapture.ContentCaptureEventsRequest; import android.service.contentcapture.IContentCaptureService; import android.service.contentcapture.SnapshotData; import android.text.format.DateUtils; import android.view.contentcapture.ContentCaptureContext; -import android.view.contentcapture.ContentCaptureEvent; -import com.android.server.infra.AbstractMultiplePendingRequestsRemoteService; - -import java.util.List; +import com.android.internal.infra.AbstractMultiplePendingRequestsRemoteService; +import com.android.internal.os.IResultReceiver; final class RemoteContentCaptureService extends AbstractMultiplePendingRequestsRemoteService<RemoteContentCaptureService, @@ -67,21 +64,19 @@ final class RemoteContentCaptureService /** * Called by {@link ContentCaptureServerSession} to generate a call to the - * {@link RemoteContentCaptureService} to indicate the session was created (when {@code context} - * is not {@code null} or destroyed (when {@code context} is {@code null}). + * {@link RemoteContentCaptureService} to indicate the session was created. */ - public void onSessionLifecycleRequest(@Nullable ContentCaptureContext context, - @NonNull String sessionId) { - scheduleAsyncRequest((s) -> s.onSessionLifecycle(context, sessionId)); + public void onSessionStarted(@Nullable ContentCaptureContext context, + @NonNull String sessionId, int uid, @NonNull IResultReceiver clientReceiver) { + scheduleAsyncRequest((s) -> s.onSessionStarted(context, sessionId, uid, clientReceiver)); } /** - * Called by {@link ContentCaptureServerSession} to send a batch of events to the service. + * Called by {@link ContentCaptureServerSession} to generate a call to the + * {@link RemoteContentCaptureService} to indicate the session was finished. */ - public void onContentCaptureEventsRequest(@NonNull String sessionId, - @NonNull List<ContentCaptureEvent> events) { - scheduleAsyncRequest((s) -> s.onContentCaptureEventsRequest(sessionId, - new ContentCaptureEventsRequest(events))); + public void onSessionFinished(@NonNull String sessionId) { + scheduleAsyncRequest((s) -> s.onSessionFinished(sessionId)); } /** diff --git a/services/core/java/com/android/server/AppOpsService.java b/services/core/java/com/android/server/AppOpsService.java index 8d912fadf6d1..f0ec69f488b1 100644 --- a/services/core/java/com/android/server/AppOpsService.java +++ b/services/core/java/com/android/server/AppOpsService.java @@ -86,6 +86,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IAppOpsActiveCallback; import com.android.internal.app.IAppOpsCallback; +import com.android.internal.app.IAppOpsNotedCallback; import com.android.internal.app.IAppOpsService; import com.android.internal.os.Zygote; import com.android.internal.util.ArrayUtils; @@ -456,6 +457,7 @@ public class AppOpsService extends IAppOpsService.Stub { final ArrayMap<String, ArraySet<ModeCallback>> mPackageModeWatchers = new ArrayMap<>(); final ArrayMap<IBinder, ModeCallback> mModeWatchers = new ArrayMap<>(); final ArrayMap<IBinder, SparseArray<ActiveCallback>> mActiveWatchers = new ArrayMap<>(); + final ArrayMap<IBinder, SparseArray<NotedCallback>> mNotedWatchers = new ArrayMap<>(); final SparseArray<SparseArray<Restriction>> mAudioRestrictions = new SparseArray<>(); final class ModeCallback implements DeathRecipient { @@ -475,6 +477,7 @@ public class AppOpsService extends IAppOpsService.Stub { try { mCallback.asBinder().linkToDeath(this, 0); } catch (RemoteException e) { + /*ignored*/ } } @@ -524,6 +527,7 @@ public class AppOpsService extends IAppOpsService.Stub { try { mCallback.asBinder().linkToDeath(this, 0); } catch (RemoteException e) { + /*ignored*/ } } @@ -552,6 +556,50 @@ public class AppOpsService extends IAppOpsService.Stub { } } + final class NotedCallback implements DeathRecipient { + final IAppOpsNotedCallback mCallback; + final int mWatchingUid; + final int mCallingUid; + final int mCallingPid; + + NotedCallback(IAppOpsNotedCallback callback, int watchingUid, int callingUid, + int callingPid) { + mCallback = callback; + mWatchingUid = watchingUid; + mCallingUid = callingUid; + mCallingPid = callingPid; + try { + mCallback.asBinder().linkToDeath(this, 0); + } catch (RemoteException e) { + /*ignored*/ + } + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(128); + sb.append("NotedCallback{"); + sb.append(Integer.toHexString(System.identityHashCode(this))); + sb.append(" watchinguid="); + UserHandle.formatUid(sb, mWatchingUid); + sb.append(" from uid="); + UserHandle.formatUid(sb, mCallingUid); + sb.append(" pid="); + sb.append(mCallingPid); + sb.append('}'); + return sb.toString(); + } + + void destroy() { + mCallback.asBinder().unlinkToDeath(this, 0); + } + + @Override + public void binderDied() { + stopWatchingNoted(mCallback); + } + } + final ArrayMap<IBinder, ClientState> mClients = new ArrayMap<>(); final class ClientState extends Binder implements DeathRecipient { @@ -1629,7 +1677,7 @@ public class AppOpsService extends IAppOpsService.Stub { UidState uidState = getUidStateLocked(uid, false); if (uidState != null && uidState.opModes != null && uidState.opModes.indexOfKey(code) >= 0) { - return uidState.opModes.get(code); + return uidState.evalMode(uidState.opModes.get(code)); } Op op = getOpLocked(code, uid, packageName, false, true, false); if (op == null) { @@ -1795,12 +1843,16 @@ public class AppOpsService extends IAppOpsService.Stub { final Ops ops = getOpsRawLocked(uid, packageName, true /* edit */, false /* uidMismatchExpected */); if (ops == null) { + scheduleOpNotedIfNeededLocked(code, uid, packageName, + AppOpsManager.MODE_IGNORED); if (DEBUG) Slog.d(TAG, "noteOperation: no op for code " + code + " uid " + uid + " package " + packageName); return AppOpsManager.MODE_ERRORED; } final Op op = getOpLocked(ops, code, true); if (isOpRestrictedLocked(uid, code, packageName)) { + scheduleOpNotedIfNeededLocked(code, uid, packageName, + AppOpsManager.MODE_IGNORED); return AppOpsManager.MODE_IGNORED; } final UidState uidState = ops.uidState; @@ -1820,6 +1872,7 @@ public class AppOpsService extends IAppOpsService.Stub { + switchCode + " (" + code + ") uid " + uid + " package " + packageName); op.rejectTime[uidState.state] = System.currentTimeMillis(); + scheduleOpNotedIfNeededLocked(code, uid, packageName, uidMode); return uidMode; } } else { @@ -1830,6 +1883,7 @@ public class AppOpsService extends IAppOpsService.Stub { + switchCode + " (" + code + ") uid " + uid + " package " + packageName); op.rejectTime[uidState.state] = System.currentTimeMillis(); + scheduleOpNotedIfNeededLocked(op.op, uid, packageName, mode); return mode; } } @@ -1839,6 +1893,8 @@ public class AppOpsService extends IAppOpsService.Stub { op.rejectTime[uidState.state] = 0; op.proxyUid = proxyUid; op.proxyPackageName = proxyPackageName; + scheduleOpNotedIfNeededLocked(code, uid, packageName, + AppOpsManager.MODE_ALLOWED); return AppOpsManager.MODE_ALLOWED; } } @@ -1886,10 +1942,50 @@ public class AppOpsService extends IAppOpsService.Stub { } final int callbackCount = activeCallbacks.size(); for (int i = 0; i < callbackCount; i++) { - // Apps ops are mapped to a singleton - if (i == 0) { - activeCallbacks.valueAt(i).destroy(); - } + activeCallbacks.valueAt(i).destroy(); + } + } + } + + @Override + public void startWatchingNoted(@NonNull int[] ops, @NonNull IAppOpsNotedCallback callback) { + int watchedUid = Process.INVALID_UID; + final int callingUid = Binder.getCallingUid(); + final int callingPid = Binder.getCallingPid(); + if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS) + != PackageManager.PERMISSION_GRANTED) { + watchedUid = callingUid; + } + Preconditions.checkArgument(!ArrayUtils.isEmpty(ops), "Ops cannot be null or empty"); + Preconditions.checkArrayElementsInRange(ops, 0, AppOpsManager._NUM_OP - 1, + "Invalid op code in: " + Arrays.toString(ops)); + Preconditions.checkNotNull(callback, "Callback cannot be null"); + synchronized (this) { + SparseArray<NotedCallback> callbacks = mNotedWatchers.get(callback.asBinder()); + if (callbacks == null) { + callbacks = new SparseArray<>(); + mNotedWatchers.put(callback.asBinder(), callbacks); + } + final NotedCallback notedCallback = new NotedCallback(callback, watchedUid, + callingUid, callingPid); + for (int op : ops) { + callbacks.put(op, notedCallback); + } + } + } + + @Override + public void stopWatchingNoted(IAppOpsNotedCallback callback) { + Preconditions.checkNotNull(callback, "Callback cannot be null"); + synchronized (this) { + final SparseArray<NotedCallback> notedCallbacks = + mNotedWatchers.remove(callback.asBinder()); + if (notedCallbacks == null) { + return; + } + final int callbackCount = notedCallbacks.size(); + for (int i = 0; i < callbackCount; i++) { + notedCallbacks.valueAt(i).destroy(); } } } @@ -2052,6 +2148,51 @@ public class AppOpsService extends IAppOpsService.Stub { } } + private void scheduleOpNotedIfNeededLocked(int code, int uid, String packageName, + int result) { + ArraySet<NotedCallback> dispatchedCallbacks = null; + final int callbackListCount = mNotedWatchers.size(); + for (int i = 0; i < callbackListCount; i++) { + final SparseArray<NotedCallback> callbacks = mNotedWatchers.valueAt(i); + final NotedCallback callback = callbacks.get(code); + if (callback != null) { + if (callback.mWatchingUid >= 0 && callback.mWatchingUid != uid) { + continue; + } + if (dispatchedCallbacks == null) { + dispatchedCallbacks = new ArraySet<>(); + } + dispatchedCallbacks.add(callback); + } + } + if (dispatchedCallbacks == null) { + return; + } + mHandler.sendMessage(PooledLambda.obtainMessage( + AppOpsService::notifyOpChecked, + this, dispatchedCallbacks, code, uid, packageName, result)); + } + + private void notifyOpChecked(ArraySet<NotedCallback> callbacks, + int code, int uid, String packageName, int result) { + // There are components watching for checks in our process. The callbacks in + // these components may require permissions our remote caller does not have. + final long identity = Binder.clearCallingIdentity(); + try { + final int callbackCount = callbacks.size(); + for (int i = 0; i < callbackCount; i++) { + final NotedCallback callback = callbacks.valueAt(i); + try { + callback.mCallback.opNoted(code, uid, packageName, result); + } catch (RemoteException e) { + /* do nothing */ + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + @Override public int permissionToOpCode(String permission) { if (permission == null) { @@ -3463,6 +3604,46 @@ public class AppOpsService extends IAppOpsService.Stub { pw.println(cb); } } + if (mNotedWatchers.size() > 0 && dumpMode < 0) { + needSep = true; + boolean printedHeader = false; + for (int i = 0; i < mNotedWatchers.size(); i++) { + final SparseArray<NotedCallback> notedWatchers = mNotedWatchers.valueAt(i); + if (notedWatchers.size() <= 0) { + continue; + } + final NotedCallback cb = notedWatchers.valueAt(0); + if (dumpOp >= 0 && notedWatchers.indexOfKey(dumpOp) < 0) { + continue; + } + if (dumpPackage != null && cb.mWatchingUid >= 0 + && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) { + continue; + } + if (!printedHeader) { + pw.println(" All op noted watchers:"); + printedHeader = true; + } + pw.print(" "); + pw.print(Integer.toHexString(System.identityHashCode( + mNotedWatchers.keyAt(i)))); + pw.println(" ->"); + pw.print(" ["); + final int opCount = notedWatchers.size(); + for (i = 0; i < opCount; i++) { + if (i > 0) { + pw.print(' '); + } + pw.print(AppOpsManager.opToName(notedWatchers.keyAt(i))); + if (i < opCount - 1) { + pw.print(','); + } + } + pw.println("]"); + pw.print(" "); + pw.println(cb); + } + } if (mClients.size() > 0 && dumpMode < 0) { needSep = true; boolean printedHeader = false; diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java index a07939e86325..7bbc543a6aab 100644 --- a/services/core/java/com/android/server/BluetoothManagerService.java +++ b/services/core/java/com/android/server/BluetoothManagerService.java @@ -76,6 +76,7 @@ import java.util.HashMap; import java.util.LinkedList; import java.util.Locale; import java.util.Map; +import java.util.NoSuchElementException; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.ReentrantReadWriteLock; @@ -1276,7 +1277,11 @@ class BluetoothManagerService extends IBluetoothManager.Stub { if (mService == null) { return; } - mService.unlinkToDeath(this, 0); + try { + mService.unlinkToDeath(this, 0); + } catch (NoSuchElementException e) { + Log.e(TAG, "error unlinking to death", e); + } mService = null; mClassName = null; diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 5afb90d681af..fe632e52cae6 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -1665,7 +1665,7 @@ public final class ActiveServices { AppBindRecord b = s.retrieveAppBindingLocked(service, callerApp); ConnectionRecord c = new ConnectionRecord(b, activity, connection, flags, clientLabel, clientIntent, - callerApp.uid, callerApp.processName); + callerApp.uid, callerApp.processName, callingPackage); IBinder binder = connection.asBinder(); ArrayList<ConnectionRecord> clist = s.connections.get(binder); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 739dbbc65f6f..3bfd363de836 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -25,6 +25,7 @@ import static android.Manifest.permission.REMOVE_TASKS; import static android.app.ActivityManager.INSTR_FLAG_DISABLE_HIDDEN_API_CHECKS; import static android.app.ActivityManager.INSTR_FLAG_MOUNT_EXTERNAL_STORAGE_FULL; import static android.app.ActivityManager.PROCESS_STATE_CACHED_ACTIVITY; +import static android.app.ActivityManager.PROCESS_STATE_CACHED_EMPTY; import static android.app.ActivityManager.PROCESS_STATE_LAST_ACTIVITY; import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT; import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY; @@ -2329,9 +2330,16 @@ public class ActivityManagerService extends IActivityManager.Stub fos.close(); long[] rssAfter = Process.getRss(pid); long end = SystemClock.uptimeMillis(); + long time = end - start; EventLog.writeEvent(EventLogTags.AM_COMPACT, pid, name, action, rssBefore[0], rssBefore[1], rssBefore[2], rssBefore[3], - rssAfter[0], rssAfter[1], rssAfter[2], rssAfter[3], end-start); + rssAfter[0], rssAfter[1], rssAfter[2], rssAfter[3], time, + lastCompactAction, lastCompactTime, msg.arg1, msg.arg2); + 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)); synchronized(ActivityManagerService.this) { proc.lastCompactTime = end; proc.lastCompactAction = pendingAction; @@ -6284,7 +6292,7 @@ public class ActivityManagerService extends IActivityManager.Stub ContentProviderConnection incProviderCountLocked(ProcessRecord r, final ContentProviderRecord cpr, IBinder externalProcessToken, int callingUid, - String callingTag, boolean stable) { + String callingPackage, String callingTag, boolean stable) { if (r != null) { for (int i=0; i<r.conProviders.size(); i++) { ContentProviderConnection conn = r.conProviders.get(i); @@ -6304,7 +6312,7 @@ public class ActivityManagerService extends IActivityManager.Stub return conn; } } - ContentProviderConnection conn = new ContentProviderConnection(cpr, r); + ContentProviderConnection conn = new ContentProviderConnection(cpr, r, callingPackage); conn.startAssociationIfNeeded(); if (stable) { conn.stableCount = 1; @@ -6411,8 +6419,8 @@ public class ActivityManagerService extends IActivityManager.Stub } private ContentProviderHolder getContentProviderImpl(IApplicationThread caller, - String name, IBinder token, int callingUid, String callingTag, boolean stable, - int userId) { + String name, IBinder token, int callingUid, String callingPackage, String callingTag, + boolean stable, int userId) { ContentProviderRecord cpr; ContentProviderConnection conn = null; ProviderInfo cpi = null; @@ -6535,7 +6543,8 @@ public class ActivityManagerService extends IActivityManager.Stub // In this case the provider instance already exists, so we can // return it right away. - conn = incProviderCountLocked(r, cpr, token, callingUid, callingTag, stable); + conn = incProviderCountLocked(r, cpr, token, callingUid, callingPackage, callingTag, + stable); if (conn != null && (conn.stableCount+conn.unstableCount) == 1) { if (cpr.proc != null && r.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ) { // If this is a perceptible app accessing the provider, @@ -6782,7 +6791,8 @@ public class ActivityManagerService extends IActivityManager.Stub } mProviderMap.putProviderByName(name, cpr); - conn = incProviderCountLocked(r, cpr, token, callingUid, callingTag, stable); + conn = incProviderCountLocked(r, cpr, token, callingUid, callingPackage, callingTag, + stable); if (conn != null) { conn.waiting = true; } @@ -6924,7 +6934,8 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public final ContentProviderHolder getContentProvider( - IApplicationThread caller, String name, int userId, boolean stable) { + IApplicationThread caller, String callingPackage, String name, int userId, + boolean stable) { enforceNotIsolatedCaller("getContentProvider"); if (caller == null) { String msg = "null IApplicationThread when getting content provider " @@ -6934,8 +6945,14 @@ public class ActivityManagerService extends IActivityManager.Stub } // The incoming user check is now handled in checkContentProviderPermissionLocked() to deal // with cross-user grant. - return getContentProviderImpl(caller, name, null, Binder.getCallingUid(), null, stable, - userId); + final int callingUid = Binder.getCallingUid(); + if (callingPackage != null && mAppOpsService.checkPackage(callingUid, callingPackage) + != AppOpsManager.MODE_ALLOWED) { + throw new SecurityException("Given calling package " + callingPackage + + " does not match caller's uid " + callingUid); + } + return getContentProviderImpl(caller, name, null, callingUid, callingPackage, + null, stable, userId); } public ContentProviderHolder getContentProviderExternal( @@ -6950,7 +6967,8 @@ public class ActivityManagerService extends IActivityManager.Stub private ContentProviderHolder getContentProviderExternalUnchecked(String name, IBinder token, int callingUid, String callingTag, int userId) { - return getContentProviderImpl(null, name, token, callingUid, callingTag, true, userId); + return getContentProviderImpl(null, name, token, callingUid, null, callingTag, + true, userId); } /** @@ -15694,7 +15712,7 @@ public class ActivityManagerService extends IActivityManager.Stub } private final boolean computeOomAdjLocked(ProcessRecord app, int cachedAdj, ProcessRecord TOP_APP, - boolean doingAll, long now) { + boolean doingAll, long now, boolean cycleReEval) { if (mAdjSeq == app.adjSeq) { if (app.adjSeq == app.completedAdjSeq) { // This adjustment has already been computed successfully. @@ -15760,20 +15778,21 @@ public class ActivityManagerService extends IActivityManager.Stub app.systemNoUi = false; } if (!app.systemNoUi) { - if (mWakefulness == PowerManagerInternal.WAKEFULNESS_AWAKE) { - // screen on, promote UI - app.setCurProcState(ActivityManager.PROCESS_STATE_PERSISTENT_UI); - app.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_TOP_APP); - } else { - // screen off, restrict UI scheduling - app.setCurProcState(ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE); - app.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_RESTRICTED); - } + if (mWakefulness == PowerManagerInternal.WAKEFULNESS_AWAKE) { + // screen on, promote UI + app.setCurProcState(ActivityManager.PROCESS_STATE_PERSISTENT_UI); + app.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_TOP_APP); + } else { + // screen off, restrict UI scheduling + app.setCurProcState(ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE); + app.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_RESTRICTED); + } } + app.setCurRawProcState(app.getCurProcState()); app.curAdj = app.maxAdj; app.completedAdjSeq = app.adjSeq; // if curAdj is less than prevAppAdj, then this process was promoted - return app.curAdj < prevAppAdj; + return app.curAdj < prevAppAdj || app.getCurProcState() < prevProcState; } app.systemNoUi = false; @@ -16015,8 +16034,13 @@ public class ActivityManagerService extends IActivityManager.Stub // By default, we use the computed adjustment. It may be changed if // there are applications dependent on our services or providers, but // this gives us a baseline and makes sure we don't get into an - // infinite recursion. - app.setCurRawAdj(adj); + // infinite recursion. If we're re-evaluating due to cycles, use the previously computed + // values. + app.setCurRawAdj(!cycleReEval ? adj : Math.min(adj, app.getCurRawAdj())); + app.setCurRawProcState(!cycleReEval + ? procState + : Math.min(procState, app.getCurRawProcState())); + app.hasStartedServices = false; app.adjSeq = mAdjSeq; @@ -16118,21 +16142,15 @@ public class ActivityManagerService extends IActivityManager.Stub boolean trackedProcState = false; if ((cr.flags&Context.BIND_WAIVE_PRIORITY) == 0) { ProcessRecord client = cr.binding.client; - computeOomAdjLocked(client, cachedAdj, TOP_APP, doingAll, now); - if (client.containsCycle) { - // We've detected a cycle. We should retry computeOomAdjLocked later in - // case a later-checked connection from a client would raise its - // priority legitimately. - app.containsCycle = true; - // If the client has not been completely evaluated, skip using its - // priority. Else use the conservative value for now and look for a - // better state in the next iteration. - if (client.completedAdjSeq < mAdjSeq) { - continue; - } + computeOomAdjLocked(client, cachedAdj, TOP_APP, doingAll, now, cycleReEval); + + if (shouldSkipDueToCycle(app, client, procState, adj, cycleReEval)) { + continue; } + int clientAdj = client.getCurRawAdj(); - int clientProcState = client.getCurProcState(); + int clientProcState = client.getCurRawProcState(); + if (clientProcState >= PROCESS_STATE_CACHED_ACTIVITY) { // If the other app is cached for any reason, for purposes here // we are going to consider it empty. The specific cached state @@ -16217,6 +16235,7 @@ public class ActivityManagerService extends IActivityManager.Stub } if (adj > newAdj) { adj = newAdj; + app.setCurRawAdj(adj); adjType = "service"; } } @@ -16288,6 +16307,7 @@ public class ActivityManagerService extends IActivityManager.Stub } if (procState > clientProcState) { procState = clientProcState; + app.setCurRawProcState(procState); if (adjType == null) { adjType = "service"; } @@ -16319,6 +16339,7 @@ public class ActivityManagerService extends IActivityManager.Stub if (a != null && adj > ProcessList.FOREGROUND_APP_ADJ && a.isActivityVisible()) { adj = ProcessList.FOREGROUND_APP_ADJ; + app.setCurRawAdj(adj); if ((cr.flags&Context.BIND_NOT_FOREGROUND) == 0) { if ((cr.flags&Context.BIND_IMPORTANT) != 0) { schedGroup = ProcessList.SCHED_GROUP_TOP_APP_BOUND; @@ -16360,21 +16381,15 @@ public class ActivityManagerService extends IActivityManager.Stub // Being our own client is not interesting. continue; } - computeOomAdjLocked(client, cachedAdj, TOP_APP, doingAll, now); - if (client.containsCycle) { - // We've detected a cycle. We should retry computeOomAdjLocked later in - // case a later-checked connection from a client would raise its - // priority legitimately. - app.containsCycle = true; - // If the client has not been completely evaluated, skip using its - // priority. Else use the conservative value for now and look for a - // better state in the next iteration. - if (client.completedAdjSeq < mAdjSeq) { - continue; - } + computeOomAdjLocked(client, cachedAdj, TOP_APP, doingAll, now, cycleReEval); + + if (shouldSkipDueToCycle(app, client, procState, adj, cycleReEval)) { + continue; } + int clientAdj = client.getCurRawAdj(); - int clientProcState = client.getCurProcState(); + int clientProcState = client.getCurRawProcState(); + if (clientProcState >= PROCESS_STATE_CACHED_ACTIVITY) { // If the other app is cached for any reason, for purposes here // we are going to consider it empty. @@ -16388,6 +16403,7 @@ public class ActivityManagerService extends IActivityManager.Stub } else { adj = clientAdj > ProcessList.FOREGROUND_APP_ADJ ? clientAdj : ProcessList.FOREGROUND_APP_ADJ; + app.setCurRawAdj(adj); adjType = "provider"; } app.cached &= client.cached; @@ -16423,6 +16439,7 @@ public class ActivityManagerService extends IActivityManager.Stub conn.trackProcState(clientProcState, mAdjSeq, now); if (procState > clientProcState) { procState = clientProcState; + app.setCurRawProcState(procState); } if (client.getCurrentSchedulingGroup() > schedGroup) { schedGroup = ProcessList.SCHED_GROUP_DEFAULT; @@ -16448,6 +16465,7 @@ public class ActivityManagerService extends IActivityManager.Stub if (cpr.hasExternalProcessHandles()) { if (adj > ProcessList.FOREGROUND_APP_ADJ) { adj = ProcessList.FOREGROUND_APP_ADJ; + app.setCurRawAdj(adj); schedGroup = ProcessList.SCHED_GROUP_DEFAULT; app.cached = false; app.adjType = "ext-provider"; @@ -16459,6 +16477,7 @@ public class ActivityManagerService extends IActivityManager.Stub } if (procState > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) { procState = ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND; + app.setCurRawProcState(procState); if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise procstate to external provider: " + app); @@ -16603,6 +16622,7 @@ public class ActivityManagerService extends IActivityManager.Stub app.curAdj = app.modifyRawOomAdj(adj); app.setCurrentSchedulingGroup(schedGroup); app.setCurProcState(procState); + app.setCurRawProcState(procState); app.setHasForegroundActivities(foregroundActivities); app.completedAdjSeq = mAdjSeq; @@ -16610,6 +16630,44 @@ public class ActivityManagerService extends IActivityManager.Stub return app.curAdj < prevAppAdj || app.getCurProcState() < prevProcState; } + /** + * Checks if for the given app and client, there's a cycle that should skip over the client + * for now or use partial values to evaluate the effect of the client binding. + * @param app + * @param client + * @param procState procstate evaluated so far for this app + * @param adj oom_adj evaluated so far for this app + * @param cycleReEval whether we're currently re-evaluating due to a cycle, and not the first + * evaluation. + * @return whether to skip using the client connection at this time + */ + private boolean shouldSkipDueToCycle(ProcessRecord app, ProcessRecord client, + int procState, int adj, boolean cycleReEval) { + if (client.containsCycle) { + // We've detected a cycle. We should retry computeOomAdjLocked later in + // case a later-checked connection from a client would raise its + // priority legitimately. + app.containsCycle = true; + // If the client has not been completely evaluated, check if it's worth + // using the partial values. + if (client.completedAdjSeq < mAdjSeq) { + if (cycleReEval) { + // If the partial values are no better, skip until the next + // attempt + if (client.getCurRawProcState() >= procState + && client.getCurRawAdj() >= adj) { + return true; + } + // Else use the client's partial procstate and adj to adjust the + // effect of the binding + } else { + return true; + } + } + } + return false; + } + private static final class RecordPssRunnable implements Runnable { private final ActivityManagerService mService; private final ProcessRecord mProc; @@ -17028,12 +17086,16 @@ public class ActivityManagerService extends IActivityManager.Stub app.curAdj == ProcessList.HOME_APP_ADJ)) { app.reqCompactAction = COMPACT_PROCESS_SOME; mPendingCompactionProcesses.add(app); - mCompactionHandler.sendEmptyMessage(COMPACT_PROCESS_MSG); + mCompactionHandler.sendMessage( + mCompactionHandler.obtainMessage( + COMPACT_PROCESS_MSG, app.curAdj, app.setProcState)); } else if (app.setAdj < ProcessList.CACHED_APP_MIN_ADJ && app.curAdj >= ProcessList.CACHED_APP_MIN_ADJ) { app.reqCompactAction = COMPACT_PROCESS_FULL; mPendingCompactionProcesses.add(app); - mCompactionHandler.sendEmptyMessage(COMPACT_PROCESS_MSG); + mCompactionHandler.sendMessage( + mCompactionHandler.obtainMessage( + COMPACT_PROCESS_MSG, app.curAdj, app.setProcState)); } } ProcessList.setOomAdj(app.pid, app.uid, app.curAdj); @@ -17472,7 +17534,7 @@ public class ActivityManagerService extends IActivityManager.Stub return false; } - computeOomAdjLocked(app, cachedAdj, TOP_APP, doingAll, now); + computeOomAdjLocked(app, cachedAdj, TOP_APP, doingAll, now, false); return applyOomAdjLocked(app, doingAll, now, SystemClock.elapsedRealtime()); } @@ -17847,12 +17909,14 @@ public class ActivityManagerService extends IActivityManager.Stub for (int i=N-1; i>=0; i--) { ProcessRecord app = mProcessList.mLruProcesses.get(i); app.containsCycle = false; + app.setCurRawProcState(PROCESS_STATE_CACHED_EMPTY); + app.setCurRawAdj(ProcessList.UNKNOWN_ADJ); } for (int i=N-1; i>=0; i--) { ProcessRecord app = mProcessList.mLruProcesses.get(i); if (!app.killedByAm && app.thread != null) { app.procStateChanged = false; - computeOomAdjLocked(app, ProcessList.UNKNOWN_ADJ, TOP_APP, true, now); + computeOomAdjLocked(app, ProcessList.UNKNOWN_ADJ, TOP_APP, true, now, false); // if any app encountered a cycle, we need to perform an additional loop later retryCycles |= app.containsCycle; @@ -17955,8 +18019,8 @@ public class ActivityManagerService extends IActivityManager.Stub for (int i=0; i<N; i++) { ProcessRecord app = mProcessList.mLruProcesses.get(i); if (!app.killedByAm && app.thread != null && app.containsCycle == true) { - - if (computeOomAdjLocked(app, ProcessList.UNKNOWN_ADJ, TOP_APP, true, now)) { + if (computeOomAdjLocked(app, ProcessList.UNKNOWN_ADJ, TOP_APP, true, now, + true)) { retryCycles = true; } } diff --git a/services/core/java/com/android/server/am/ConnectionRecord.java b/services/core/java/com/android/server/am/ConnectionRecord.java index aa76b3d6b856..af1031e5b887 100644 --- a/services/core/java/com/android/server/am/ConnectionRecord.java +++ b/services/core/java/com/android/server/am/ConnectionRecord.java @@ -43,6 +43,7 @@ final class ConnectionRecord { final PendingIntent clientIntent; // How to launch the client. final int clientUid; // The identity of this connection's client final String clientProcessName; // The source process of this connection's client + final String clientPackageName; // The source package of this connection's client public AssociationState.SourceState association; // Association tracking String stringName; // Caching of toString. boolean serviceDead; // Well is it? @@ -96,7 +97,7 @@ final class ConnectionRecord { ActivityServiceConnectionsHolder<ConnectionRecord> _activity, IServiceConnection _conn, int _flags, int _clientLabel, PendingIntent _clientIntent, - int _clientUid, String _clientProcessName) { + int _clientUid, String _clientProcessName, String _clientPackageName) { binding = _binding; activity = _activity; conn = _conn; @@ -105,6 +106,7 @@ final class ConnectionRecord { clientIntent = _clientIntent; clientUid = _clientUid; clientProcessName = _clientProcessName; + clientPackageName = _clientPackageName; } public void startAssociationIfNeeded() { @@ -125,7 +127,7 @@ final class ConnectionRecord { } else { association = holder.pkg.getAssociationStateLocked(holder.state, binding.service.instanceName.getClassName()).startSource(clientUid, - clientProcessName); + clientProcessName, clientPackageName); } } diff --git a/services/core/java/com/android/server/am/ContentProviderConnection.java b/services/core/java/com/android/server/am/ContentProviderConnection.java index f2d4f739c8bf..5f184c2dabee 100644 --- a/services/core/java/com/android/server/am/ContentProviderConnection.java +++ b/services/core/java/com/android/server/am/ContentProviderConnection.java @@ -32,6 +32,7 @@ import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; public final class ContentProviderConnection extends Binder { public final ContentProviderRecord provider; public final ProcessRecord client; + public final String clientPackage; public AssociationState.SourceState association; public final long createTime; public int stableCount; @@ -46,9 +47,11 @@ public final class ContentProviderConnection extends Binder { public int numStableIncs; public int numUnstableIncs; - public ContentProviderConnection(ContentProviderRecord _provider, ProcessRecord _client) { + public ContentProviderConnection(ContentProviderRecord _provider, ProcessRecord _client, + String _clientPackage) { provider = _provider; client = _client; + clientPackage = _clientPackage; createTime = SystemClock.elapsedRealtime(); } @@ -69,7 +72,8 @@ public final class ContentProviderConnection extends Binder { + provider.name.toShortString() + ": proc=" + provider.proc); } else { association = holder.pkg.getAssociationStateLocked(holder.state, - provider.name.getClassName()).startSource(client.uid, client.processName); + provider.name.getClassName()).startSource(client.uid, client.processName, + clientPackage); } } diff --git a/services/core/java/com/android/server/am/ContentProviderRecord.java b/services/core/java/com/android/server/am/ContentProviderRecord.java index 2fc4adc2dc6c..46dfc7c60fd9 100644 --- a/services/core/java/com/android/server/am/ContentProviderRecord.java +++ b/services/core/java/com/android/server/am/ContentProviderRecord.java @@ -305,7 +305,7 @@ final class ContentProviderRecord implements ComponentName.WithComponentName { } else { mAssociation = holder.pkg.getAssociationStateLocked(holder.state, provider.name.getClassName()).startSource(mOwningUid, - mOwningProcessName); + mOwningProcessName, null); } } diff --git a/services/core/java/com/android/server/am/EventLogTags.logtags b/services/core/java/com/android/server/am/EventLogTags.logtags index fa7a4c532f42..a71f6af80bec 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)
\ No newline at end of file +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) diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index c4b715042953..c15b7c7d82c2 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -153,6 +153,7 @@ final class ProcessRecord implements WindowProcessListener { int trimMemoryLevel; // Last selected memory trimming level private int mCurProcState = PROCESS_STATE_NONEXISTENT; // Currently computed process state private int mRepProcState = PROCESS_STATE_NONEXISTENT; // Last reported process state + private int mCurRawProcState = PROCESS_STATE_NONEXISTENT; // Temp state during computation int setProcState = PROCESS_STATE_NONEXISTENT; // Last set process state in process tracker int pssProcState = PROCESS_STATE_NONEXISTENT; // Currently requesting pss for int pssStatType; // The type of stat collection that we are currently requesting @@ -902,6 +903,7 @@ final class ProcessRecord implements WindowProcessListener { if (mRepProcState > newState) { mRepProcState = newState; setCurProcState(newState); + setCurRawProcState(newState); for (int ipkg = pkgList.size() - 1; ipkg >= 0; ipkg--) { StatsLog.write(StatsLog.PROCESS_STATE_CHANGED, uid, processName, pkgList.keyAt(ipkg), @@ -984,6 +986,14 @@ final class ProcessRecord implements WindowProcessListener { return mCurProcState; } + void setCurRawProcState(int curRawProcState) { + mCurRawProcState = curRawProcState; + } + + int getCurRawProcState() { + return mCurRawProcState; + } + void setReportedProcState(int repProcState) { mRepProcState = repProcState; for (int ipkg = pkgList.size() - 1; ipkg >= 0; ipkg--) { diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java index 1882be26ebf0..a381477b01cb 100644 --- a/services/core/java/com/android/server/biometrics/BiometricService.java +++ b/services/core/java/com/android/server/biometrics/BiometricService.java @@ -653,7 +653,7 @@ public class BiometricService extends SystemService { } mHandler.post(() -> { - final Pair<Integer, Integer> result = checkAndGetBiometricModality(callingUserId); + final Pair<Integer, Integer> result = checkAndGetBiometricModality(userId); final int modality = result.first; final int error = result.second; @@ -950,7 +950,7 @@ public class BiometricService extends SystemService { * {@link BiometricAuthenticator#TYPE_FACE} * and the error containing one of the {@link BiometricConstants} errors. */ - private Pair<Integer, Integer> checkAndGetBiometricModality(int callingUid) { + private Pair<Integer, Integer> checkAndGetBiometricModality(int userId) { int modality = TYPE_NONE; // No biometric features, send error @@ -979,7 +979,7 @@ public class BiometricService extends SystemService { // order. firstHwAvailable = modality; } - if (authenticator.hasEnrolledTemplates(callingUid)) { + if (authenticator.hasEnrolledTemplates(userId)) { hasTemplatesEnrolled = true; if (isEnabledForApp(modality)) { // TODO(b/110907543): When face settings (and other settings) have both a diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java index 78b3c15500ea..52ecccaa7367 100644 --- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java +++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java @@ -17,6 +17,13 @@ package com.android.server.display; import android.annotation.Nullable; +import android.app.ActivityManager; +import android.app.ActivityManager.StackInfo; +import android.app.ActivityTaskManager; +import android.app.IActivityTaskManager; +import android.app.TaskStackListener; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManagerInternal; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; @@ -27,6 +34,8 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.PowerManager; +import android.os.Process; +import android.os.RemoteException; import android.os.SystemClock; import android.os.Trace; import android.util.EventLog; @@ -34,14 +43,15 @@ import android.util.MathUtils; import android.util.Slog; import android.util.TimeUtils; +import com.android.internal.os.BackgroundThread; import com.android.server.EventLogTags; +import com.android.server.LocalServices; import java.io.PrintWriter; class AutomaticBrightnessController { private static final String TAG = "AutomaticBrightnessController"; - private static final boolean DEBUG = false; private static final boolean DEBUG_PRETEND_LIGHT_SENSOR_ABSENT = false; // If true, enables the use of the screen auto-brightness adjustment setting. @@ -66,6 +76,8 @@ class AutomaticBrightnessController { private static final int MSG_UPDATE_AMBIENT_LUX = 1; private static final int MSG_BRIGHTNESS_ADJUSTMENT_SAMPLE = 2; private static final int MSG_INVALIDATE_SHORT_TERM_MODEL = 3; + private static final int MSG_UPDATE_FOREGROUND_APP = 4; + private static final int MSG_UPDATE_FOREGROUND_APP_SYNC = 5; // Length of the ambient light horizon used to calculate the long term estimate of ambient // light. @@ -126,6 +138,8 @@ class AutomaticBrightnessController { private final HysteresisLevels mAmbientBrightnessThresholds; private final HysteresisLevels mScreenBrightnessThresholds; + private boolean mLoggingEnabled; + // Amount of time to delay auto-brightness after screen on while waiting for // the light sensor to warm-up in milliseconds. // May be 0 if no warm-up is required. @@ -192,6 +206,19 @@ class AutomaticBrightnessController { private float mShortTermModelAnchor; private float SHORT_TERM_MODEL_THRESHOLD_RATIO = 0.6f; + // Context-sensitive brightness configurations require keeping track of the foreground app's + // package name and category, which is done by registering a TaskStackListener to call back to + // us onTaskStackChanged, and then using the ActivityTaskManager to get the foreground app's + // package namd and PackageManager to get its category (so might as well cache them). + private int mUserId; + private String mForegroundAppPackageName; + private String mPendingForegroundAppPackageName; + private @ApplicationInfo.Category int mForegroundAppCategory; + private @ApplicationInfo.Category int mPendingForegroundAppCategory; + private TaskStackListenerImpl mTaskStackListener; + private IActivityTaskManager mActivityTaskManager; + private PackageManagerInternal mPackageManagerInternal; + public AutomaticBrightnessController(Callbacks callbacks, Looper looper, SensorManager sensorManager, BrightnessMappingStrategy mapper, int lightSensorWarmUpTime, int brightnessMin, int brightnessMax, float dozeScaleFactor, @@ -226,6 +253,42 @@ class AutomaticBrightnessController { if (!DEBUG_PRETEND_LIGHT_SENSOR_ABSENT) { mLightSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT); } + + mUserId = ActivityManager.getCurrentUser(); + mActivityTaskManager = ActivityTaskManager.getService(); + mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); + mTaskStackListener = new TaskStackListenerImpl(); + mForegroundAppPackageName = null; + mPendingForegroundAppPackageName = null; + mForegroundAppCategory = ApplicationInfo.CATEGORY_UNDEFINED; + mPendingForegroundAppCategory = ApplicationInfo.CATEGORY_UNDEFINED; + } + + /** + * Enable/disable logging. + * + * @param loggingEnabled + * Whether logging should be on/off. + * + * @return Whether the method succeeded or not. + */ + public boolean setLoggingEnabled(boolean loggingEnabled) { + if (mLoggingEnabled == loggingEnabled) { + return false; + } + mBrightnessMapper.setLoggingEnabled(loggingEnabled); + mLoggingEnabled = loggingEnabled; + return true; + } + + /** + * Update the current user's ID. + * + * @param userId + * The current user's ID. + */ + public void onSwitchUser(int userId) { + mUserId = userId; } public int getAutomaticScreenBrightness() { @@ -290,7 +353,7 @@ class AutomaticBrightnessController { } final int oldPolicy = mDisplayPolicy; mDisplayPolicy = policy; - if (DEBUG) { + if (mLoggingEnabled) { Slog.d(TAG, "Display policy transitioning from " + oldPolicy + " to " + policy); } if (!isInteractivePolicy(policy) && isInteractivePolicy(oldPolicy)) { @@ -317,7 +380,7 @@ class AutomaticBrightnessController { mBrightnessMapper.addUserDataPoint(mAmbientLux, brightness); mShortTermModelValid = true; mShortTermModelAnchor = mAmbientLux; - if (DEBUG) { + if (mLoggingEnabled) { Slog.d(TAG, "ShortTermModel: anchor=" + mShortTermModelAnchor); } return true; @@ -330,7 +393,7 @@ class AutomaticBrightnessController { } private void invalidateShortTermModel() { - if (DEBUG) { + if (mLoggingEnabled) { Slog.d(TAG, "ShortTermModel: invalidate user data"); } mShortTermModelValid = false; @@ -383,7 +446,11 @@ class AutomaticBrightnessController { pw.println(" mBrightnessAdjustmentSampleOldLux=" + mBrightnessAdjustmentSampleOldLux); pw.println(" mBrightnessAdjustmentSampleOldBrightness=" + mBrightnessAdjustmentSampleOldBrightness); - pw.println(" mShortTermModelValid=" + mShortTermModelValid); + pw.println(" mUserId=" + mUserId); + pw.println(" mForegroundAppPackageName=" + mForegroundAppPackageName); + pw.println(" mPendingForegroundAppPackageName=" + mPendingForegroundAppPackageName); + pw.println(" mForegroundAppCategory=" + mForegroundAppCategory); + pw.println(" mPendingForegroundAppCategory=" + mPendingForegroundAppCategory); pw.println(); mBrightnessMapper.dump(pw); @@ -399,6 +466,7 @@ class AutomaticBrightnessController { mLightSensorEnabled = true; mLightSensorEnableTime = SystemClock.uptimeMillis(); mCurrentLightSensorRate = mInitialLightSensorRate; + registerForegroundAppUpdater(); mSensorManager.registerListener(mLightSensorListener, mLightSensor, mCurrentLightSensorRate * 1000, mHandler); return true; @@ -411,6 +479,7 @@ class AutomaticBrightnessController { mAmbientLightRingBuffer.clear(); mCurrentLightSensorRate = -1; mHandler.removeMessages(MSG_UPDATE_AMBIENT_LUX); + unregisterForegroundAppUpdater(); mSensorManager.unregisterListener(mLightSensorListener); } return false; @@ -441,7 +510,7 @@ class AutomaticBrightnessController { private void adjustLightSensorRate(int lightSensorRate) { // if the light sensor rate changed, update the sensor listener if (lightSensorRate != mCurrentLightSensorRate) { - if (DEBUG) { + if (mLoggingEnabled) { Slog.d(TAG, "adjustLightSensorRate: " + "previousRate=" + mCurrentLightSensorRate + ", " + "currentRate=" + lightSensorRate); @@ -458,7 +527,7 @@ class AutomaticBrightnessController { } private void setAmbientLux(float lux) { - if (DEBUG) { + if (mLoggingEnabled) { Slog.d(TAG, "setAmbientLux(" + lux + ")"); } if (lux < 0) { @@ -476,7 +545,7 @@ class AutomaticBrightnessController { final float maxAmbientLux = mShortTermModelAnchor + mShortTermModelAnchor * SHORT_TERM_MODEL_THRESHOLD_RATIO; if (minAmbientLux < mAmbientLux && mAmbientLux < maxAmbientLux) { - if (DEBUG) { + if (mLoggingEnabled) { Slog.d(TAG, "ShortTermModel: re-validate user data, ambient lux is " + minAmbientLux + " < " + mAmbientLux + " < " + maxAmbientLux); } @@ -490,7 +559,7 @@ class AutomaticBrightnessController { } private float calculateAmbientLux(long now, long horizon) { - if (DEBUG) { + if (mLoggingEnabled) { Slog.d(TAG, "calculateAmbientLux(" + now + ", " + horizon + ")"); } final int N = mAmbientLightRingBuffer.size(); @@ -509,7 +578,7 @@ class AutomaticBrightnessController { break; } } - if (DEBUG) { + if (mLoggingEnabled) { Slog.d(TAG, "calculateAmbientLux: selected endIndex=" + endIndex + ", point=(" + mAmbientLightRingBuffer.getTime(endIndex) + ", " + mAmbientLightRingBuffer.getLux(endIndex) + ")"); @@ -527,7 +596,7 @@ class AutomaticBrightnessController { final long startTime = eventTime - now; float weight = calculateWeight(startTime, endTime); float lux = mAmbientLightRingBuffer.getLux(i); - if (DEBUG) { + if (mLoggingEnabled) { Slog.d(TAG, "calculateAmbientLux: [" + startTime + ", " + endTime + "]: " + "lux=" + lux + ", " + "weight=" + weight); @@ -536,7 +605,7 @@ class AutomaticBrightnessController { sum += lux * weight; endTime = startTime; } - if (DEBUG) { + if (mLoggingEnabled) { Slog.d(TAG, "calculateAmbientLux: " + "totalWeight=" + totalWeight + ", " + "newAmbientLux=" + (sum / totalWeight)); @@ -591,7 +660,7 @@ class AutomaticBrightnessController { final long timeWhenSensorWarmedUp = mLightSensorWarmUpTimeConfig + mLightSensorEnableTime; if (time < timeWhenSensorWarmedUp) { - if (DEBUG) { + if (mLoggingEnabled) { Slog.d(TAG, "updateAmbientLux: Sensor not ready yet: " + "time=" + time + ", " + "timeWhenSensorWarmedUp=" + timeWhenSensorWarmedUp); @@ -602,7 +671,7 @@ class AutomaticBrightnessController { } setAmbientLux(calculateAmbientLux(time, AMBIENT_LIGHT_SHORT_HORIZON_MILLIS)); mAmbientLuxValid = true; - if (DEBUG) { + if (mLoggingEnabled) { Slog.d(TAG, "updateAmbientLux: Initializing: " + "mAmbientLightRingBuffer=" + mAmbientLightRingBuffer + ", " + "mAmbientLux=" + mAmbientLux); @@ -630,10 +699,10 @@ class AutomaticBrightnessController { && fastAmbientLux <= mAmbientDarkeningThreshold && nextDarkenTransition <= time)) { setAmbientLux(fastAmbientLux); - if (DEBUG) { + if (mLoggingEnabled) { Slog.d(TAG, "updateAmbientLux: " + ((fastAmbientLux > mAmbientLux) ? "Brightened" : "Darkened") + ": " - + "mAmbientBrighteningThreshold=" + mAmbientBrighteningThreshold + ", " + + "mBrighteningLuxThreshold=" + mAmbientBrighteningThreshold + ", " + "mAmbientLightRingBuffer=" + mAmbientLightRingBuffer + ", " + "mAmbientLux=" + mAmbientLux); } @@ -650,7 +719,7 @@ class AutomaticBrightnessController { // weighted ambient lux or not. nextTransitionTime = nextTransitionTime > time ? nextTransitionTime : time + mNormalLightSensorRate; - if (DEBUG) { + if (mLoggingEnabled) { Slog.d(TAG, "updateAmbientLux: Scheduling ambient lux update for " + nextTransitionTime + TimeUtils.formatUptime(nextTransitionTime)); } @@ -662,7 +731,8 @@ class AutomaticBrightnessController { return; } - float value = mBrightnessMapper.getBrightness(mAmbientLux); + float value = mBrightnessMapper.getBrightness(mAmbientLux, mForegroundAppPackageName, + mForegroundAppCategory); int newScreenAutoBrightness = clampScreenBrightness(Math.round(value * PowerManager.BRIGHTNESS_ON)); @@ -673,7 +743,7 @@ class AutomaticBrightnessController { if (mScreenAutoBrightness != -1 && newScreenAutoBrightness > mScreenDarkeningThreshold && newScreenAutoBrightness < mScreenBrighteningThreshold) { - if (DEBUG) { + if (mLoggingEnabled) { Slog.d(TAG, "ignoring newScreenAutoBrightness: " + mScreenDarkeningThreshold + " < " + newScreenAutoBrightness + " < " + mScreenBrighteningThreshold); } @@ -681,8 +751,7 @@ class AutomaticBrightnessController { } if (mScreenAutoBrightness != newScreenAutoBrightness) { - - if (DEBUG) { + if (mLoggingEnabled) { Slog.d(TAG, "updateAutoBrightness: " + "mScreenAutoBrightness=" + mScreenAutoBrightness + ", " + "newScreenAutoBrightness=" + newScreenAutoBrightness); @@ -718,18 +787,11 @@ class AutomaticBrightnessController { BRIGHTNESS_ADJUSTMENT_SAMPLE_DEBOUNCE_MILLIS); } - private void cancelBrightnessAdjustmentSample() { - if (mBrightnessAdjustmentSamplePending) { - mBrightnessAdjustmentSamplePending = false; - mHandler.removeMessages(MSG_BRIGHTNESS_ADJUSTMENT_SAMPLE); - } - } - private void collectBrightnessAdjustmentSample() { if (mBrightnessAdjustmentSamplePending) { mBrightnessAdjustmentSamplePending = false; if (mAmbientLuxValid && mScreenAutoBrightness >= 0) { - if (DEBUG) { + if (mLoggingEnabled) { Slog.d(TAG, "Auto-brightness adjustment changed by user: " + "lux=" + mAmbientLux + ", " + "brightness=" + mScreenAutoBrightness + ", " + @@ -745,6 +807,68 @@ class AutomaticBrightnessController { } } + // Register a TaskStackListener to call back to us onTaskStackChanged, so we can update the + // foreground app's package name and category and correct the brightness accordingly. + private void registerForegroundAppUpdater() { + try { + mActivityTaskManager.registerTaskStackListener(mTaskStackListener); + // This will not get called until the foreground app changes for the first time, so + // call it explicitly to get the current foreground app's info. + updateForegroundApp(); + } catch (RemoteException e) { + // Nothing to do. + } + } + + private void unregisterForegroundAppUpdater() { + try { + mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener); + } catch (RemoteException e) { + // Nothing to do. + } + mForegroundAppPackageName = null; + mForegroundAppCategory = ApplicationInfo.CATEGORY_UNDEFINED; + } + + // Set the foreground app's package name and category, so brightness can be corrected per app. + private void updateForegroundApp() { + // The ActivityTaskManager's lock tends to get contended, so this is done in a background + // thread and applied via this thread's handler synchronously. + BackgroundThread.getHandler().post(new Runnable() { + public void run() { + try { + // The foreground app is the top activity of the focused tasks stack. + final StackInfo info = mActivityTaskManager.getFocusedStackInfo(); + if (info == null || info.topActivity == null) { + return; + } + final String packageName = info.topActivity.getPackageName(); + // If the app didn't change, there's nothing to do. Otherwise, we have to + // update the category and re-apply the brightness correction. + if (mForegroundAppPackageName != null + && mForegroundAppPackageName.equals(packageName)) { + return; + } + mPendingForegroundAppPackageName = packageName; + ApplicationInfo app = mPackageManagerInternal.getApplicationInfo(packageName, + 0 /* flags */, Process.SYSTEM_UID /* filterCallingUid */, mUserId); + mPendingForegroundAppCategory = app.category; + mHandler.sendEmptyMessage(MSG_UPDATE_FOREGROUND_APP_SYNC); + } catch (RemoteException e) { + // Nothing to do. + } + } + }); + } + + private void updateForegroundAppSync() { + mForegroundAppPackageName = mPendingForegroundAppPackageName; + mPendingForegroundAppPackageName = null; + mForegroundAppCategory = mPendingForegroundAppCategory; + mPendingForegroundAppCategory = ApplicationInfo.CATEGORY_UNDEFINED; + updateAutoBrightness(true /* sendUpdate */); + } + private final class AutomaticBrightnessHandler extends Handler { public AutomaticBrightnessHandler(Looper looper) { super(looper, null, true /*async*/); @@ -764,6 +888,14 @@ class AutomaticBrightnessController { case MSG_INVALIDATE_SHORT_TERM_MODEL: invalidateShortTermModel(); break; + + case MSG_UPDATE_FOREGROUND_APP: + updateForegroundApp(); + break; + + case MSG_UPDATE_FOREGROUND_APP_SYNC: + updateForegroundAppSync(); + break; } } } @@ -784,6 +916,15 @@ class AutomaticBrightnessController { } }; + // Call back whenever the tasks stack changes, which includes tasks being created, removed, and + // moving to top. + class TaskStackListenerImpl extends TaskStackListener { + @Override + public void onTaskStackChanged() { + mHandler.sendEmptyMessage(MSG_UPDATE_FOREGROUND_APP); + } + } + /** Callbacks to request updates to the display's power state. */ interface Callbacks { void updateBrightness(); diff --git a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java index 76c191d5e9ea..9fce644d6c4b 100644 --- a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java +++ b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java @@ -17,9 +17,11 @@ package com.android.server.display; import android.annotation.Nullable; +import android.content.pm.ApplicationInfo; import android.content.res.Resources; import android.content.res.TypedArray; import android.hardware.display.BrightnessConfiguration; +import android.hardware.display.BrightnessCorrection; import android.os.PowerManager; import android.util.MathUtils; import android.util.Pair; @@ -42,11 +44,12 @@ import java.util.Arrays; */ public abstract class BrightnessMappingStrategy { private static final String TAG = "BrightnessMappingStrategy"; - private static final boolean DEBUG = false; private static final float LUX_GRAD_SMOOTHING = 0.25f; private static final float MAX_GRAD = 1.0f; + protected boolean mLoggingEnabled; + private static final Plog PLOG = Plog.createSystemPlog(TAG); @Nullable @@ -161,6 +164,22 @@ public abstract class BrightnessMappingStrategy { } /** + * Enable/disable logging. + * + * @param loggingEnabled + * Whether logging should be on/off. + * + * @return Whether the method succeeded or not. + */ + public boolean setLoggingEnabled(boolean loggingEnabled) { + if (mLoggingEnabled == loggingEnabled) { + return false; + } + mLoggingEnabled = loggingEnabled; + return true; + } + + /** * Sets the {@link BrightnessConfiguration}. * * @param config The new configuration. If {@code null} is passed, the default configuration is @@ -170,15 +189,33 @@ public abstract class BrightnessMappingStrategy { public abstract boolean setBrightnessConfiguration(@Nullable BrightnessConfiguration config); /** - * Returns the desired brightness of the display based on the current ambient lux. + * Returns the desired brightness of the display based on the current ambient lux, including + * any context-related corrections. * * The returned brightness will be in the range [0, 1.0], where 1.0 is the display at max * brightness and 0 is the display at minimum brightness. * * @param lux The current ambient brightness in lux. + * @param packageName the foreground app package name. + * @param category the foreground app package category. * @return The desired brightness of the display normalized to the range [0, 1.0]. */ - public abstract float getBrightness(float lux); + public abstract float getBrightness(float lux, String packageName, + @ApplicationInfo.Category int category); + + /** + * Returns the desired brightness of the display based on the current ambient lux. + * + * The returned brightness wil be in the range [0, 1.0], where 1.0 is the display at max + * brightness and 0 is the display at minimum brightness. + * + * @param lux The current ambient brightness in lux. + * + * @return The desired brightness of the display normalized to the range [0, 1.0]. + */ + public float getBrightness(float lux) { + return getBrightness(lux, null /* packageName */, ApplicationInfo.CATEGORY_UNDEFINED); + } /** * Returns the current auto-brightness adjustment. @@ -239,13 +276,13 @@ public abstract class BrightnessMappingStrategy { public abstract void dump(PrintWriter pw); - private static float normalizeAbsoluteBrightness(int brightness) { + protected float normalizeAbsoluteBrightness(int brightness) { brightness = MathUtils.constrain(brightness, PowerManager.BRIGHTNESS_OFF, PowerManager.BRIGHTNESS_ON); return (float) brightness / PowerManager.BRIGHTNESS_ON; } - private static Pair<float[], float[]> insertControlPoint( + private Pair<float[], float[]> insertControlPoint( float[] luxLevels, float[] brightnessLevels, float lux, float brightness) { final int idx = findInsertionPoint(luxLevels, lux); final float[] newLuxLevels; @@ -278,7 +315,7 @@ public abstract class BrightnessMappingStrategy { * This assumes that {@code arr} is sorted. If all values in {@code arr} are greater * than val, then it will return the length of arr as the insertion point. */ - private static int findInsertionPoint(float[] arr, float val) { + private int findInsertionPoint(float[] arr, float val) { for (int i = 0; i < arr.length; i++) { if (val <= arr[i]) { return i; @@ -287,8 +324,8 @@ public abstract class BrightnessMappingStrategy { return arr.length; } - private static void smoothCurve(float[] lux, float[] brightness, int idx) { - if (DEBUG) { + private void smoothCurve(float[] lux, float[] brightness, int idx) { + if (mLoggingEnabled) { PLOG.logCurve("unsmoothed curve", lux, brightness); } float prevLux = lux[idx]; @@ -323,19 +360,19 @@ public abstract class BrightnessMappingStrategy { prevBrightness = newBrightness; brightness[i] = newBrightness; } - if (DEBUG) { + if (mLoggingEnabled) { PLOG.logCurve("smoothed curve", lux, brightness); } } - private static float permissibleRatio(float currLux, float prevLux) { + private float permissibleRatio(float currLux, float prevLux) { return MathUtils.exp(MAX_GRAD * (MathUtils.log(currLux + LUX_GRAD_SMOOTHING) - MathUtils.log(prevLux + LUX_GRAD_SMOOTHING))); } - private static float inferAutoBrightnessAdjustment(float maxGamma, - float desiredBrightness, float currentBrightness) { + protected float inferAutoBrightnessAdjustment(float maxGamma, float desiredBrightness, + float currentBrightness) { float adjustment = 0; float gamma = Float.NaN; // Extreme edge cases: use a simpler heuristic, as proper gamma correction around the edges @@ -355,7 +392,7 @@ public abstract class BrightnessMappingStrategy { adjustment = -MathUtils.log(gamma) / MathUtils.log(maxGamma); } adjustment = MathUtils.constrain(adjustment, -1, +1); - if (DEBUG) { + if (mLoggingEnabled) { Slog.d(TAG, "inferAutoBrightnessAdjustment: " + maxGamma + "^" + -adjustment + "=" + MathUtils.pow(maxGamma, -adjustment) + " == " + gamma); Slog.d(TAG, "inferAutoBrightnessAdjustment: " + currentBrightness + "^" + gamma + "=" + @@ -364,16 +401,16 @@ public abstract class BrightnessMappingStrategy { return adjustment; } - private static Pair<float[], float[]> getAdjustedCurve(float[] lux, float[] brightness, + protected Pair<float[], float[]> getAdjustedCurve(float[] lux, float[] brightness, float userLux, float userBrightness, float adjustment, float maxGamma) { float[] newLux = lux; float[] newBrightness = Arrays.copyOf(brightness, brightness.length); - if (DEBUG) { + if (mLoggingEnabled) { PLOG.logCurve("unadjusted curve", newLux, newBrightness); } adjustment = MathUtils.constrain(adjustment, -1, 1); float gamma = MathUtils.pow(maxGamma, -adjustment); - if (DEBUG) { + if (mLoggingEnabled) { Slog.d(TAG, "getAdjustedCurve: " + maxGamma + "^" + -adjustment + "=" + MathUtils.pow(maxGamma, -adjustment) + " == " + gamma); } @@ -382,7 +419,7 @@ public abstract class BrightnessMappingStrategy { newBrightness[i] = MathUtils.pow(newBrightness[i], gamma); } } - if (DEBUG) { + if (mLoggingEnabled) { PLOG.logCurve("gamma adjusted curve", newLux, newBrightness); } if (userLux != -1) { @@ -390,7 +427,7 @@ public abstract class BrightnessMappingStrategy { userBrightness); newLux = curve.first; newBrightness = curve.second; - if (DEBUG) { + if (mLoggingEnabled) { PLOG.logCurve("gamma and user adjusted curve", newLux, newBrightness); // This is done for comparison. curve = insertControlPoint(lux, brightness, userLux, userBrightness); @@ -440,7 +477,7 @@ public abstract class BrightnessMappingStrategy { mAutoBrightnessAdjustment = 0; mUserLux = -1; mUserBrightness = -1; - if (DEBUG) { + if (mLoggingEnabled) { PLOG.start("simple mapping strategy"); } computeSpline(); @@ -452,7 +489,8 @@ public abstract class BrightnessMappingStrategy { } @Override - public float getBrightness(float lux) { + public float getBrightness(float lux, String packageName, + @ApplicationInfo.Category int category) { return mSpline.interpolate(lux); } @@ -467,7 +505,7 @@ public abstract class BrightnessMappingStrategy { if (adjustment == mAutoBrightnessAdjustment) { return false; } - if (DEBUG) { + if (mLoggingEnabled) { Slog.d(TAG, "setAutoBrightnessAdjustment: " + mAutoBrightnessAdjustment + " => " + adjustment); PLOG.start("auto-brightness adjustment"); @@ -485,7 +523,7 @@ public abstract class BrightnessMappingStrategy { @Override public void addUserDataPoint(float lux, float brightness) { float unadjustedBrightness = getUnadjustedBrightness(lux); - if (DEBUG){ + if (mLoggingEnabled) { Slog.d(TAG, "addUserDataPoint: (" + lux + "," + brightness + ")"); PLOG.start("add user data point") .logPoint("user data point", lux, brightness) @@ -494,7 +532,7 @@ public abstract class BrightnessMappingStrategy { float adjustment = inferAutoBrightnessAdjustment(mMaxGamma, brightness /* desiredBrightness */, unadjustedBrightness /* currentBrightness */); - if (DEBUG) { + if (mLoggingEnabled) { Slog.d(TAG, "addUserDataPoint: " + mAutoBrightnessAdjustment + " => " + adjustment); } @@ -507,7 +545,7 @@ public abstract class BrightnessMappingStrategy { @Override public void clearUserDataPoints() { if (mUserLux != -1) { - if (DEBUG) { + if (mLoggingEnabled) { Slog.d(TAG, "clearUserDataPoints: " + mAutoBrightnessAdjustment + " => 0"); PLOG.start("clear user data points") .logPoint("user data point", mUserLux, mUserBrightness); @@ -614,7 +652,7 @@ public abstract class BrightnessMappingStrategy { mBacklightToNitsSpline = Spline.createSpline(normalizedBacklight, nits); mDefaultConfig = config; - if (DEBUG) { + if (mLoggingEnabled) { PLOG.start("physical mapping strategy"); } mConfig = config; @@ -629,7 +667,7 @@ public abstract class BrightnessMappingStrategy { if (config.equals(mConfig)) { return false; } - if (DEBUG) { + if (mLoggingEnabled) { PLOG.start("brightness configuration"); } mConfig = config; @@ -638,9 +676,17 @@ public abstract class BrightnessMappingStrategy { } @Override - public float getBrightness(float lux) { + public float getBrightness(float lux, String packageName, + @ApplicationInfo.Category int category) { float nits = mBrightnessSpline.interpolate(lux); float backlight = mNitsToBacklightSpline.interpolate(nits); + // Correct the brightness according to the current application and its category, but + // only if no user data point is set (as this will oevrride the user setting). + if (mUserLux == -1) { + backlight = correctBrightness(backlight, packageName, category); + } else if (mLoggingEnabled) { + Slog.d(TAG, "user point set, correction not applied"); + } return backlight; } @@ -655,7 +701,7 @@ public abstract class BrightnessMappingStrategy { if (adjustment == mAutoBrightnessAdjustment) { return false; } - if (DEBUG) { + if (mLoggingEnabled) { Slog.d(TAG, "setAutoBrightnessAdjustment: " + mAutoBrightnessAdjustment + " => " + adjustment); PLOG.start("auto-brightness adjustment"); @@ -673,7 +719,7 @@ public abstract class BrightnessMappingStrategy { @Override public void addUserDataPoint(float lux, float brightness) { float unadjustedBrightness = getUnadjustedBrightness(lux); - if (DEBUG){ + if (mLoggingEnabled) { Slog.d(TAG, "addUserDataPoint: (" + lux + "," + brightness + ")"); PLOG.start("add user data point") .logPoint("user data point", lux, brightness) @@ -682,7 +728,7 @@ public abstract class BrightnessMappingStrategy { float adjustment = inferAutoBrightnessAdjustment(mMaxGamma, brightness /* desiredBrightness */, unadjustedBrightness /* currentBrightness */); - if (DEBUG) { + if (mLoggingEnabled) { Slog.d(TAG, "addUserDataPoint: " + mAutoBrightnessAdjustment + " => " + adjustment); } @@ -695,7 +741,7 @@ public abstract class BrightnessMappingStrategy { @Override public void clearUserDataPoints() { if (mUserLux != -1) { - if (DEBUG) { + if (mLoggingEnabled) { Slog.d(TAG, "clearUserDataPoints: " + mAutoBrightnessAdjustment + " => 0"); PLOG.start("clear user data points") .logPoint("user data point", mUserLux, mUserBrightness); @@ -758,5 +804,21 @@ public abstract class BrightnessMappingStrategy { Spline spline = Spline.createSpline(curve.first, curve.second); return mNitsToBacklightSpline.interpolate(spline.interpolate(lux)); } + + private float correctBrightness(float brightness, String packageName, int category) { + if (packageName != null) { + BrightnessCorrection correction = mConfig.getCorrectionByPackageName(packageName); + if (correction != null) { + return correction.apply(brightness); + } + } + if (category != ApplicationInfo.CATEGORY_UNDEFINED) { + BrightnessCorrection correction = mConfig.getCorrectionByCategory(category); + if (correction != null) { + return correction.apply(brightness); + } + } + return brightness; + } } } diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index cf8d21b66417..b1ba05c035b3 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -1296,58 +1296,21 @@ public final class DisplayManagerService extends SystemService { return; } display.configureDisplayLocked(t, device, info.state == Display.STATE_OFF); - + final int viewportType; // Update the corresponding viewport. - DisplayViewport internalViewport = getInternalViewportLocked(); if ((info.flags & DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY) != 0) { - populateViewportLocked(internalViewport, display, device); - } - DisplayViewport externalViewport = getExternalViewportLocked(); - if (info.touch == DisplayDeviceInfo.TOUCH_EXTERNAL) { - populateViewportLocked(externalViewport, display, device); - } else if (!externalViewport.valid) { - // TODO (b/116850516) move this logic into InputReader - externalViewport.copyFrom(internalViewport); - externalViewport.type = DisplayViewport.VIEWPORT_EXTERNAL; - } - - if (info.touch == DisplayDeviceInfo.TOUCH_VIRTUAL && !TextUtils.isEmpty(info.uniqueId)) { - final DisplayViewport viewport = getVirtualViewportLocked(info.uniqueId); - populateViewportLocked(viewport, display, device); - } - } - - /** Get the virtual device viewport that has the specified uniqueId. - * If such viewport does not exist, create it. */ - private DisplayViewport getVirtualViewportLocked(@NonNull String uniqueId) { - DisplayViewport viewport; - final int count = mViewports.size(); - for (int i = 0; i < count; i++) { - viewport = mViewports.get(i); - if (uniqueId.equals(viewport.uniqueId)) { - if (viewport.type != VIEWPORT_VIRTUAL) { - Slog.wtf(TAG, "Found a viewport with uniqueId '" + uniqueId - + "' but it has type " + DisplayViewport.typeToString(viewport.type) - + " (expected VIRTUAL)"); - continue; - } - return viewport; - } + viewportType = VIEWPORT_INTERNAL; + } else if (info.touch == DisplayDeviceInfo.TOUCH_EXTERNAL) { + viewportType = VIEWPORT_EXTERNAL; + } else if (info.touch == DisplayDeviceInfo.TOUCH_VIRTUAL + && !TextUtils.isEmpty(info.uniqueId)) { + viewportType = VIEWPORT_VIRTUAL; + } else { + Slog.wtf(TAG, "Unable to populate viewport for display device: " + info); + return; } - viewport = new DisplayViewport(); - viewport.uniqueId = uniqueId; - viewport.type = VIEWPORT_VIRTUAL; - mViewports.add(viewport); - return viewport; - } - - private DisplayViewport getInternalViewportLocked() { - return getViewportByTypeLocked(VIEWPORT_INTERNAL); - } - - private DisplayViewport getExternalViewportLocked() { - return getViewportByTypeLocked(VIEWPORT_EXTERNAL); + populateViewportLocked(viewportType, display.getDisplayIdLocked(), device, info.uniqueId); } /** @@ -1355,35 +1318,44 @@ public final class DisplayManagerService extends SystemService { * @param viewportType - either INTERNAL or EXTERNAL * @return the viewport with the requested type */ - private DisplayViewport getViewportByTypeLocked(int viewportType) { - // Only allow a single INTERNAL or EXTERNAL viewport, which makes this function possible. - // TODO (b/116824030) allow multiple EXTERNAL viewports and remove this function. - // Creates the viewport if none exists. - if (viewportType != VIEWPORT_INTERNAL && viewportType != VIEWPORT_EXTERNAL) { + private DisplayViewport getViewportLocked(int viewportType, String uniqueId) { + if (viewportType != VIEWPORT_INTERNAL && viewportType != VIEWPORT_EXTERNAL + && viewportType != VIEWPORT_VIRTUAL) { Slog.wtf(TAG, "Cannot call getViewportByTypeLocked for type " + DisplayViewport.typeToString(viewportType)); return null; } + + // Only allow a single INTERNAL or EXTERNAL viewport by forcing their uniqueIds + // to be identical (in particular, empty). + // TODO (b/116824030) allow multiple EXTERNAL viewports and remove this function. + if (viewportType != VIEWPORT_VIRTUAL) { + uniqueId = ""; + } + DisplayViewport viewport; final int count = mViewports.size(); for (int i = 0; i < count; i++) { viewport = mViewports.get(i); - if (viewport.type == viewportType) { + if (viewport.type == viewportType && uniqueId.equals(viewport.uniqueId)) { return viewport; } } + // Creates the viewport if none exists. viewport = new DisplayViewport(); viewport.type = viewportType; + viewport.uniqueId = uniqueId; mViewports.add(viewport); return viewport; } - private static void populateViewportLocked(DisplayViewport viewport, - LogicalDisplay display, DisplayDevice device) { - viewport.valid = true; - viewport.displayId = display.getDisplayIdLocked(); + private void populateViewportLocked(int viewportType, + int displayId, DisplayDevice device, String uniqueId) { + final DisplayViewport viewport = getViewportLocked(viewportType, uniqueId); device.populateViewportLocked(viewport); + viewport.valid = true; + viewport.displayId = displayId; } private LogicalDisplay findLogicalDisplayForDeviceLocked(DisplayDevice device) { @@ -2172,6 +2144,14 @@ public final class DisplayManagerService extends SystemService { mContext.getPackageName()); } + void setAutoBrightnessLoggingEnabled(boolean enabled) { + if (mDisplayPowerController != null) { + synchronized (mSyncRoot) { + mDisplayPowerController.setAutoBrightnessLoggingEnabled(enabled); + } + } + } + private boolean validatePackageName(int uid, String packageName) { if (packageName != null) { String[] packageNames = mContext.getPackageManager().getPackagesForUid(uid); diff --git a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java index 27cad1eece09..abbfc7b18f94 100644 --- a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java +++ b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java @@ -17,14 +17,9 @@ package com.android.server.display; import android.content.Intent; -import android.os.RemoteException; -import android.os.ResultReceiver; -import android.os.ShellCallback; import android.os.ShellCommand; -import android.util.Slog; import java.io.PrintWriter; -import java.lang.NumberFormatException; class DisplayManagerShellCommand extends ShellCommand { private static final String TAG = "DisplayManagerShellCommand"; @@ -46,6 +41,10 @@ class DisplayManagerShellCommand extends ShellCommand { return setBrightness(); case "reset-brightness-configuration": return resetBrightnessConfiguration(); + case "ab-logging-enable": + return setAutoBrightnessLoggingEnabled(true); + case "ab-logging-disable": + return setAutoBrightnessLoggingEnabled(false); default: return handleDefaultCommands(cmd); } @@ -62,6 +61,10 @@ class DisplayManagerShellCommand extends ShellCommand { pw.println(" Sets the current brightness to BRIGHTNESS (a number between 0 and 1)."); pw.println(" reset-brightness-configuration"); pw.println(" Reset the brightness to its default configuration."); + pw.println(" ab-logging-enable"); + pw.println(" Enable auto-brightness logging."); + pw.println(" ab-logging-disable"); + pw.println(" Disable auto-brightness logging."); pw.println(); Intent.printIntentArgsHelp(pw , ""); } @@ -89,4 +92,9 @@ class DisplayManagerShellCommand extends ShellCommand { mService.resetBrightnessConfiguration(); return 0; } + + private int setAutoBrightnessLoggingEnabled(boolean enabled) { + mService.setAutoBrightnessLoggingEnabled(enabled); + return 0; + } } diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 249270bfda7e..c9ed9f7cea43 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -523,6 +523,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call public void onSwitchUser(@UserIdInt int newUserId) { handleSettingsChange(true /* userSwitch */); mBrightnessTracker.onSwitchUser(newUserId); + if (mAutomaticBrightnessController != null) { + mAutomaticBrightnessController.onSwitchUser(newUserId); + } } public ParceledListSlice<AmbientBrightnessDayStats> getAmbientBrightnessStats( @@ -1836,4 +1839,10 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mHandler.sendMessage(msg); } } + + void setAutoBrightnessLoggingEnabled(boolean enabled) { + if (mAutomaticBrightnessController != null) { + mAutomaticBrightnessController.setLoggingEnabled(enabled); + } + } } diff --git a/services/core/java/com/android/server/display/PersistentDataStore.java b/services/core/java/com/android/server/display/PersistentDataStore.java index 89cef621a55d..9aec43b6eaed 100644 --- a/services/core/java/com/android/server/display/PersistentDataStore.java +++ b/services/core/java/com/android/server/display/PersistentDataStore.java @@ -16,13 +16,6 @@ package com.android.server.display; -import com.android.internal.util.FastXmlSerializer; -import com.android.internal.util.XmlUtils; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; -import org.xmlpull.v1.XmlSerializer; - import android.annotation.Nullable; import android.graphics.Point; import android.hardware.display.BrightnessConfiguration; @@ -30,13 +23,20 @@ import android.hardware.display.WifiDisplay; import android.util.AtomicFile; import android.util.Slog; import android.util.SparseArray; -import android.util.Pair; import android.util.SparseLongArray; import android.util.TimeUtils; import android.util.Xml; import android.view.Display; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.FastXmlSerializer; +import com.android.internal.util.XmlUtils; + +import libcore.io.IoUtils; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; @@ -50,12 +50,9 @@ import java.io.PrintWriter; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.Objects; -import libcore.io.IoUtils; - /** * Manages persistent state recorded by the display manager service as an XML file. * Caller must acquire lock on the data store before accessing it. @@ -110,14 +107,9 @@ final class PersistentDataStore { private static final String TAG_BRIGHTNESS_CONFIGURATIONS = "brightness-configurations"; private static final String TAG_BRIGHTNESS_CONFIGURATION = "brightness-configuration"; - private static final String TAG_BRIGHTNESS_CURVE = "brightness-curve"; - private static final String TAG_BRIGHTNESS_POINT = "brightness-point"; private static final String ATTR_USER_SERIAL = "user-serial"; private static final String ATTR_PACKAGE_NAME = "package-name"; private static final String ATTR_TIME_STAMP = "timestamp"; - private static final String ATTR_LUX = "lux"; - private static final String ATTR_NITS = "nits"; - private static final String ATTR_DESCRIPTION = "description"; // Remembered Wifi display devices. private ArrayList<WifiDisplay> mRememberedWifiDisplays = new ArrayList<WifiDisplay>(); @@ -646,7 +638,8 @@ final class PersistentDataStore { } try { - BrightnessConfiguration config = loadConfigurationFromXml(parser); + BrightnessConfiguration config = + BrightnessConfiguration.loadFromXml(parser); if (userSerial >= 0 && config != null) { mConfigurations.put(userSerial, config); if (timeStamp != -1) { @@ -663,56 +656,6 @@ final class PersistentDataStore { } } - private static BrightnessConfiguration loadConfigurationFromXml(XmlPullParser parser) - throws IOException, XmlPullParserException { - final int outerDepth = parser.getDepth(); - String description = null; - Pair<float[], float[]> curve = null; - while (XmlUtils.nextElementWithin(parser, outerDepth)) { - if (TAG_BRIGHTNESS_CURVE.equals(parser.getName())) { - description = parser.getAttributeValue(null, ATTR_DESCRIPTION); - curve = loadCurveFromXml(parser); - } - } - if (curve == null) { - return null; - } - final BrightnessConfiguration.Builder builder = new BrightnessConfiguration.Builder( - curve.first, curve.second); - builder.setDescription(description); - return builder.build(); - } - - private static Pair<float[], float[]> loadCurveFromXml(XmlPullParser parser) - throws IOException, XmlPullParserException { - final int outerDepth = parser.getDepth(); - List<Float> luxLevels = new ArrayList<>(); - List<Float> nitLevels = new ArrayList<>(); - while (XmlUtils.nextElementWithin(parser, outerDepth)) { - if (TAG_BRIGHTNESS_POINT.equals(parser.getName())) { - luxLevels.add(loadFloat(parser.getAttributeValue(null, ATTR_LUX))); - nitLevels.add(loadFloat(parser.getAttributeValue(null, ATTR_NITS))); - } - } - final int N = luxLevels.size(); - float[] lux = new float[N]; - float[] nits = new float[N]; - for (int i = 0; i < N; i++) { - lux[i] = luxLevels.get(i); - nits[i] = nitLevels.get(i); - } - return Pair.create(lux, nits); - } - - private static float loadFloat(String val) { - try { - return Float.parseFloat(val); - } catch (NullPointerException | NumberFormatException e) { - Slog.e(TAG, "Failed to parse float loading brightness config", e); - return Float.NEGATIVE_INFINITY; - } - } - public void saveToXml(XmlSerializer serializer) throws IOException { for (int i = 0; i < mConfigurations.size(); i++) { final int userSerial = mConfigurations.keyAt(i); @@ -728,27 +671,11 @@ final class PersistentDataStore { if (timestamp != -1) { serializer.attribute(null, ATTR_TIME_STAMP, Long.toString(timestamp)); } - saveConfigurationToXml(serializer, config); + config.saveToXml(serializer); serializer.endTag(null, TAG_BRIGHTNESS_CONFIGURATION); } } - private static void saveConfigurationToXml(XmlSerializer serializer, - BrightnessConfiguration config) throws IOException { - serializer.startTag(null, TAG_BRIGHTNESS_CURVE); - if (config.getDescription() != null) { - serializer.attribute(null, ATTR_DESCRIPTION, config.getDescription()); - } - final Pair<float[], float[]> curve = config.getCurve(); - for (int i = 0; i < curve.first.length; i++) { - serializer.startTag(null, TAG_BRIGHTNESS_POINT); - serializer.attribute(null, ATTR_LUX, Float.toString(curve.first[i])); - serializer.attribute(null, ATTR_NITS, Float.toString(curve.second[i])); - serializer.endTag(null, TAG_BRIGHTNESS_POINT); - } - serializer.endTag(null, TAG_BRIGHTNESS_CURVE); - } - public void dump(final PrintWriter pw, final String prefix) { for (int i = 0; i < mConfigurations.size(); i++) { final int userSerial = mConfigurations.keyAt(i); diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index c0d3fdfb8f91..f468c0bf6652 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -81,8 +81,10 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Locale; +import java.util.Map; import libcore.util.EmptyArray; /** @@ -94,6 +96,31 @@ public class HdmiControlService extends SystemService { private final Locale HONG_KONG = new Locale("zh", "HK"); private final Locale MACAU = new Locale("zh", "MO"); + private static final Map<String, String> mTerminologyToBibliographicMap; + static { + mTerminologyToBibliographicMap = new HashMap<>(); + // NOTE: (TERMINOLOGY_CODE, BIBLIOGRAPHIC_CODE) + mTerminologyToBibliographicMap.put("sqi", "alb"); // Albanian + mTerminologyToBibliographicMap.put("hye", "arm"); // Armenian + mTerminologyToBibliographicMap.put("eus", "baq"); // Basque + mTerminologyToBibliographicMap.put("mya", "bur"); // Burmese + mTerminologyToBibliographicMap.put("ces", "cze"); // Czech + mTerminologyToBibliographicMap.put("nld", "dut"); // Dutch + mTerminologyToBibliographicMap.put("kat", "geo"); // Georgian + mTerminologyToBibliographicMap.put("deu", "ger"); // German + mTerminologyToBibliographicMap.put("ell", "gre"); // Greek + mTerminologyToBibliographicMap.put("fra", "fre"); // French + mTerminologyToBibliographicMap.put("isl", "ice"); // Icelandic + mTerminologyToBibliographicMap.put("mkd", "mac"); // Macedonian + mTerminologyToBibliographicMap.put("mri", "mao"); // Maori + mTerminologyToBibliographicMap.put("msa", "may"); // Malay + mTerminologyToBibliographicMap.put("fas", "per"); // Persian + mTerminologyToBibliographicMap.put("ron", "rum"); // Romanian + mTerminologyToBibliographicMap.put("slk", "slo"); // Slovak + mTerminologyToBibliographicMap.put("bod", "tib"); // Tibetan + mTerminologyToBibliographicMap.put("cym", "wel"); // Welsh + } + static final String PERMISSION = "android.permission.HDMI_CEC"; // The reason code to initiate initializeCec(). @@ -177,7 +204,18 @@ public class HdmiControlService extends SystemService { // Chinese used in Taiwan/Hong Kong/Macau. return "chi"; } else { - return locale.getISO3Language(); + String language = locale.getISO3Language(); + + // locale.getISO3Language() returns terminology code and need to + // send it as bibliographic code instead since the Bibliographic + // codes of ISO/FDIS 639-2 shall be used. + // NOTE: Chinese also has terminology/bibliographic code "zho" and "chi" + // But, as it depends on the locale, is not handled here. + if (mTerminologyToBibliographicMap.containsKey(language)) { + language = mTerminologyToBibliographicMap.get(language); + } + + return language; } } } diff --git a/services/core/java/com/android/server/infra/ServiceNameResolver.java b/services/core/java/com/android/server/infra/ServiceNameResolver.java index 205d40b9e5cd..fd87e3d32b1d 100644 --- a/services/core/java/com/android/server/infra/ServiceNameResolver.java +++ b/services/core/java/com/android/server/infra/ServiceNameResolver.java @@ -18,6 +18,8 @@ package com.android.server.infra; import android.annotation.NonNull; import android.annotation.Nullable; +import com.android.internal.infra.AbstractRemoteService; + import java.io.PrintWriter; /** diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 67293b9c9ea0..3552e6635738 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -1685,6 +1685,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub return getInputMethodList(false /* isVrOnly */); } + @Override public List<InputMethodInfo> getVrInputMethodList() { return getInputMethodList(true /* isVrOnly */); } diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java index 93b66208e752..b7bb2c610e62 100644 --- a/services/core/java/com/android/server/media/MediaSessionService.java +++ b/services/core/java/com/android/server/media/MediaSessionService.java @@ -27,8 +27,10 @@ import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; +import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ServiceInfo; import android.content.pm.UserInfo; import android.database.ContentObserver; import android.media.AudioManager; @@ -634,11 +636,16 @@ public class MediaSessionService extends SystemService implements Monitor { * <p>The contents of this object is guarded by {@link #mLock}. */ final class FullUserRecord implements MediaSessionStack.OnMediaButtonSessionChangedListener { + public static final int COMPONENT_TYPE_BROADCAST = 0; + public static final int COMPONENT_TYPE_ACTIVITY = 1; + public static final int COMPONENT_TYPE_SERVICE = 2; private static final String COMPONENT_NAME_USER_ID_DELIM = ","; + private final int mFullUserId; private final MediaSessionStack mPriorityStack; private PendingIntent mLastMediaButtonReceiver; private ComponentName mRestoredMediaButtonReceiver; + private int mRestoredMediaButtonReceiverComponentType; private int mRestoredMediaButtonReceiverUserId; private IOnVolumeKeyLongPressListener mOnVolumeKeyLongPressListener; @@ -655,17 +662,23 @@ public class MediaSessionService extends SystemService implements Monitor { mFullUserId = fullUserId; mPriorityStack = new MediaSessionStack(mAudioPlayerStateMonitor, this); // Restore the remembered media button receiver before the boot. - String mediaButtonReceiver = Settings.Secure.getStringForUser(mContentResolver, + String mediaButtonReceiverInfo = Settings.Secure.getStringForUser(mContentResolver, Settings.System.MEDIA_BUTTON_RECEIVER, mFullUserId); - if (mediaButtonReceiver == null) { + if (mediaButtonReceiverInfo == null) { return; } - String[] tokens = mediaButtonReceiver.split(COMPONENT_NAME_USER_ID_DELIM); - if (tokens == null || tokens.length != 2) { + String[] tokens = mediaButtonReceiverInfo.split(COMPONENT_NAME_USER_ID_DELIM); + if (tokens == null || (tokens.length != 2 && tokens.length != 3)) { return; } mRestoredMediaButtonReceiver = ComponentName.unflattenFromString(tokens[0]); mRestoredMediaButtonReceiverUserId = Integer.parseInt(tokens[1]); + if (tokens.length == 3) { + mRestoredMediaButtonReceiverComponentType = Integer.parseInt(tokens[2]); + } else { + mRestoredMediaButtonReceiverComponentType = + getComponentType(mRestoredMediaButtonReceiver); + } } public void destroySessionsForUserLocked(int userId) { @@ -696,6 +709,8 @@ public class MediaSessionService extends SystemService implements Monitor { pw.println(indent + "Callback: " + mCallback); pw.println(indent + "Last MediaButtonReceiver: " + mLastMediaButtonReceiver); pw.println(indent + "Restored MediaButtonReceiver: " + mRestoredMediaButtonReceiver); + pw.println(indent + "Restored MediaButtonReceiverComponentType: " + + mRestoredMediaButtonReceiverComponentType); mPriorityStack.dump(pw, indent); } @@ -722,17 +737,21 @@ public class MediaSessionService extends SystemService implements Monitor { PendingIntent receiver = record.getMediaButtonReceiver(); mLastMediaButtonReceiver = receiver; mRestoredMediaButtonReceiver = null; - String componentName = ""; + + String mediaButtonReceiverInfo = ""; if (receiver != null) { ComponentName component = receiver.getIntent().getComponent(); if (component != null && record.getPackageName().equals(component.getPackageName())) { - componentName = component.flattenToString(); + String componentName = component.flattenToString(); + int componentType = getComponentType(component); + mediaButtonReceiverInfo = String.join(COMPONENT_NAME_USER_ID_DELIM, + componentName, String.valueOf(record.getUserId()), + String.valueOf(componentType)); } } Settings.Secure.putStringForUser(mContentResolver, - Settings.System.MEDIA_BUTTON_RECEIVER, - componentName + COMPONENT_NAME_USER_ID_DELIM + record.getUserId(), + Settings.System.MEDIA_BUTTON_RECEIVER, mediaButtonReceiverInfo, mFullUserId); } @@ -762,6 +781,32 @@ public class MediaSessionService extends SystemService implements Monitor { return isGlobalPriorityActiveLocked() ? mGlobalPrioritySession : mPriorityStack.getMediaButtonSession(); } + + private int getComponentType(ComponentName componentName) { + PackageManager pm = getContext().getPackageManager(); + try { + ActivityInfo activityInfo = pm.getActivityInfo(componentName, + PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE + | PackageManager.GET_ACTIVITIES); + if (activityInfo != null) { + return COMPONENT_TYPE_ACTIVITY; + } + } catch (NameNotFoundException e) { + } + try { + ServiceInfo serviceInfo = pm.getServiceInfo(componentName, + PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE + | PackageManager.GET_SERVICES); + if (serviceInfo != null) { + return COMPONENT_TYPE_SERVICE; + } + } catch (NameNotFoundException e) { + } + // Pick legacy behavior for BroadcastReceiver or unknown. + return COMPONENT_TYPE_BROADCAST; + } } final class SessionsListenerRecord implements IBinder.DeathRecipient { @@ -1580,14 +1625,32 @@ public class MediaSessionService extends SystemService implements Monitor { } else { ComponentName receiver = mCurrentFullUserRecord.mRestoredMediaButtonReceiver; + int componentType = mCurrentFullUserRecord + .mRestoredMediaButtonReceiverComponentType; + UserHandle userHandle = UserHandle.of(mCurrentFullUserRecord + .mRestoredMediaButtonReceiverUserId); if (DEBUG_KEY_EVENT) { Log.d(TAG, "Sending " + keyEvent + " to the restored intent " - + receiver); + + receiver + ", type=" + componentType); } mediaButtonIntent.setComponent(receiver); - getContext().sendBroadcastAsUser(mediaButtonIntent, - UserHandle.of(mCurrentFullUserRecord - .mRestoredMediaButtonReceiverUserId)); + try { + switch (componentType) { + case FullUserRecord.COMPONENT_TYPE_ACTIVITY: + getContext().startActivityAsUser(mediaButtonIntent, userHandle); + break; + case FullUserRecord.COMPONENT_TYPE_SERVICE: + getContext().startForegroundServiceAsUser(mediaButtonIntent, + userHandle); + break; + default: + // Legacy behavior for other cases. + getContext().sendBroadcastAsUser(mediaButtonIntent, userHandle); + } + } catch (Exception e) { + Log.w(TAG, "Error sending media button to the restored intent " + + receiver + ", type=" + componentType, e); + } if (mCurrentFullUserRecord.mCallback != null) { mCurrentFullUserRecord.mCallback .onMediaKeyEventDispatchedToMediaButtonReceiver( diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 405edd216f0f..53ee16b1b585 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -842,25 +842,13 @@ public class NotificationManagerService extends SystemService { // Report to usage stats that notification was made visible if (DBG) Slog.d(TAG, "Marking notification as visible " + nv.key); reportSeen(r); - - // If the newly visible notification has smart suggestions - // then log that the user has seen them. - if ((r.getNumSmartRepliesAdded() > 0 || r.getNumSmartActionsAdded() > 0) - && !r.hasSeenSmartReplies()) { - r.setSeenSmartReplies(true); - LogMaker logMaker = r.getLogMaker() - .setCategory(MetricsEvent.SMART_REPLY_VISIBLE) - .addTaggedData(MetricsEvent.NOTIFICATION_SMART_REPLY_COUNT, - r.getNumSmartRepliesAdded()) - .addTaggedData(MetricsEvent.NOTIFICATION_SMART_ACTION_COUNT, - r.getNumSmartActionsAdded()) - .addTaggedData( - MetricsEvent.NOTIFICATION_SMART_SUGGESTION_ASSISTANT_GENERATED, - r.getSuggestionsGeneratedByAssistant()); - mMetricsLogger.write(logMaker); - } } r.setVisibility(true, nv.rank, nv.count); + // hasBeenVisiblyExpanded must be called after updating the expansion state of + // the NotificationRecord to ensure the expansion state is up-to-date. + if (r.hasBeenVisiblyExpanded()) { + logSmartSuggestionsVisible(r); + } maybeRecordInterruptionLocked(r); nv.recycle(); } @@ -884,6 +872,11 @@ public class NotificationManagerService extends SystemService { NotificationRecord r = mNotificationsByKey.get(key); if (r != null) { r.stats.onExpansionChanged(userAction, expanded); + // hasBeenVisiblyExpanded must be called after updating the expansion state of + // the NotificationRecord to ensure the expansion state is up-to-date. + if (r.hasBeenVisiblyExpanded()) { + logSmartSuggestionsVisible(r); + } final long now = System.currentTimeMillis(); if (userAction) { MetricsLogger.action(r.getItemLogMaker() @@ -961,6 +954,26 @@ public class NotificationManagerService extends SystemService { } }; + @VisibleForTesting + void logSmartSuggestionsVisible(NotificationRecord r) { + // If the newly visible notification has smart suggestions + // then log that the user has seen them. + if ((r.getNumSmartRepliesAdded() > 0 || r.getNumSmartActionsAdded() > 0) + && !r.hasSeenSmartReplies()) { + r.setSeenSmartReplies(true); + LogMaker logMaker = r.getLogMaker() + .setCategory(MetricsEvent.SMART_REPLY_VISIBLE) + .addTaggedData(MetricsEvent.NOTIFICATION_SMART_REPLY_COUNT, + r.getNumSmartRepliesAdded()) + .addTaggedData(MetricsEvent.NOTIFICATION_SMART_ACTION_COUNT, + r.getNumSmartActionsAdded()) + .addTaggedData( + MetricsEvent.NOTIFICATION_SMART_SUGGESTION_ASSISTANT_GENERATED, + r.getSuggestionsGeneratedByAssistant()); + mMetricsLogger.write(logMaker); + } + } + @GuardedBy("mNotificationLock") private void clearSoundLocked() { mSoundNotificationKey = null; @@ -2279,6 +2292,26 @@ public class NotificationManagerService extends SystemService { } @Override + public boolean areAppOverlaysAllowed(String pkg) { + return areAppOverlaysAllowedForPackage(pkg, Binder.getCallingUid()); + } + + @Override + public boolean areAppOverlaysAllowedForPackage(String pkg, int uid) { + checkCallerIsSystemOrSameApp(pkg); + + return mPreferencesHelper.areAppOverlaysAllowed(pkg, uid); + } + + @Override + public void setAppOverlaysAllowed(String pkg, int uid, boolean allowed) { + checkCallerIsSystem(); + + mPreferencesHelper.setAppOverlaysAllowed(pkg, uid, allowed); + handleSavePolicyFile(); + } + + @Override public int getPackageImportance(String pkg) { checkCallerIsSystemOrSameApp(pkg); return mPreferencesHelper.getImportance(pkg, Binder.getCallingUid()); @@ -2647,6 +2680,10 @@ public class NotificationManagerService extends SystemService { * Note that since notification posting is done asynchronously, this will not return * notifications that are in the process of being posted. * + * From {@link Build.VERSION_CODES#Q}, will also return notifications you've posted as + * an app's notification delegate via + * {@link NotificationManager#notifyAsPackage(String, String, int, Notification)}. + * * @returns A list of all the package's notifications, in natural order. */ @Override @@ -2689,16 +2726,18 @@ public class NotificationManagerService extends SystemService { private StatusBarNotification sanitizeSbn(String pkg, int userId, StatusBarNotification sbn) { - if (sbn.getPackageName().equals(pkg) && sbn.getUserId() == userId) { - // We could pass back a cloneLight() but clients might get confused and - // try to send this thing back to notify() again, which would not work - // very well. - return new StatusBarNotification( - sbn.getPackageName(), - sbn.getOpPkg(), - sbn.getId(), sbn.getTag(), sbn.getUid(), sbn.getInitialPid(), - sbn.getNotification().clone(), - sbn.getUser(), sbn.getOverrideGroupKey(), sbn.getPostTime()); + if (sbn.getUserId() == userId) { + if (sbn.getPackageName().equals(pkg) || sbn.getOpPkg().equals(pkg)) { + // We could pass back a cloneLight() but clients might get confused and + // try to send this thing back to notify() again, which would not work + // very well. + return new StatusBarNotification( + sbn.getPackageName(), + sbn.getOpPkg(), + sbn.getId(), sbn.getTag(), sbn.getUid(), sbn.getInitialPid(), + sbn.getNotification().clone(), + sbn.getUser(), sbn.getOverrideGroupKey(), sbn.getPostTime()); + } } return null; } @@ -4403,7 +4442,7 @@ public class NotificationManagerService extends SystemService { notification.flags &= ~Notification.FLAG_CAN_COLORIZE; } - if (ai.targetSdkVersion >= Build.VERSION_CODES.Q) { + if (notification.fullScreenIntent != null && ai.targetSdkVersion >= Build.VERSION_CODES.Q) { int fullscreenIntentPermission = mPackageManagerClient.checkPermission( android.Manifest.permission.USE_FULL_SCREEN_INTENT, pkg); if (fullscreenIntentPermission != PERMISSION_GRANTED) { diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java index 89ec38db99e5..1f8893c56874 100644 --- a/services/core/java/com/android/server/notification/NotificationRecord.java +++ b/services/core/java/com/android/server/notification/NotificationRecord.java @@ -1166,6 +1166,13 @@ public final class NotificationRecord { mHasSeenSmartReplies = hasSeenSmartReplies; } + /** + * Returns whether this notification has been visible and expanded at the same time. + */ + public boolean hasBeenVisiblyExpanded() { + return stats.hasBeenVisiblyExpanded(); + } + public void setSystemGeneratedSmartActions( ArrayList<Notification.Action> systemGeneratedSmartActions) { mSystemGeneratedSmartActions = systemGeneratedSmartActions; diff --git a/services/core/java/com/android/server/notification/NotificationUsageStats.java b/services/core/java/com/android/server/notification/NotificationUsageStats.java index e40dad6e83f1..d630b9ab54d6 100644 --- a/services/core/java/com/android/server/notification/NotificationUsageStats.java +++ b/services/core/java/com/android/server/notification/NotificationUsageStats.java @@ -916,6 +916,13 @@ public class NotificationUsageStats { updateVisiblyExpandedStats(); } + /** + * Returns whether this notification has been visible and expanded at the same. + */ + public boolean hasBeenVisiblyExpanded() { + return posttimeToFirstVisibleExpansionMs >= 0; + } + private void updateVisiblyExpandedStats() { long elapsedNowMs = SystemClock.elapsedRealtime(); if (isExpanded && isVisible) { diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index eb46d53b5157..7c0e0b0983fb 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -80,6 +80,7 @@ public class PreferencesHelper implements RankingConfig { private static final String ATT_NAME = "name"; private static final String ATT_UID = "uid"; private static final String ATT_ID = "id"; + private static final String ATT_APP_OVERLAY = "overlay"; private static final String ATT_PRIORITY = "priority"; private static final String ATT_VISIBILITY = "visibility"; private static final String ATT_IMPORTANCE = "importance"; @@ -92,6 +93,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; private static final boolean DEFAULT_SHOW_BADGE = true; + private static final boolean DEFAULT_ALLOW_APP_OVERLAY = true; /** * Default value for what fields are user locked. See {@link LockableAppFields} for all lockable * fields. @@ -104,6 +106,7 @@ public class PreferencesHelper implements RankingConfig { @IntDef({LockableAppFields.USER_LOCKED_IMPORTANCE}) public @interface LockableAppFields { int USER_LOCKED_IMPORTANCE = 0x00000001; + int USER_LOCKED_APP_OVERLAY = 0x00000002; } // pkg|uid => PackagePreferences @@ -169,7 +172,9 @@ public class PreferencesHelper implements RankingConfig { XmlUtils.readIntAttribute( parser, ATT_VISIBILITY, DEFAULT_VISIBILITY), XmlUtils.readBooleanAttribute( - parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE)); + parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE), + XmlUtils.readBooleanAttribute( + parser, ATT_APP_OVERLAY, DEFAULT_ALLOW_APP_OVERLAY)); r.importance = XmlUtils.readIntAttribute( parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE); r.priority = XmlUtils.readIntAttribute( @@ -264,11 +269,12 @@ public class PreferencesHelper implements RankingConfig { private PackagePreferences getOrCreatePackagePreferences(String pkg, int uid) { return getOrCreatePackagePreferences(pkg, uid, - DEFAULT_IMPORTANCE, DEFAULT_PRIORITY, DEFAULT_VISIBILITY, DEFAULT_SHOW_BADGE); + DEFAULT_IMPORTANCE, DEFAULT_PRIORITY, DEFAULT_VISIBILITY, DEFAULT_SHOW_BADGE, + DEFAULT_ALLOW_APP_OVERLAY); } private PackagePreferences getOrCreatePackagePreferences(String pkg, int uid, int importance, - int priority, int visibility, boolean showBadge) { + int priority, int visibility, boolean showBadge, boolean allowAppOverlay) { final String key = packagePreferencesKey(pkg, uid); synchronized (mPackagePreferences) { PackagePreferences @@ -282,6 +288,7 @@ public class PreferencesHelper implements RankingConfig { r.priority = priority; r.visibility = visibility; r.showBadge = showBadge; + r.appOverlay = allowAppOverlay; try { createDefaultChannelIfNeeded(r); @@ -382,7 +389,8 @@ public class PreferencesHelper implements RankingConfig { || r.lockedAppFields != DEFAULT_LOCKED_APP_FIELDS || r.channels.size() > 0 || r.groups.size() > 0 - || r.delegate != null; + || r.delegate != null + || r.appOverlay != DEFAULT_ALLOW_APP_OVERLAY; if (hasNonDefaultSettings) { out.startTag(null, TAG_PACKAGE); out.attribute(null, ATT_NAME, r.pkg); @@ -395,6 +403,9 @@ public class PreferencesHelper implements RankingConfig { if (r.visibility != DEFAULT_VISIBILITY) { out.attribute(null, ATT_VISIBILITY, Integer.toString(r.visibility)); } + if (r.appOverlay != DEFAULT_ALLOW_APP_OVERLAY) { + out.attribute(null, ATT_APP_OVERLAY, Boolean.toString(r.appOverlay)); + } out.attribute(null, ATT_SHOW_BADGE, Boolean.toString(r.showBadge)); out.attribute(null, ATT_APP_USER_LOCKED_FIELDS, Integer.toString(r.lockedAppFields)); @@ -439,6 +450,20 @@ public class PreferencesHelper implements RankingConfig { out.endTag(null, TAG_RANKING); } + public void setAppOverlaysAllowed(String pkg, int uid, boolean allowed) { + PackagePreferences p = getOrCreatePackagePreferences(pkg, uid); + p.appOverlay = allowed; + p.lockedAppFields = p.lockedAppFields | LockableAppFields.USER_LOCKED_APP_OVERLAY; + } + + public boolean areAppOverlaysAllowed(String pkg, int uid) { + return getOrCreatePackagePreferences(pkg, uid).appOverlay; + } + + public int getAppLockedFields(String pkg, int uid) { + return getOrCreatePackagePreferences(pkg, uid).lockedAppFields; + } + /** * Gets importance. */ @@ -512,7 +537,6 @@ public class PreferencesHelper implements RankingConfig { // apps can't update the blocked status or app overlay permission if (fromTargetApp) { group.setBlocked(oldGroup.isBlocked()); - group.setAllowAppOverlay(oldGroup.canOverlayApps()); group.unlockFields(group.getUserLockedFields()); group.lockFields(oldGroup.getUserLockedFields()); } else { @@ -521,9 +545,6 @@ public class PreferencesHelper implements RankingConfig { group.lockFields(NotificationChannelGroup.USER_LOCKED_BLOCKED_STATE); updateChannelsBypassingDnd(mContext.getUserId()); } - if (group.canOverlayApps() != oldGroup.canOverlayApps()) { - group.lockFields(NotificationChannelGroup.USER_LOCKED_ALLOW_APP_OVERLAY); - } } } r.groups.put(group.getId(), group); @@ -1581,6 +1602,7 @@ public class PreferencesHelper implements RankingConfig { int priority = DEFAULT_PRIORITY; int visibility = DEFAULT_VISIBILITY; boolean showBadge = DEFAULT_SHOW_BADGE; + boolean appOverlay = DEFAULT_ALLOW_APP_OVERLAY; int lockedAppFields = DEFAULT_LOCKED_APP_FIELDS; Delegate delegate = null; diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java index 51619cf940a8..164af38be5a6 100644 --- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java +++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java @@ -143,6 +143,13 @@ public final class DefaultPermissionGrantPolicy { LOCATION_PERMISSIONS.add(Manifest.permission.ACCESS_COARSE_LOCATION); } + private static final Set<String> ALWAYS_LOCATION_PERMISSIONS = new ArraySet<>(); + static { + ALWAYS_LOCATION_PERMISSIONS.add(Manifest.permission.ACCESS_FINE_LOCATION); + ALWAYS_LOCATION_PERMISSIONS.add(Manifest.permission.ACCESS_COARSE_LOCATION); + ALWAYS_LOCATION_PERMISSIONS.add(Manifest.permission.ACCESS_BACKGROUND_LOCATION); + } + private static final Set<String> ACTIVITY_RECOGNITION_PERMISSIONS = new ArraySet<>(); static { ACTIVITY_RECOGNITION_PERMISSIONS.add(Manifest.permission.ACTIVITY_RECOGNITION); @@ -690,7 +697,7 @@ public final class DefaultPermissionGrantPolicy { // Companion devices grantSystemFixedPermissionsToSystemPackage( CompanionDeviceManager.COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME, userId, - LOCATION_PERMISSIONS); + ALWAYS_LOCATION_PERMISSIONS); // Ringtone Picker grantSystemFixedPermissionsToSystemPackage( diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index 02d8c0bcb584..fc21adbdcca3 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -718,12 +718,12 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D disabledData += " }"; final UiState state = getUiState(displayId); - Log.d(TAG, "disabledlocked (b/113914868): displayId=" + displayId + "net1=" + net1 + Log.d(TAG, "disabledlocked (b/113914868): displayId=" + displayId + ", net1=" + net1 + ", mDisabled1=" + state.mDisabled1 + ", token=" + token + ", mDisableRecords=" + mDisableRecords.size() + " => " + disabledData); } final UiState state = getUiState(displayId); - if (state.disableEquals(net1, net2)) { + if (!state.disableEquals(net1, net2)) { state.setDisabled(net1, net2); mHandler.post(() -> mNotificationDelegate.onSetDisabled(net1)); if (mBar != null) { diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 2157c9950457..7c61e373c53b 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -900,12 +900,21 @@ public class WallpaperManagerService extends IWallpaperManager.Stub if (mLastWallpaper == null || mFallbackWallpaper == null) return; final WallpaperConnection systemConnection = mLastWallpaper.connection; final WallpaperConnection fallbackConnection = mFallbackWallpaper.connection; + if (fallbackConnection == null) { + Slog.w(TAG, "Fallback wallpaper connection has not been created yet!!"); + return; + } if (supportsMultiDisplay(systemConnection) && fallbackConnection.getConnectedEngineSize() != 0) { fallbackConnection.forEachDisplayConnector( WallpaperConnection.DisplayConnector::disconnectLocked); fallbackConnection.mDisplayConnector.clear(); } else { + // TODO(b/121181553) Handle wallpaper service disconnect case. + if (fallbackConnection.mService == null) { + Slog.w(TAG, "There is no fallback wallpaper service"); + return; + } fallbackConnection.appendConnectorWithCondition(display -> fallbackConnection.isUsableDisplay(display) && display.getDisplayId() != DEFAULT_DISPLAY diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 5f00bcc26984..4d8440a899a3 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -42,6 +42,7 @@ import static android.app.WindowConfiguration.activityTypeToString; import static android.content.Intent.ACTION_MAIN; import static android.content.Intent.CATEGORY_HOME; import static android.content.Intent.CATEGORY_LAUNCHER; +import static android.content.Intent.CATEGORY_SECONDARY_HOME; import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS; import static android.content.Intent.FLAG_ACTIVITY_NO_HISTORY; import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION; @@ -1178,7 +1179,8 @@ final class ActivityRecord extends ConfigurationContainer { private boolean isHomeIntent(Intent intent) { return ACTION_MAIN.equals(intent.getAction()) - && intent.hasCategory(CATEGORY_HOME) + && (intent.hasCategory(CATEGORY_HOME) + || intent.hasCategory(CATEGORY_SECONDARY_HOME)) && intent.getCategories().size() == 1 && intent.getData() == null && intent.getType() == null; diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java index 2663d997162c..6755c7363ede 100644 --- a/services/core/java/com/android/server/wm/ActivityStack.java +++ b/services/core/java/com/android/server/wm/ActivityStack.java @@ -4914,7 +4914,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai // Update override configurations of all tasks in the stack. final Rect taskBounds = tempTaskBounds != null ? tempTaskBounds : bounds; - final Rect insetBounds = tempTaskInsetBounds != null ? tempTaskInsetBounds : taskBounds; mTmpBounds.clear(); mTmpInsetBounds.clear(); @@ -4922,7 +4921,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai for (int i = mTaskHistory.size() - 1; i >= 0; i--) { final TaskRecord task = mTaskHistory.get(i); if (task.isResizeable()) { - task.updateOverrideConfiguration(taskBounds, insetBounds); + task.updateOverrideConfiguration(taskBounds, tempTaskInsetBounds); } if (task.hasDisplayedBounds()) { diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 986115726efb..f662d0cc7f23 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -5475,6 +5475,31 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { return intent; } + /** + * Return the intent set with {@link Intent#CATEGORY_SECONDARY_HOME} to resolve secondary home + * activities. + * + * @param preferredPackage Specify a preferred package name, otherwise use secondary home + * component defined in config_secondaryHomeComponent. + * @return the intent set with {@link Intent#CATEGORY_SECONDARY_HOME} + */ + Intent getSecondaryHomeIntent(String preferredPackage) { + final Intent intent = new Intent(mTopAction, mTopData != null ? Uri.parse(mTopData) : null); + if (preferredPackage == null) { + // Using the component stored in config if no package name. + final String secondaryHomeComponent = mContext.getResources().getString( + com.android.internal.R.string.config_secondaryHomeComponent); + intent.setComponent(ComponentName.unflattenFromString(secondaryHomeComponent)); + } else { + intent.setPackage(preferredPackage); + } + intent.addFlags(Intent.FLAG_DEBUG_TRIAGED_MISSING); + if (mFactoryTest != FactoryTest.FACTORY_TEST_LOW_LEVEL) { + intent.addCategory(Intent.CATEGORY_SECONDARY_HOME); + } + return intent; + } + ApplicationInfo getAppInfoForUser(ApplicationInfo info, int userId) { if (info == null) return null; ApplicationInfo newInfo = new ApplicationInfo(info); diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java index 8d49bf374baf..d8b2b5200f0c 100644 --- a/services/core/java/com/android/server/wm/AppWindowToken.java +++ b/services/core/java/com/android/server/wm/AppWindowToken.java @@ -1837,23 +1837,23 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree return false; } } + } - if (transferStartingWindow(transferFrom)) { - return true; - } - - // There is no existing starting window, and we don't want to create a splash screen, so - // that's it! - if (type != STARTING_WINDOW_TYPE_SPLASH_SCREEN) { - return false; - } + if (transferStartingWindow(transferFrom)) { + return true; + } - if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "Creating SplashScreenStartingData"); - startingData = new SplashScreenStartingData(mWmService, pkg, - theme, compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags, - getMergedOverrideConfiguration()); - scheduleAddStartingWindow(); + // There is no existing starting window, and we don't want to create a splash screen, so + // that's it! + if (type != STARTING_WINDOW_TYPE_SPLASH_SCREEN) { + return false; } + + if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "Creating SplashScreenStartingData"); + startingData = new SplashScreenStartingData(mWmService, pkg, + theme, compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags, + getMergedOverrideConfiguration()); + scheduleAddStartingWindow(); return true; } diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java index ec2d6737ee51..cb9cbd6465fb 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimation.java +++ b/services/core/java/com/android/server/wm/RecentsAnimation.java @@ -134,10 +134,8 @@ class RecentsAnimation implements RecentsAnimationCallbacks, mWindowManager.deferSurfaceLayout(); try { - final int userId = mService.getCurrentUserId(); - // Kick off the assist data request in the background before showing the target activity - requestAssistData(recentsComponent, recentsUid, assistDataReceiver, userId); + requestAssistData(recentsComponent, recentsUid, assistDataReceiver); if (hasExistingActivity) { // Move the recents activity into place for the animation if it is not top most @@ -164,7 +162,7 @@ class RecentsAnimation implements RecentsAnimationCallbacks, .setCallingUid(recentsUid) .setCallingPackage(recentsComponent.getPackageName()) .setActivityOptions(SafeActivityOptions.fromBundle(options.toBundle())) - .setMayWait(userId) + .setMayWait(mService.getCurrentUserId()) .execute(); // Move the recents activity into place for the animation @@ -221,7 +219,7 @@ class RecentsAnimation implements RecentsAnimationCallbacks, * Requests assist data for the top visible activities. */ private void requestAssistData(ComponentName recentsComponent, int recentsUid, - @Deprecated IAssistDataReceiver assistDataReceiver, int userId) { + @Deprecated IAssistDataReceiver assistDataReceiver) { final AppOpsManager appOpsManager = (AppOpsManager) mService.mContext.getSystemService(Context.APP_OPS_SERVICE); final List<IBinder> topActivities = @@ -237,8 +235,9 @@ class RecentsAnimation implements RecentsAnimationCallbacks, final ContentCaptureManagerInternal imService = LocalServices.getService(ContentCaptureManagerInternal.class); final IBinder activityToken = topActivities.get(activityIndex); - if (imService == null - || !imService.sendActivityAssistData(userId, activityToken, data)) { + 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); } @@ -263,7 +262,10 @@ class RecentsAnimation implements RecentsAnimationCallbacks, int activityCount) { // Try to notify the intelligence service final IBinder activityToken = topActivities.get(activityIndex); - imService.sendActivityAssistData(userId, activityToken, data); + final ActivityRecord r = ActivityRecord.forTokenLocked(activityToken); + if (r != null) { + imService.sendActivityAssistData(r.mUserId, activityToken, data); + } } }; } diff --git a/services/core/java/com/android/server/wm/RootActivityContainer.java b/services/core/java/com/android/server/wm/RootActivityContainer.java index d0144fdf670a..8ec97c5117f1 100644 --- a/services/core/java/com/android/server/wm/RootActivityContainer.java +++ b/services/core/java/com/android/server/wm/RootActivityContainer.java @@ -90,17 +90,18 @@ import android.graphics.Rect; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManagerInternal; import android.hardware.power.V1_0.PowerHint; -import android.os.Build; import android.os.FactoryTest; import android.os.IBinder; import android.os.RemoteException; import android.os.SystemClock; import android.os.Trace; import android.os.UserHandle; +import android.provider.Settings; import android.service.voice.IVoiceInteractionSession; import android.util.ArraySet; import android.util.DisplayMetrics; import android.util.IntArray; +import android.util.Pair; import android.util.Slog; import android.util.SparseArray; import android.util.SparseIntArray; @@ -110,6 +111,7 @@ import android.view.Display; import android.view.DisplayInfo; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.app.ResolverActivity; import com.android.server.LocalServices; import com.android.server.am.ActivityManagerService; import com.android.server.am.AppTimeTracker; @@ -346,35 +348,53 @@ class RootActivityContainer extends ConfigurationContainer } /** - * This starts home activity on displays that can have system decorations and only if the - * home activity can have multiple instances. + * This starts home activity on displays that can have system decorations based on displayId - + * Default display always use primary home component. + * For Secondary displays, the home activity must have category SECONDARY_HOME and then resolves + * according to the priorities listed below. + * - If default home is not set, always use the secondary home defined in the config. + * - Use currently selected primary home activity. + * - Use the activity in the same package as currently selected primary home activity. + * If there are multiple activities matched, use first one. + * - Use the secondary home defined in the config. */ boolean startHomeOnDisplay(int userId, String reason, int displayId) { - final Intent homeIntent = mService.getHomeIntent(); - final ActivityInfo aInfo = resolveHomeActivity(userId, homeIntent); + Intent homeIntent; + ActivityInfo aInfo; + if (displayId == DEFAULT_DISPLAY) { + homeIntent = mService.getHomeIntent(); + aInfo = resolveHomeActivity(userId, homeIntent); + } else { + Pair<ActivityInfo, Intent> info = resolveSecondaryHomeActivity(userId, displayId); + aInfo = info.first; + homeIntent = info.second; + } if (aInfo == null) { return false; } - if (!canStartHomeOnDisplay(aInfo, displayId, - false /* allowInstrumenting */)) { + if (!canStartHomeOnDisplay(aInfo, displayId, false /* allowInstrumenting */)) { return false; } + // Updates the home component of the intent. + homeIntent.setComponent(new ComponentName(aInfo.applicationInfo.packageName, aInfo.name)); + homeIntent.setFlags(homeIntent.getFlags() | FLAG_ACTIVITY_NEW_TASK); // Update the reason for ANR debugging to verify if the user activity is the one that // actually launched. final String myReason = reason + ":" + userId + ":" + UserHandle.getUserId( - aInfo.applicationInfo.uid); + aInfo.applicationInfo.uid) + ":" + displayId; mService.getActivityStartController().startHomeActivity(homeIntent, aInfo, myReason, displayId); return true; } /** - * This resolves the home activity info and updates the home component of the given intent. + * This resolves the home activity info. * @return the home activity info if any. */ - private ActivityInfo resolveHomeActivity(int userId, Intent homeIntent) { + @VisibleForTesting + ActivityInfo resolveHomeActivity(int userId, Intent homeIntent) { final int flags = ActivityManagerService.STOCK_PM_FLAGS; final ComponentName comp = homeIntent.getComponent(); ActivityInfo aInfo = null; @@ -400,13 +420,82 @@ class RootActivityContainer extends ConfigurationContainer return null; } - homeIntent.setComponent(new ComponentName(aInfo.applicationInfo.packageName, aInfo.name)); aInfo = new ActivityInfo(aInfo); aInfo.applicationInfo = mService.getAppInfoForUser(aInfo.applicationInfo, userId); - homeIntent.setFlags(homeIntent.getFlags() | FLAG_ACTIVITY_NEW_TASK); return aInfo; } + @VisibleForTesting + Pair<ActivityInfo, Intent> resolveSecondaryHomeActivity(int userId, int displayId) { + if (displayId == DEFAULT_DISPLAY) { + throw new IllegalArgumentException( + "resolveSecondaryHomeActivity: Should not be DEFAULT_DISPLAY"); + } + // Resolve activities in the same package as currently selected primary home activity. + Intent homeIntent = mService.getHomeIntent(); + ActivityInfo aInfo = resolveHomeActivity(userId, homeIntent); + if (aInfo != null) { + if (ResolverActivity.class.getName().equals(aInfo.name)) { + // Always fallback to secondary home component if default home is not set. + aInfo = null; + } else { + // Look for secondary home activities in the currently selected default home + // package. + homeIntent = mService.getSecondaryHomeIntent(aInfo.applicationInfo.packageName); + final List<ResolveInfo> resolutions = resolveActivities(userId, homeIntent); + final int size = resolutions.size(); + final String targetName = aInfo.name; + aInfo = null; + for (int i = 0; i < size; i++) { + ResolveInfo resolveInfo = resolutions.get(i); + // We need to traverse all resolutions to check if the currently selected + // default home activity is present. + if (resolveInfo.activityInfo.name.equals(targetName)) { + aInfo = resolveInfo.activityInfo; + break; + } + } + if (aInfo == null && size > 0) { + // First one is the best. + aInfo = resolutions.get(0).activityInfo; + } + } + } + + if (aInfo != null) { + if (!canStartHomeOnDisplay(aInfo, displayId, false /* allowInstrumenting */)) { + aInfo = null; + } + } + + // Fallback to secondary home component. + if (aInfo == null) { + homeIntent = mService.getSecondaryHomeIntent(null); + aInfo = resolveHomeActivity(userId, homeIntent); + } + return Pair.create(aInfo, homeIntent); + } + + /** + * Retrieve all activities that match the given intent. + * The list should already ordered from best to worst matched. + * {@link android.content.pm.PackageManager#queryIntentActivities} + */ + @VisibleForTesting + List<ResolveInfo> resolveActivities(int userId, Intent homeIntent) { + List<ResolveInfo> resolutions; + try { + final String resolvedType = + homeIntent.resolveTypeIfNeeded(mService.mContext.getContentResolver()); + resolutions = AppGlobals.getPackageManager().queryIntentActivities(homeIntent, + resolvedType, ActivityManagerService.STOCK_PM_FLAGS, userId).getList(); + + } catch (RemoteException e) { + resolutions = new ArrayList<>(); + } + return resolutions; + } + boolean resumeHomeActivity(ActivityRecord prev, String reason, int displayId) { if (!mService.isBooting() && !mService.isBooted()) { // Not ready yet! @@ -457,6 +546,14 @@ class RootActivityContainer extends ConfigurationContainer return true; } + final boolean deviceProvisioned = Settings.Global.getInt( + mService.mContext.getContentResolver(), + Settings.Global.DEVICE_PROVISIONED, 0) != 0; + if (displayId != DEFAULT_DISPLAY && displayId != INVALID_DISPLAY && !deviceProvisioned) { + // Can't launch home on secondary display before device is provisioned. + return false; + } + final ActivityDisplay display = getActivityDisplay(displayId); if (display == null || display.isRemoved() || !display.supportsSystemDecorations()) { // Can't launch home on display that doesn't support system decorations. @@ -464,13 +561,9 @@ class RootActivityContainer extends ConfigurationContainer } final boolean supportMultipleInstance = homeInfo.launchMode != LAUNCH_SINGLE_TASK - && homeInfo.launchMode != LAUNCH_SINGLE_INSTANCE - && homeInfo.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.Q; + && homeInfo.launchMode != LAUNCH_SINGLE_INSTANCE; if (!supportMultipleInstance) { - // Can't launch home on other displays if it requested to be single instance. Also we - // don't allow home applications that target before Q to have multiple home activity - // instances because they may not be expected to have multiple home scenario and - // haven't explicitly request for single instance. + // Can't launch home on secondary displays if it requested to be single instance. return false; } diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 801e5f2038ad..dcade2f012db 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -794,8 +794,9 @@ class RootWindowContainer extends WindowContainer<DisplayContent> private void handleResizingWindows() { for (int i = mWmService.mResizingWindows.size() - 1; i >= 0; i--) { WindowState win = mWmService.mResizingWindows.get(i); - if (win.mAppFreezing) { - // Don't remove this window until rotation has completed. + if (win.mAppFreezing || win.getDisplayContent().mWaitingForConfig) { + // Don't remove this window until rotation has completed and is not waiting for the + // complete configuration. continue; } win.reportResized(); diff --git a/services/core/java/com/android/server/wm/TaskRecord.java b/services/core/java/com/android/server/wm/TaskRecord.java index 5bb64407a28d..8c800097e49d 100644 --- a/services/core/java/com/android/server/wm/TaskRecord.java +++ b/services/core/java/com/android/server/wm/TaskRecord.java @@ -1716,7 +1716,7 @@ class TaskRecord extends ConfigurationContainer { updateTaskDescription(); } - private void adjustForMinimalTaskDimensions(Rect bounds) { + private void adjustForMinimalTaskDimensions(Rect bounds, Rect previousBounds) { if (bounds == null) { return; } @@ -1747,9 +1747,8 @@ class TaskRecord extends ConfigurationContainer { return; } - final Rect configBounds = getRequestedOverrideBounds(); if (adjustWidth) { - if (!configBounds.isEmpty() && bounds.right == configBounds.right) { + if (!previousBounds.isEmpty() && bounds.right == previousBounds.right) { bounds.left = bounds.right - minWidth; } else { // Either left bounds match, or neither match, or the previous bounds were @@ -1758,7 +1757,7 @@ class TaskRecord extends ConfigurationContainer { } } if (adjustHeight) { - if (!configBounds.isEmpty() && bounds.bottom == configBounds.bottom) { + if (!previousBounds.isEmpty() && bounds.bottom == previousBounds.bottom) { bounds.top = bounds.bottom - minHeight; } else { // Either top bounds match, or neither match, or the previous bounds were @@ -2113,11 +2112,15 @@ class TaskRecord extends ConfigurationContainer { // TODO(b/113900640): remove this once ActivityRecord is changed to not need it anymore. void computeResolvedOverrideConfiguration(Configuration inOutConfig, Configuration parentConfig, Configuration overrideConfig) { + // Save previous bounds because adjustForMinimalTaskDimensions uses that to determine if it + // changes left bound vs. right bound, or top bound vs. bottom bound. + mTmpBounds.set(inOutConfig.windowConfiguration.getBounds()); + inOutConfig.setTo(overrideConfig); Rect outOverrideBounds = inOutConfig.windowConfiguration.getBounds(); if (outOverrideBounds != null && !outOverrideBounds.isEmpty()) { - adjustForMinimalTaskDimensions(outOverrideBounds); + adjustForMinimalTaskDimensions(outOverrideBounds, mTmpBounds); int windowingMode = overrideConfig.windowConfiguration.getWindowingMode(); if (windowingMode == WINDOWING_MODE_UNDEFINED) { diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index cf03d613634f..d1dfd7d14437 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -2116,30 +2116,33 @@ public final class SystemServer { private void startContentCaptureService(@NonNull Context context) { - // First check if it was explicitly enabled by Settings - boolean explicitlySupported = false; + // Check if it was explicitly enabled by Settings final String settings = Settings.Global.getString(context.getContentResolver(), Settings.Global.CONTENT_CAPTURE_SERVICE_EXPLICITLY_ENABLED); - if (settings != null) { - explicitlySupported = Boolean.parseBoolean(settings); - if (explicitlySupported) { - Slog.d(TAG, "ContentCaptureService explicitly enabled by Settings"); - } else { - Slog.d(TAG, "ContentCaptureService explicitly disabled by Settings"); - return; - } + if (settings == null) { + // Better be safe than sorry... + Slog.d(TAG, "ContentCaptureService disabled because its not set by OEM"); + return; } - - // Then check if OEM overlaid the resource that defines the service. - if (!explicitlySupported) { - final String serviceName = context - .getString(com.android.internal.R.string.config_defaultContentCaptureService); - if (TextUtils.isEmpty(serviceName)) { - Slog.d(TAG, "ContentCaptureService disabled because resource is not overlaid"); + switch (settings) { + case Settings.Global.CONTENT_CAPTURE_SERVICE_EXPLICITLY_ENABLED_ALWAYS: + // Should be used only during development + Slog.d(TAG, "ContentCaptureService explicitly enabled by Settings"); + break; + case Settings.Global.CONTENT_CAPTURE_SERVICE_EXPLICITLY_ENABLED_DEFAULT: + // Default case: check if OEM overlaid the resource that defines the service. + final String serviceName = context.getString( + com.android.internal.R.string.config_defaultContentCaptureService); + if (TextUtils.isEmpty(serviceName)) { + Slog.d(TAG, "ContentCaptureService disabled because resource is not overlaid"); + return; + } + break; + default: + // Kill switch for OEMs + Slog.d(TAG, "ContentCaptureService disabled because its set to: " + settings); return; - } } - traceBeginAndSlog("StartContentCaptureService"); mSystemServiceManager.startService(CONTENT_CAPTURE_MANAGER_SERVICE_CLASS); traceEnd(); diff --git a/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceTest.java b/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceTest.java index 83f66c5258b2..b253e0ae0a40 100644 --- a/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceTest.java +++ b/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceTest.java @@ -23,6 +23,7 @@ import static com.android.server.backup.testing.TransportData.backupTransport; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -86,9 +87,7 @@ public class BackupManagerServiceTest { mContext = application; mShadowContext = shadowOf(application); - // TODO(b/120212806): Hardcoding system user for now since most methods in BMS don't yet - // take an user parameter (and instead hardcode the system user). - mUserOneId = UserHandle.USER_SYSTEM; + mUserOneId = UserHandle.USER_SYSTEM + 1; mUserTwoId = mUserOneId + 1; } @@ -176,9 +175,52 @@ public class BackupManagerServiceTest { assertThat(serviceUsers.get(mUserOneId)).isEqualTo(mUserOneService); } - // TODO(b/120212806): When BMS methods take in a user parameter, modify unknown user tests to - // check that that we don't call the method on another registered user. Currently these tests - // have no registered users since we hardcode the system user in BMS. + /** + * Test that the backup services throws a {@link SecurityException} if the caller does not have + * INTERACT_ACROSS_USERS_FULL permission and passes a different user id. + */ + @Test + public void testGetServiceForUser_withoutPermission_throwsSecurityExceptionForNonCallingUser() { + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); + + expectThrows( + SecurityException.class, + () -> + backupManagerService.getServiceForUserIfCallerHasPermission( + mUserOneId, "test")); + } + + /** + * Test that the backup services does not throw a {@link SecurityException} if the caller has + * INTERACT_ACROSS_USERS_FULL permission and passes a different user id. + */ + @Test + public void testGetServiceForUserIfCallerHasPermission_withPermission_worksForNonCallingUser() { + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ true); + + assertEquals( + mUserOneService, + backupManagerService.getServiceForUserIfCallerHasPermission(mUserOneId, "test")); + } + + /** + * Test that the backup services does not throw a {@link SecurityException} if the caller does + * not have INTERACT_ACROSS_USERS_FULL permission and passes in the calling user id. + */ + @Test + public void testGetServiceForUserIfCallerHasPermission_withoutPermission_worksForCallingUser() { + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); + + assertEquals( + mUserOneService, + backupManagerService.getServiceForUserIfCallerHasPermission(mUserOneId, "test")); + } // --------------------------------------------- // Backup agent tests @@ -189,8 +231,9 @@ public class BackupManagerServiceTest { public void testDataChanged_onRegisteredUser_callsMethodForUser() throws Exception { BackupManagerService backupManagerService = createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); - backupManagerService.dataChanged(TEST_PACKAGE); + backupManagerService.dataChanged(mUserOneId, TEST_PACKAGE); verify(mUserOneService).dataChanged(TEST_PACKAGE); } @@ -198,9 +241,11 @@ public class BackupManagerServiceTest { /** Test that the backup service does not route methods for non-registered users. */ @Test public void testDataChanged_onUnknownUser_doesNotPropagateCall() throws Exception { - BackupManagerService backupManagerService = createService(); + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); - backupManagerService.dataChanged(TEST_PACKAGE); + backupManagerService.dataChanged(mUserTwoId, TEST_PACKAGE); verify(mUserOneService, never()).dataChanged(TEST_PACKAGE); } @@ -210,9 +255,10 @@ public class BackupManagerServiceTest { public void testAgentConnected_onRegisteredUser_callsMethodForUser() throws Exception { BackupManagerService backupManagerService = createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); IBinder agentBinder = mock(IBinder.class); - backupManagerService.agentConnected(TEST_PACKAGE, agentBinder); + backupManagerService.agentConnected(mUserOneId, TEST_PACKAGE, agentBinder); verify(mUserOneService).agentConnected(TEST_PACKAGE, agentBinder); } @@ -220,10 +266,12 @@ public class BackupManagerServiceTest { /** Test that the backup service does not route methods for non-registered users. */ @Test public void testAgentConnected_onUnknownUser_doesNotPropagateCall() throws Exception { - BackupManagerService backupManagerService = createService(); + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); IBinder agentBinder = mock(IBinder.class); - backupManagerService.agentConnected(TEST_PACKAGE, agentBinder); + backupManagerService.agentConnected(mUserTwoId, TEST_PACKAGE, agentBinder); verify(mUserOneService, never()).agentConnected(TEST_PACKAGE, agentBinder); } @@ -233,8 +281,9 @@ public class BackupManagerServiceTest { public void testAgentDisconnected_onRegisteredUser_callsMethodForUser() throws Exception { BackupManagerService backupManagerService = createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); - backupManagerService.agentDisconnected(TEST_PACKAGE); + backupManagerService.agentDisconnected(mUserOneId, TEST_PACKAGE); verify(mUserOneService).agentDisconnected(TEST_PACKAGE); } @@ -242,9 +291,11 @@ public class BackupManagerServiceTest { /** Test that the backup service does not route methods for non-registered users. */ @Test public void testAgentDisconnected_onUnknownUser_doesNotPropagateCall() throws Exception { - BackupManagerService backupManagerService = createService(); + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); - backupManagerService.agentDisconnected(TEST_PACKAGE); + backupManagerService.agentDisconnected(mUserTwoId, TEST_PACKAGE); verify(mUserOneService, never()).agentDisconnected(TEST_PACKAGE); } @@ -254,8 +305,9 @@ public class BackupManagerServiceTest { public void testOpComplete_onRegisteredUser_callsMethodForUser() throws Exception { BackupManagerService backupManagerService = createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); - backupManagerService.opComplete(/* token */ 0, /* result */ 0L); + backupManagerService.opComplete(mUserOneId, /* token */ 0, /* result */ 0L); verify(mUserOneService).opComplete(/* token */ 0, /* result */ 0L); } @@ -263,9 +315,11 @@ public class BackupManagerServiceTest { /** Test that the backup service does not route methods for non-registered users. */ @Test public void testOpComplete_onUnknownUser_doesNotPropagateCall() throws Exception { - BackupManagerService backupManagerService = createService(); + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); - backupManagerService.opComplete(/* token */ 0, /* result */ 0L); + backupManagerService.opComplete(mUserTwoId, /* token */ 0, /* result */ 0L); verify(mUserOneService, never()).opComplete(/* token */ 0, /* result */ 0L); } @@ -279,9 +333,10 @@ public class BackupManagerServiceTest { public void testInitializeTransports_onRegisteredUser_callsMethodForUser() throws Exception { BackupManagerService backupManagerService = createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); String[] transports = {TEST_TRANSPORT}; - backupManagerService.initializeTransports(transports, /* observer */ null); + backupManagerService.initializeTransports(mUserOneId, transports, /* observer */ null); verify(mUserOneService).initializeTransports(transports, /* observer */ null); } @@ -289,10 +344,12 @@ public class BackupManagerServiceTest { /** Test that the backup service does not route methods for non-registered users. */ @Test public void testInitializeTransports_onUnknownUser_doesNotPropagateCall() throws Exception { - BackupManagerService backupManagerService = createService(); + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); String[] transports = {TEST_TRANSPORT}; - backupManagerService.initializeTransports(transports, /* observer */ null); + backupManagerService.initializeTransports(mUserTwoId, transports, /* observer */ null); verify(mUserOneService, never()).initializeTransports(transports, /* observer */ null); } @@ -302,8 +359,9 @@ public class BackupManagerServiceTest { public void testClearBackupData_onRegisteredUser_callsMethodForUser() throws Exception { BackupManagerService backupManagerService = createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); - backupManagerService.clearBackupData(TEST_TRANSPORT, TEST_PACKAGE); + backupManagerService.clearBackupData(mUserOneId, TEST_TRANSPORT, TEST_PACKAGE); verify(mUserOneService).clearBackupData(TEST_TRANSPORT, TEST_PACKAGE); } @@ -311,9 +369,11 @@ public class BackupManagerServiceTest { /** Test that the backup service does not route methods for non-registered users. */ @Test public void testClearBackupData_onUnknownUser_doesNotPropagateCall() throws Exception { - BackupManagerService backupManagerService = createService(); + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); - backupManagerService.clearBackupData(TEST_TRANSPORT, TEST_PACKAGE); + backupManagerService.clearBackupData(mUserTwoId, TEST_TRANSPORT, TEST_PACKAGE); verify(mUserOneService, never()).clearBackupData(TEST_TRANSPORT, TEST_PACKAGE); } @@ -323,8 +383,9 @@ public class BackupManagerServiceTest { public void testGetCurrentTransport_onRegisteredUser_callsMethodForUser() throws Exception { BackupManagerService backupManagerService = createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); - backupManagerService.getCurrentTransport(); + backupManagerService.getCurrentTransport(mUserOneId); verify(mUserOneService).getCurrentTransport(); } @@ -332,9 +393,11 @@ public class BackupManagerServiceTest { /** Test that the backup service does not route methods for non-registered users. */ @Test public void testGetCurrentTransport_onUnknownUser_doesNotPropagateCall() throws Exception { - BackupManagerService backupManagerService = createService(); + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); - backupManagerService.getCurrentTransport(); + backupManagerService.getCurrentTransport(mUserTwoId); verify(mUserOneService, never()).getCurrentTransport(); } @@ -345,8 +408,9 @@ public class BackupManagerServiceTest { throws Exception { BackupManagerService backupManagerService = createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); - backupManagerService.getCurrentTransportComponent(); + backupManagerService.getCurrentTransportComponent(mUserOneId); verify(mUserOneService).getCurrentTransportComponent(); } @@ -355,9 +419,11 @@ public class BackupManagerServiceTest { @Test public void testGetCurrentTransportComponent_onUnknownUser_doesNotPropagateCall() throws Exception { - BackupManagerService backupManagerService = createService(); + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); - backupManagerService.getCurrentTransportComponent(); + backupManagerService.getCurrentTransportComponent(mUserTwoId); verify(mUserOneService, never()).getCurrentTransportComponent(); } @@ -367,8 +433,9 @@ public class BackupManagerServiceTest { public void testListAllTransports_onRegisteredUser_callsMethodForUser() throws Exception { BackupManagerService backupManagerService = createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); - backupManagerService.listAllTransports(); + backupManagerService.listAllTransports(mUserOneId); verify(mUserOneService).listAllTransports(); } @@ -376,9 +443,11 @@ public class BackupManagerServiceTest { /** Test that the backup service does not route methods for non-registered users. */ @Test public void testListAllTransports_onUnknownUser_doesNotPropagateCall() throws Exception { - BackupManagerService backupManagerService = createService(); + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); - backupManagerService.listAllTransports(); + backupManagerService.listAllTransports(mUserTwoId); verify(mUserOneService, never()).listAllTransports(); } @@ -389,8 +458,9 @@ public class BackupManagerServiceTest { throws Exception { BackupManagerService backupManagerService = createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); - backupManagerService.listAllTransportComponents(); + backupManagerService.listAllTransportComponents(mUserOneId); verify(mUserOneService).listAllTransportComponents(); } @@ -399,32 +469,13 @@ public class BackupManagerServiceTest { @Test public void testListAllTransportComponents_onUnknownUser_doesNotPropagateCall() throws Exception { - BackupManagerService backupManagerService = createService(); - - backupManagerService.listAllTransportComponents(); - - verify(mUserOneService, never()).listAllTransportComponents(); - } - - /** Test that the backup service routes methods correctly to the user that requests it. */ - @Test - public void testGetTransportWhitelist_onRegisteredUser_callsMethodForUser() throws Exception { BackupManagerService backupManagerService = createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); - backupManagerService.getTransportWhitelist(); - - verify(mUserOneService).getTransportWhitelist(); - } - - /** Test that the backup service does not route methods for non-registered users. */ - @Test - public void testGetTransportWhitelist_onUnknownUser_doesNotPropagateCall() throws Exception { - BackupManagerService backupManagerService = createService(); - - backupManagerService.getTransportWhitelist(); + backupManagerService.listAllTransportComponents(mUserTwoId); - verify(mUserOneService, never()).getTransportWhitelist(); + verify(mUserOneService, never()).listAllTransportComponents(); } /** Test that the backup service routes methods correctly to the user that requests it. */ @@ -433,11 +484,13 @@ public class BackupManagerServiceTest { throws Exception { BackupManagerService backupManagerService = createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); TransportData transport = backupTransport(); Intent configurationIntent = new Intent(); Intent dataManagementIntent = new Intent(); backupManagerService.updateTransportAttributes( + mUserOneId, transport.getTransportComponent(), transport.transportName, configurationIntent, @@ -459,12 +512,15 @@ public class BackupManagerServiceTest { @Test public void testUpdateTransportAttributes_onUnknownUser_doesNotPropagateCall() throws Exception { - BackupManagerService backupManagerService = createService(); + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); TransportData transport = backupTransport(); Intent configurationIntent = new Intent(); Intent dataManagementIntent = new Intent(); backupManagerService.updateTransportAttributes( + mUserTwoId, transport.getTransportComponent(), transport.transportName, configurationIntent, @@ -487,8 +543,9 @@ public class BackupManagerServiceTest { public void testSelectBackupTransport_onRegisteredUser_callsMethodForUser() throws Exception { BackupManagerService backupManagerService = createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); - backupManagerService.selectBackupTransport(TEST_TRANSPORT); + backupManagerService.selectBackupTransport(mUserOneId, TEST_TRANSPORT); verify(mUserOneService).selectBackupTransport(TEST_TRANSPORT); } @@ -496,9 +553,11 @@ public class BackupManagerServiceTest { /** Test that the backup service does not route methods for non-registered users. */ @Test public void testSelectBackupTransport_onUnknownUser_doesNotPropagateCall() throws Exception { - BackupManagerService backupManagerService = createService(); + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); - backupManagerService.selectBackupTransport(TEST_TRANSPORT); + backupManagerService.selectBackupTransport(mUserTwoId, TEST_TRANSPORT); verify(mUserOneService, never()).selectBackupTransport(TEST_TRANSPORT); } @@ -508,11 +567,12 @@ public class BackupManagerServiceTest { public void testSelectTransportAsync_onRegisteredUser_callsMethodForUser() throws Exception { BackupManagerService backupManagerService = createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); TransportData transport = backupTransport(); ISelectBackupTransportCallback callback = mock(ISelectBackupTransportCallback.class); backupManagerService.selectBackupTransportAsync( - transport.getTransportComponent(), callback); + mUserOneId, transport.getTransportComponent(), callback); verify(mUserOneService) .selectBackupTransportAsync(transport.getTransportComponent(), callback); @@ -522,12 +582,14 @@ public class BackupManagerServiceTest { @Test public void testSelectBackupTransportAsync_onUnknownUser_doesNotPropagateCall() throws Exception { - BackupManagerService backupManagerService = createService(); + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); TransportData transport = backupTransport(); ISelectBackupTransportCallback callback = mock(ISelectBackupTransportCallback.class); backupManagerService.selectBackupTransportAsync( - transport.getTransportComponent(), callback); + mUserTwoId, transport.getTransportComponent(), callback); verify(mUserOneService, never()) .selectBackupTransportAsync(transport.getTransportComponent(), callback); @@ -538,8 +600,9 @@ public class BackupManagerServiceTest { public void testGetConfigurationIntent_onRegisteredUser_callsMethodForUser() throws Exception { BackupManagerService backupManagerService = createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); - backupManagerService.getConfigurationIntent(TEST_TRANSPORT); + backupManagerService.getConfigurationIntent(mUserOneId, TEST_TRANSPORT); verify(mUserOneService).getConfigurationIntent(TEST_TRANSPORT); } @@ -547,9 +610,11 @@ public class BackupManagerServiceTest { /** Test that the backup service does not route methods for non-registered users. */ @Test public void testGetConfigurationIntent_onUnknownUser_doesNotPropagateCall() throws Exception { - BackupManagerService backupManagerService = createService(); + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); - backupManagerService.getConfigurationIntent(TEST_TRANSPORT); + backupManagerService.getConfigurationIntent(mUserTwoId, TEST_TRANSPORT); verify(mUserOneService, never()).getConfigurationIntent(TEST_TRANSPORT); } @@ -559,8 +624,9 @@ public class BackupManagerServiceTest { public void testGetDestinationString_onRegisteredUser_callsMethodForUser() throws Exception { BackupManagerService backupManagerService = createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); - backupManagerService.getDestinationString(TEST_TRANSPORT); + backupManagerService.getDestinationString(mUserOneId, TEST_TRANSPORT); verify(mUserOneService).getDestinationString(TEST_TRANSPORT); } @@ -568,9 +634,11 @@ public class BackupManagerServiceTest { /** Test that the backup service does not route methods for non-registered users. */ @Test public void testGetDestinationString_onUnknownUser_doesNotPropagateCall() throws Exception { - BackupManagerService backupManagerService = createService(); + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); - backupManagerService.getDestinationString(TEST_TRANSPORT); + backupManagerService.getDestinationString(mUserTwoId, TEST_TRANSPORT); verify(mUserOneService, never()).getDestinationString(TEST_TRANSPORT); } @@ -580,8 +648,9 @@ public class BackupManagerServiceTest { public void testGetDataManagementIntent_onRegisteredUser_callsMethodForUser() throws Exception { BackupManagerService backupManagerService = createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); - backupManagerService.getDataManagementIntent(TEST_TRANSPORT); + backupManagerService.getDataManagementIntent(mUserOneId, TEST_TRANSPORT); verify(mUserOneService).getDataManagementIntent(TEST_TRANSPORT); } @@ -589,9 +658,11 @@ public class BackupManagerServiceTest { /** Test that the backup service does not route methods for non-registered users. */ @Test public void testGetDataManagementIntent_onUnknownUser_doesNotPropagateCall() throws Exception { - BackupManagerService backupManagerService = createService(); + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); - backupManagerService.getDataManagementIntent(TEST_TRANSPORT); + backupManagerService.getDataManagementIntent(mUserTwoId, TEST_TRANSPORT); verify(mUserOneService, never()).getDataManagementIntent(TEST_TRANSPORT); } @@ -601,8 +672,9 @@ public class BackupManagerServiceTest { public void testGetDataManagementLabel_onRegisteredUser_callsMethodForUser() throws Exception { BackupManagerService backupManagerService = createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); - backupManagerService.getDataManagementLabel(TEST_TRANSPORT); + backupManagerService.getDataManagementLabel(mUserOneId, TEST_TRANSPORT); verify(mUserOneService).getDataManagementLabel(TEST_TRANSPORT); } @@ -610,9 +682,11 @@ public class BackupManagerServiceTest { /** Test that the backup service does not route methods for non-registered users. */ @Test public void testGetDataManagementLabel_onUnknownUser_doesNotPropagateCall() throws Exception { - BackupManagerService backupManagerService = createService(); + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); - backupManagerService.getDataManagementLabel(TEST_TRANSPORT); + backupManagerService.getDataManagementLabel(mUserTwoId, TEST_TRANSPORT); verify(mUserOneService, never()).getDataManagementLabel(TEST_TRANSPORT); } @@ -620,7 +694,6 @@ public class BackupManagerServiceTest { // --------------------------------------------- // Settings tests // --------------------------------------------- - /** * Test that the backup services throws a {@link SecurityException} if the caller does not have * INTERACT_ACROSS_USERS_FULL permission and passes a different user id. @@ -681,8 +754,9 @@ public class BackupManagerServiceTest { public void testSetAutoRestore_onRegisteredUser_callsMethodForUser() throws Exception { BackupManagerService backupManagerService = createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); - backupManagerService.setAutoRestore(true); + backupManagerService.setAutoRestore(mUserOneId, true); verify(mUserOneService).setAutoRestore(true); } @@ -690,62 +764,13 @@ public class BackupManagerServiceTest { /** Test that the backup service does not route methods for non-registered users. */ @Test public void testSetAutoRestore_onUnknownUser_doesNotPropagateCall() throws Exception { - BackupManagerService backupManagerService = createService(); - - backupManagerService.setAutoRestore(true); - - verify(mUserOneService, never()).setAutoRestore(true); - } - - /** Test that the backup service routes methods correctly to the user that requests it. */ - @Test - public void testSetBackupProvisioned_onRegisteredUser_callsMethodForUser() throws Exception { BackupManagerService backupManagerService = createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); - backupManagerService.setBackupProvisioned(true); - - verify(mUserOneService).setBackupProvisioned(true); - } - - /** Test that the backup service does not route methods for non-registered users. */ - @Test - public void testSetBackupProvisioned_onUnknownUser_doesNotPropagateCall() throws Exception { - BackupManagerService backupManagerService = createService(); - - backupManagerService.setBackupProvisioned(true); - - verify(mUserOneService, never()).setBackupProvisioned(true); - } - - /** - * Test that the backup services throws a {@link SecurityException} if the caller does not have - * INTERACT_ACROSS_USERS_FULL permission and passes a different user id. - */ - @Test - public void testIsBackupEnabled_withoutPermission_throwsSecurityExceptionForNonCallingUser() { - BackupManagerService backupManagerService = - createServiceAndRegisterUser(mUserOneId, mUserOneService); - setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); - - expectThrows( - SecurityException.class, () -> backupManagerService.isBackupEnabled(mUserTwoId)); - } - - /** - * Test that the backup service does not throw a {@link SecurityException} if the caller has - * INTERACT_ACROSS_USERS_FULL permission and passes a different user id. - */ - @Test - public void testIsBackupEnabled_withPermission_propagatesForNonCallingUser() { - BackupManagerService backupManagerService = - createServiceAndRegisterUser(mUserOneId, mUserOneService); - backupManagerService.startServiceForUser(mUserTwoId, mUserTwoService); - setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ true); - - backupManagerService.isBackupEnabled(mUserTwoId); + backupManagerService.setAutoRestore(mUserTwoId, true); - verify(mUserTwoService).isBackupEnabled(); + verify(mUserOneService, never()).setAutoRestore(true); } /** Test that the backup service routes methods correctly to the user that requests it. */ @@ -781,8 +806,9 @@ public class BackupManagerServiceTest { public void testIsAppEligibleForBackup_onRegisteredUser_callsMethodForUser() throws Exception { BackupManagerService backupManagerService = createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); - backupManagerService.isAppEligibleForBackup(TEST_PACKAGE); + backupManagerService.isAppEligibleForBackup(mUserOneId, TEST_PACKAGE); verify(mUserOneService).isAppEligibleForBackup(TEST_PACKAGE); } @@ -790,9 +816,11 @@ public class BackupManagerServiceTest { /** Test that the backup service does not route methods for non-registered users. */ @Test public void testIsAppEligibleForBackup_onUnknownUser_doesNotPropagateCall() throws Exception { - BackupManagerService backupManagerService = createService(); + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); - backupManagerService.isAppEligibleForBackup(TEST_PACKAGE); + backupManagerService.isAppEligibleForBackup(mUserTwoId, TEST_PACKAGE); verify(mUserOneService, never()).isAppEligibleForBackup(TEST_PACKAGE); } @@ -803,9 +831,10 @@ public class BackupManagerServiceTest { throws Exception { BackupManagerService backupManagerService = createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); String[] packages = {TEST_PACKAGE}; - backupManagerService.filterAppsEligibleForBackup(packages); + backupManagerService.filterAppsEligibleForBackup(mUserOneId, packages); verify(mUserOneService).filterAppsEligibleForBackup(packages); } @@ -814,10 +843,12 @@ public class BackupManagerServiceTest { @Test public void testFilterAppsEligibleForBackup_onUnknownUser_doesNotPropagateCall() throws Exception { - BackupManagerService backupManagerService = createService(); + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); String[] packages = {TEST_PACKAGE}; - backupManagerService.filterAppsEligibleForBackup(packages); + backupManagerService.filterAppsEligibleForBackup(mUserTwoId, packages); verify(mUserOneService, never()).filterAppsEligibleForBackup(packages); } @@ -1001,7 +1032,7 @@ public class BackupManagerServiceTest { @Test public void testBeginFullBackup_onRegisteredUser_callsMethodForUser() throws Exception { BackupManagerService backupManagerService = - createServiceAndRegisterUser(mUserOneId, mUserOneService); + createServiceAndRegisterUser(UserHandle.USER_SYSTEM, mUserOneService); FullBackupJob job = new FullBackupJob(); backupManagerService.beginFullBackup(job); @@ -1024,7 +1055,7 @@ public class BackupManagerServiceTest { @Test public void testEndFullBackup_onRegisteredUser_callsMethodForUser() throws Exception { BackupManagerService backupManagerService = - createServiceAndRegisterUser(mUserOneId, mUserOneService); + createServiceAndRegisterUser(UserHandle.USER_SYSTEM, mUserOneService); backupManagerService.endFullBackup(); @@ -1046,9 +1077,10 @@ public class BackupManagerServiceTest { public void testFullTransportBackup_onRegisteredUser_callsMethodForUser() throws Exception { BackupManagerService backupManagerService = createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); String[] packages = {TEST_PACKAGE}; - backupManagerService.fullTransportBackup(packages); + backupManagerService.fullTransportBackup(mUserOneId, packages); verify(mUserOneService).fullTransportBackup(packages); } @@ -1056,10 +1088,12 @@ public class BackupManagerServiceTest { /** Test that the backup service does not route methods for non-registered users. */ @Test public void testFullTransportBackup_onUnknownUser_doesNotPropagateCall() throws Exception { - BackupManagerService backupManagerService = createService(); + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); String[] packages = {TEST_PACKAGE}; - backupManagerService.fullTransportBackup(packages); + backupManagerService.fullTransportBackup(mUserTwoId, packages); verify(mUserOneService, never()).fullTransportBackup(packages); } @@ -1073,8 +1107,9 @@ public class BackupManagerServiceTest { public void testRestoreAtInstall_onRegisteredUser_callsMethodForUser() throws Exception { BackupManagerService backupManagerService = createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); - backupManagerService.restoreAtInstall(TEST_PACKAGE, /* token */ 0); + backupManagerService.restoreAtInstall(mUserOneId, TEST_PACKAGE, /* token */ 0); verify(mUserOneService).restoreAtInstall(TEST_PACKAGE, /* token */ 0); } @@ -1082,9 +1117,11 @@ public class BackupManagerServiceTest { /** Test that the backup service does not route methods for non-registered users. */ @Test public void testRestoreAtInstall_onUnknownUser_doesNotPropagateCall() throws Exception { - BackupManagerService backupManagerService = createService(); + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); - backupManagerService.restoreAtInstall(TEST_PACKAGE, /* token */ 0); + backupManagerService.restoreAtInstall(mUserTwoId, TEST_PACKAGE, /* token */ 0); verify(mUserOneService, never()).restoreAtInstall(TEST_PACKAGE, /* token */ 0); } @@ -1094,8 +1131,9 @@ public class BackupManagerServiceTest { public void testBeginRestoreSession_onRegisteredUser_callsMethodForUser() throws Exception { BackupManagerService backupManagerService = createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); - backupManagerService.beginRestoreSession(TEST_PACKAGE, TEST_TRANSPORT); + backupManagerService.beginRestoreSession(mUserOneId, TEST_PACKAGE, TEST_TRANSPORT); verify(mUserOneService).beginRestoreSession(TEST_PACKAGE, TEST_TRANSPORT); } @@ -1103,9 +1141,11 @@ public class BackupManagerServiceTest { /** Test that the backup service does not route methods for non-registered users. */ @Test public void testBeginRestoreSession_onUnknownUser_doesNotPropagateCall() throws Exception { - BackupManagerService backupManagerService = createService(); + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); - backupManagerService.beginRestoreSession(TEST_PACKAGE, TEST_TRANSPORT); + backupManagerService.beginRestoreSession(mUserTwoId, TEST_PACKAGE, TEST_TRANSPORT); verify(mUserOneService, never()).beginRestoreSession(TEST_PACKAGE, TEST_TRANSPORT); } @@ -1116,8 +1156,9 @@ public class BackupManagerServiceTest { throws Exception { BackupManagerService backupManagerService = createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); - backupManagerService.getAvailableRestoreToken(TEST_PACKAGE); + backupManagerService.getAvailableRestoreToken(mUserOneId, TEST_PACKAGE); verify(mUserOneService).getAvailableRestoreToken(TEST_PACKAGE); } @@ -1125,9 +1166,11 @@ public class BackupManagerServiceTest { /** Test that the backup service does not route methods for non-registered users. */ @Test public void testGetAvailableRestoreToken_onUnknownUser_doesNotPropagateCall() throws Exception { - BackupManagerService backupManagerService = createService(); + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); - backupManagerService.getAvailableRestoreToken(TEST_PACKAGE); + backupManagerService.getAvailableRestoreToken(mUserTwoId, TEST_PACKAGE); verify(mUserOneService, never()).getAvailableRestoreToken(TEST_PACKAGE); } @@ -1140,7 +1183,7 @@ public class BackupManagerServiceTest { @Test public void testSetBackupPassword_onRegisteredUser_callsMethodForUser() throws Exception { BackupManagerService backupManagerService = - createServiceAndRegisterUser(mUserOneId, mUserOneService); + createServiceAndRegisterUser(UserHandle.USER_SYSTEM, mUserOneService); backupManagerService.setBackupPassword("currentPassword", "newPassword"); @@ -1161,7 +1204,7 @@ public class BackupManagerServiceTest { @Test public void testHasBackupPassword_onRegisteredUser_callsMethodForUser() throws Exception { BackupManagerService backupManagerService = - createServiceAndRegisterUser(mUserOneId, mUserOneService); + createServiceAndRegisterUser(UserHandle.USER_SYSTEM, mUserOneService); backupManagerService.hasBackupPassword(); @@ -1377,10 +1420,16 @@ public class BackupManagerServiceTest { throws Exception { BackupManagerService backupManagerService = createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false); IFullBackupRestoreObserver observer = mock(IFullBackupRestoreObserver.class); backupManagerService.acknowledgeAdbBackupOrRestore( - /* token */ 0, /* allow */ true, "currentPassword", "encryptionPassword", observer); + mUserOneId, + /* token */ 0, + /* allow */ true, + "currentPassword", + "encryptionPassword", + observer); verify(mUserOneService) .acknowledgeAdbBackupOrRestore( @@ -1395,11 +1444,18 @@ public class BackupManagerServiceTest { @Test public void testAcknowledgeAdbBackupOrRestore_onUnknownUser_doesNotPropagateCall() throws Exception { - BackupManagerService backupManagerService = createService(); + BackupManagerService backupManagerService = + createServiceAndRegisterUser(mUserOneId, mUserOneService); + setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false); IFullBackupRestoreObserver observer = mock(IFullBackupRestoreObserver.class); backupManagerService.acknowledgeAdbBackupOrRestore( - /* token */ 0, /* allow */ true, "currentPassword", "encryptionPassword", observer); + mUserTwoId, + /* token */ 0, + /* allow */ true, + "currentPassword", + "encryptionPassword", + observer); verify(mUserOneService, never()) .acknowledgeAdbBackupOrRestore( @@ -1418,7 +1474,7 @@ public class BackupManagerServiceTest { @Test public void testDump_onRegisteredUser_callsMethodForUser() throws Exception { BackupManagerService backupManagerService = - createServiceAndRegisterUser(mUserOneId, mUserOneService); + createServiceAndRegisterUser(UserHandle.USER_SYSTEM, mUserOneService); File testFile = new File(mContext.getFilesDir(), "test"); testFile.createNewFile(); FileDescriptor fileDescriptor = new FileDescriptor(); diff --git a/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java b/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java index efbcb960c1e9..1e468d44b8c7 100644 --- a/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java +++ b/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java @@ -87,6 +87,7 @@ public class UserBackupManagerServiceTest { private static final String TAG = "BMSTest"; private static final String PACKAGE_1 = "some.package.1"; private static final String PACKAGE_2 = "some.package.2"; + private static final int USER_ID = 10; @Mock private TransportManager mTransportManager; private HandlerThread mBackupThread; @@ -979,6 +980,7 @@ public class UserBackupManagerServiceTest { mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); UserBackupManagerService.createAndInitializeService( + USER_ID, mContext, new Trampoline(mContext), mBackupThread, @@ -1000,6 +1002,7 @@ public class UserBackupManagerServiceTest { mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); UserBackupManagerService.createAndInitializeService( + USER_ID, mContext, new Trampoline(mContext), mBackupThread, @@ -1022,6 +1025,7 @@ public class UserBackupManagerServiceTest { NullPointerException.class, () -> UserBackupManagerService.createAndInitializeService( + USER_ID, /* context */ null, new Trampoline(mContext), mBackupThread, @@ -1041,6 +1045,7 @@ public class UserBackupManagerServiceTest { NullPointerException.class, () -> UserBackupManagerService.createAndInitializeService( + USER_ID, mContext, /* trampoline */ null, mBackupThread, @@ -1060,6 +1065,7 @@ public class UserBackupManagerServiceTest { NullPointerException.class, () -> UserBackupManagerService.createAndInitializeService( + USER_ID, mContext, new Trampoline(mContext), /* backupThread */ null, @@ -1079,6 +1085,7 @@ public class UserBackupManagerServiceTest { NullPointerException.class, () -> UserBackupManagerService.createAndInitializeService( + USER_ID, mContext, new Trampoline(mContext), mBackupThread, @@ -1089,8 +1096,8 @@ public class UserBackupManagerServiceTest { /** * Test checking non-null argument on {@link - * UserBackupManagerService#createAndInitializeService(Context, Trampoline, HandlerThread, File, - * File, TransportManager)}. + * UserBackupManagerService#createAndInitializeService(int, Context, Trampoline, HandlerThread, + * File, File, TransportManager)}. */ @Test public void testCreateAndInitializeService_withNullDataDir_throws() { @@ -1098,6 +1105,7 @@ public class UserBackupManagerServiceTest { NullPointerException.class, () -> UserBackupManagerService.createAndInitializeService( + USER_ID, mContext, new Trampoline(mContext), mBackupThread, @@ -1108,8 +1116,8 @@ public class UserBackupManagerServiceTest { /** * Test checking non-null argument on {@link - * UserBackupManagerService#createAndInitializeService(Context, Trampoline, HandlerThread, File, - * File, TransportManager)}. + * UserBackupManagerService#createAndInitializeService(int, Context, Trampoline, HandlerThread, + * File, File, TransportManager)}. */ @Test public void testCreateAndInitializeService_withNullTransportManager_throws() { @@ -1117,6 +1125,7 @@ public class UserBackupManagerServiceTest { NullPointerException.class, () -> UserBackupManagerService.createAndInitializeService( + USER_ID, mContext, new Trampoline(mContext), mBackupThread, @@ -1127,7 +1136,7 @@ public class UserBackupManagerServiceTest { private UserBackupManagerService createUserBackupManagerServiceAndRunTasks() { return BackupManagerServiceTestUtils.createUserBackupManagerServiceAndRunTasks( - mContext, mBackupThread, mBaseStateDir, mDataDir, mTransportManager); + USER_ID, mContext, mBackupThread, mBaseStateDir, mDataDir, mTransportManager); } private void setUpPowerManager(UserBackupManagerService backupManagerService) { diff --git a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java index 099127cbeb4b..cf51e19edb00 100644 --- a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java +++ b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java @@ -169,6 +169,7 @@ public class KeyValueBackupTaskTest { private static final PackageData PACKAGE_2 = keyValuePackage(2); private static final String BACKUP_AGENT_SHARED_PREFS_SYNCHRONIZER_CLASS = "android.app.backup.BackupAgent$SharedPrefsSynchronizer"; + private static final int USER_ID = 10; @Mock private TransportManager mTransportManager; @Mock private DataChangedJournal mOldJournal; @@ -224,7 +225,7 @@ public class KeyValueBackupTaskTest { setUpBinderCallerAndApplicationAsSystem(mApplication); mBackupManagerService = spy(createUserBackupManagerServiceAndRunTasks( - mContext, mBaseStateDir, mDataDir, mTransportManager)); + USER_ID, mContext, mBaseStateDir, mDataDir, mTransportManager)); setUpBackupManagerServiceBasics( mBackupManagerService, mApplication, diff --git a/services/robotests/backup/src/com/android/server/backup/testing/BackupManagerServiceTestUtils.java b/services/robotests/backup/src/com/android/server/backup/testing/BackupManagerServiceTestUtils.java index 06f6d21b9ca9..b9785707fe96 100644 --- a/services/robotests/backup/src/com/android/server/backup/testing/BackupManagerServiceTestUtils.java +++ b/services/robotests/backup/src/com/android/server/backup/testing/BackupManagerServiceTestUtils.java @@ -58,13 +58,17 @@ public class BackupManagerServiceTestUtils { * <p>If the class-under-test is going to execute methods as the system, it's a good idea to * also call {@link #setUpBinderCallerAndApplicationAsSystem(Application)} before this method. * - * @see #createUserBackupManagerServiceAndRunTasks(Context, HandlerThread, File, File, + * @see #createUserBackupManagerServiceAndRunTasks(int, Context, HandlerThread, File, File, * TransportManager) */ public static UserBackupManagerService createUserBackupManagerServiceAndRunTasks( - Context context, File baseStateDir, File dataDir, TransportManager transportManager) { + int userId, + Context context, + File baseStateDir, + File dataDir, + TransportManager transportManager) { return createUserBackupManagerServiceAndRunTasks( - context, startBackupThread(null), baseStateDir, dataDir, transportManager); + userId, context, startBackupThread(null), baseStateDir, dataDir, transportManager); } /** @@ -75,6 +79,7 @@ public class BackupManagerServiceTestUtils { * also call {@link #setUpBinderCallerAndApplicationAsSystem(Application)} before this method. */ public static UserBackupManagerService createUserBackupManagerServiceAndRunTasks( + int userId, Context context, HandlerThread backupThread, File baseStateDir, @@ -82,6 +87,7 @@ public class BackupManagerServiceTestUtils { TransportManager transportManager) { UserBackupManagerService backupManagerService = UserBackupManagerService.createAndInitializeService( + userId, context, new Trampoline(context), backupThread, diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml index cf4d3a8070f9..1b5ba263e6dd 100644 --- a/services/tests/servicestests/AndroidManifest.xml +++ b/services/tests/servicestests/AndroidManifest.xml @@ -25,7 +25,6 @@ <uses-permission android:name="android.permission.READ_PHONE_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.BROADCAST_STICKY" /> - <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" /> <uses-permission android:name="android.permission.MANAGE_APP_TOKENS" /> <uses-permission android:name="android.permission.WAKE_LOCK" /> diff --git a/services/tests/servicestests/src/com/android/server/appops/AppOpsNotedWatcherTest.java b/services/tests/servicestests/src/com/android/server/appops/AppOpsNotedWatcherTest.java new file mode 100644 index 000000000000..52f434db3be3 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/appops/AppOpsNotedWatcherTest.java @@ -0,0 +1,100 @@ +/* + * 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.server.appops; + +import android.Manifest; +import android.app.AppOpsManager; +import android.app.AppOpsManager.OnOpNotedListener; +import android.content.Context; +import android.os.Process; +import androidx.test.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; +import androidx.test.filters.SmallTest; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InOrder; + + +import static org.junit.Assert.fail; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +/** + * Tests watching noted ops. + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class AppOpsNotedWatcherTest { + + private static final long NOTIFICATION_TIMEOUT_MILLIS = 5000; + + public void testWatchNotedOpsRequiresPermission() { + // Create a mock listener + final OnOpNotedListener listener = mock(OnOpNotedListener.class); + + // Try to start watching noted ops + final AppOpsManager appOpsManager = getContext().getSystemService(AppOpsManager.class); + try { + appOpsManager.startWatchingNoted(new String[]{AppOpsManager.OPSTR_FINE_LOCATION, + AppOpsManager.OPSTR_RECORD_AUDIO}, listener); + fail("Watching noted ops shoudl require " + Manifest.permission.WATCH_APPOPS); + } catch (SecurityException expected) { + /*ignored*/ + } + } + + @Test + public void testWatchNotedOps() { + // Create a mock listener + final OnOpNotedListener listener = mock(OnOpNotedListener.class); + + // Start watching noted ops + final AppOpsManager appOpsManager = getContext().getSystemService(AppOpsManager.class); + appOpsManager.startWatchingNoted(new String[]{AppOpsManager.OPSTR_FINE_LOCATION, + AppOpsManager.OPSTR_CAMERA}, listener); + + // Note some ops + appOpsManager.noteOp(AppOpsManager.OPSTR_FINE_LOCATION, Process.myUid(), + getContext().getPackageName()); + appOpsManager.noteOp(AppOpsManager.OPSTR_CAMERA, Process.myUid(), + getContext().getPackageName()); + + // Verify that we got called for the ops being noted + final InOrder inOrder = inOrder(listener); + inOrder.verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS) + .times(1)).onOpNoted(eq(AppOpsManager.OPSTR_FINE_LOCATION), + eq(Process.myUid()), eq(getContext().getPackageName()), + eq(AppOpsManager.MODE_ALLOWED)); + inOrder.verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS) + .times(1)).onOpNoted(eq(AppOpsManager.OPSTR_CAMERA), + eq(Process.myUid()), eq(getContext().getPackageName()), + eq(AppOpsManager.MODE_ALLOWED)); + + // Stop watching + appOpsManager.stopWatchingNoted(listener); + + // This should be the only two callbacks we got + verifyNoMoreInteractions(listener); + } + + private static Context getContext() { + return InstrumentationRegistry.getContext(); + } +}
\ No newline at end of file diff --git a/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java b/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java index ff31435162b1..0851cf3bc4c0 100644 --- a/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java +++ b/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java @@ -302,10 +302,22 @@ public class TrampolineTest { } @Test + public void dataChangedForUser_forwarded() throws RemoteException { + mTrampoline.initializeService(UserHandle.USER_SYSTEM); + + mTrampoline.dataChangedForUser(mUserId, PACKAGE_NAME); + + verify(mBackupManagerServiceMock).dataChanged(mUserId, PACKAGE_NAME); + } + + @Test public void dataChanged_forwarded() throws RemoteException { + TrampolineTestable.sCallingUserId = mUserId; mTrampoline.initializeService(UserHandle.USER_SYSTEM); + mTrampoline.dataChanged(PACKAGE_NAME); - verify(mBackupManagerServiceMock).dataChanged(PACKAGE_NAME); + + verify(mBackupManagerServiceMock).dataChanged(mUserId, PACKAGE_NAME); } @Test @@ -315,10 +327,22 @@ public class TrampolineTest { } @Test + public void clearBackupDataForUser_forwarded() throws RemoteException { + mTrampoline.initializeService(UserHandle.USER_SYSTEM); + + mTrampoline.clearBackupDataForUser(mUserId, TRANSPORT_NAME, PACKAGE_NAME); + + verify(mBackupManagerServiceMock).clearBackupData(mUserId, TRANSPORT_NAME, PACKAGE_NAME); + } + + @Test public void clearBackupData_forwarded() throws RemoteException { + TrampolineTestable.sCallingUserId = mUserId; mTrampoline.initializeService(UserHandle.USER_SYSTEM); + mTrampoline.clearBackupData(TRANSPORT_NAME, PACKAGE_NAME); - verify(mBackupManagerServiceMock).clearBackupData(TRANSPORT_NAME, PACKAGE_NAME); + + verify(mBackupManagerServiceMock).clearBackupData(mUserId, TRANSPORT_NAME, PACKAGE_NAME); } @Test @@ -328,10 +352,22 @@ public class TrampolineTest { } @Test + public void agentConnectedForUser_forwarded() throws RemoteException { + mTrampoline.initializeService(UserHandle.USER_SYSTEM); + + mTrampoline.agentConnectedForUser(mUserId, PACKAGE_NAME, mAgentMock); + + verify(mBackupManagerServiceMock).agentConnected(mUserId, PACKAGE_NAME, mAgentMock); + } + + @Test public void agentConnected_forwarded() throws RemoteException { + TrampolineTestable.sCallingUserId = mUserId; mTrampoline.initializeService(UserHandle.USER_SYSTEM); + mTrampoline.agentConnected(PACKAGE_NAME, mAgentMock); - verify(mBackupManagerServiceMock).agentConnected(PACKAGE_NAME, mAgentMock); + + verify(mBackupManagerServiceMock).agentConnected(mUserId, PACKAGE_NAME, mAgentMock); } @Test @@ -341,10 +377,22 @@ public class TrampolineTest { } @Test + public void agentDisconnectedForUser_forwarded() throws RemoteException { + mTrampoline.initializeService(UserHandle.USER_SYSTEM); + + mTrampoline.agentDisconnectedForUser(mUserId, PACKAGE_NAME); + + verify(mBackupManagerServiceMock).agentDisconnected(mUserId, PACKAGE_NAME); + } + + @Test public void agentDisconnected_forwarded() throws RemoteException { + TrampolineTestable.sCallingUserId = mUserId; mTrampoline.initializeService(UserHandle.USER_SYSTEM); + mTrampoline.agentDisconnected(PACKAGE_NAME); - verify(mBackupManagerServiceMock).agentDisconnected(PACKAGE_NAME); + + verify(mBackupManagerServiceMock).agentDisconnected(mUserId, PACKAGE_NAME); } @Test @@ -354,10 +402,22 @@ public class TrampolineTest { } @Test + public void restoreAtInstallForUser_forwarded() throws RemoteException { + mTrampoline.initializeService(UserHandle.USER_SYSTEM); + + mTrampoline.restoreAtInstallForUser(mUserId, PACKAGE_NAME, 123); + + verify(mBackupManagerServiceMock).restoreAtInstall(mUserId, PACKAGE_NAME, 123); + } + + @Test public void restoreAtInstall_forwarded() throws RemoteException { + TrampolineTestable.sCallingUserId = mUserId; mTrampoline.initializeService(UserHandle.USER_SYSTEM); + mTrampoline.restoreAtInstall(PACKAGE_NAME, 123); - verify(mBackupManagerServiceMock).restoreAtInstall(PACKAGE_NAME, 123); + + verify(mBackupManagerServiceMock).restoreAtInstall(mUserId, PACKAGE_NAME, 123); } @Test @@ -392,10 +452,22 @@ public class TrampolineTest { } @Test + public void setAutoRestoreForUser_forwarded() throws RemoteException { + mTrampoline.initializeService(UserHandle.USER_SYSTEM); + + mTrampoline.setAutoRestoreForUser(mUserId, true); + + verify(mBackupManagerServiceMock).setAutoRestore(mUserId, true); + } + + @Test public void setAutoRestore_forwarded() throws RemoteException { + TrampolineTestable.sCallingUserId = mUserId; mTrampoline.initializeService(UserHandle.USER_SYSTEM); + mTrampoline.setAutoRestore(true); - verify(mBackupManagerServiceMock).setAutoRestore(true); + + verify(mBackupManagerServiceMock).setAutoRestore(mUserId, true); } @Test @@ -408,7 +480,7 @@ public class TrampolineTest { public void setBackupProvisioned_forwarded() throws RemoteException { mTrampoline.initializeService(UserHandle.USER_SYSTEM); mTrampoline.setBackupProvisioned(true); - verify(mBackupManagerServiceMock).setBackupProvisioned(true); + verifyNoMoreInteractions(mBackupManagerServiceMock); } @Test @@ -507,15 +579,17 @@ public class TrampolineTest { @Test public void fullTransportBackup_calledBeforeInitialize_ignored() throws RemoteException { - mTrampoline.fullTransportBackup(PACKAGE_NAMES); + mTrampoline.fullTransportBackupForUser(mUserId, PACKAGE_NAMES); verifyNoMoreInteractions(mBackupManagerServiceMock); } @Test - public void fullTransportBackup_forwarded() throws RemoteException { + public void fullTransportBackupForUser_forwarded() throws RemoteException { mTrampoline.initializeService(UserHandle.USER_SYSTEM); - mTrampoline.fullTransportBackup(PACKAGE_NAMES); - verify(mBackupManagerServiceMock).fullTransportBackup(PACKAGE_NAMES); + + mTrampoline.fullTransportBackupForUser(mUserId, PACKAGE_NAMES); + + verify(mBackupManagerServiceMock).fullTransportBackup(mUserId, PACKAGE_NAMES); } @Test @@ -540,12 +614,43 @@ public class TrampolineTest { } @Test + public void acknowledgeFullBackupOrRestoreForUser_forwarded() throws RemoteException { + mTrampoline.initializeService(UserHandle.USER_SYSTEM); + + mTrampoline.acknowledgeFullBackupOrRestoreForUser( + mUserId, + 123, + true, + CURRENT_PASSWORD, + ENCRYPTION_PASSWORD, + mFullBackupRestoreObserverMock); + + verify(mBackupManagerServiceMock) + .acknowledgeAdbBackupOrRestore( + mUserId, + 123, + true, + CURRENT_PASSWORD, + ENCRYPTION_PASSWORD, + mFullBackupRestoreObserverMock); + } + + @Test public void acknowledgeFullBackupOrRestore_forwarded() throws RemoteException { + TrampolineTestable.sCallingUserId = mUserId; mTrampoline.initializeService(UserHandle.USER_SYSTEM); + mTrampoline.acknowledgeFullBackupOrRestore(123, true, CURRENT_PASSWORD, ENCRYPTION_PASSWORD, mFullBackupRestoreObserverMock); - verify(mBackupManagerServiceMock).acknowledgeAdbBackupOrRestore(123, true, CURRENT_PASSWORD, - ENCRYPTION_PASSWORD, mFullBackupRestoreObserverMock); + + verify(mBackupManagerServiceMock) + .acknowledgeAdbBackupOrRestore( + mUserId, + 123, + true, + CURRENT_PASSWORD, + ENCRYPTION_PASSWORD, + mFullBackupRestoreObserverMock); } @Test @@ -555,13 +660,22 @@ public class TrampolineTest { } @Test - public void getCurrentTransport_forwarded() throws RemoteException { - when(mBackupManagerServiceMock.getCurrentTransport()).thenReturn(TRANSPORT_NAME); + public void getCurrentTransportForUser_forwarded() throws RemoteException { + when(mBackupManagerServiceMock.getCurrentTransport(mUserId)).thenReturn(TRANSPORT_NAME); + mTrampoline.initializeService(UserHandle.USER_SYSTEM); + assertEquals(TRANSPORT_NAME, mTrampoline.getCurrentTransportForUser(mUserId)); + verify(mBackupManagerServiceMock).getCurrentTransport(mUserId); + } + + @Test + public void getCurrentTransport_forwarded() throws RemoteException { + TrampolineTestable.sCallingUserId = mUserId; + when(mBackupManagerServiceMock.getCurrentTransport(mUserId)).thenReturn(TRANSPORT_NAME); mTrampoline.initializeService(UserHandle.USER_SYSTEM); assertEquals(TRANSPORT_NAME, mTrampoline.getCurrentTransport()); - verify(mBackupManagerServiceMock).getCurrentTransport(); + verify(mBackupManagerServiceMock).getCurrentTransport(mUserId); } @Test @@ -571,28 +685,40 @@ public class TrampolineTest { } @Test - public void listAllTransports_forwarded() throws RemoteException { - when(mBackupManagerServiceMock.listAllTransports()).thenReturn(TRANSPORTS); + public void listAllTransportsForUser_forwarded() throws RemoteException { + when(mBackupManagerServiceMock.listAllTransports(mUserId)).thenReturn(TRANSPORTS); + mTrampoline.initializeService(UserHandle.USER_SYSTEM); + + assertEquals(TRANSPORTS, mTrampoline.listAllTransportsForUser(mUserId)); + verify(mBackupManagerServiceMock).listAllTransports(mUserId); + } + + @Test + public void listAllTransports_forwarded() throws RemoteException { + TrampolineTestable.sCallingUserId = mUserId; + when(mBackupManagerServiceMock.listAllTransports(mUserId)).thenReturn(TRANSPORTS); mTrampoline.initializeService(UserHandle.USER_SYSTEM); + assertEquals(TRANSPORTS, mTrampoline.listAllTransports()); - verify(mBackupManagerServiceMock).listAllTransports(); + verify(mBackupManagerServiceMock).listAllTransports(mUserId); } @Test - public void listAllTransportComponents_calledBeforeInitialize_ignored() throws RemoteException { - assertNull(mTrampoline.listAllTransportComponents()); + public void listAllTransportComponentsForUser_calledBeforeInitialize_ignored() + throws RemoteException { + assertNull(mTrampoline.listAllTransportComponentsForUser(mUserId)); verifyNoMoreInteractions(mBackupManagerServiceMock); } @Test - public void listAllTransportComponents_forwarded() throws RemoteException { - when(mBackupManagerServiceMock.listAllTransportComponents()).thenReturn( + public void listAllTransportComponentsForUser_forwarded() throws RemoteException { + when(mBackupManagerServiceMock.listAllTransportComponents(mUserId)).thenReturn( TRANSPORT_COMPONENTS); - mTrampoline.initializeService(UserHandle.USER_SYSTEM); - assertEquals(TRANSPORT_COMPONENTS, mTrampoline.listAllTransportComponents()); - verify(mBackupManagerServiceMock).listAllTransportComponents(); + + assertEquals(TRANSPORT_COMPONENTS, mTrampoline.listAllTransportComponentsForUser(mUserId)); + verify(mBackupManagerServiceMock).listAllTransportComponents(mUserId); } @Test @@ -611,21 +737,43 @@ public class TrampolineTest { } @Test - public void describeTransport_calledBeforeInitialize_ignored() throws RemoteException { - mTrampoline.updateTransportAttributes(TRANSPORT_COMPONENT_NAME, TRANSPORT_NAME, null, - "Transport Destination", null, "Data Management"); + public void updateTransportAttributesForUser_calledBeforeInitialize_ignored() + throws RemoteException { + mTrampoline.updateTransportAttributesForUser( + mUserId, + TRANSPORT_COMPONENT_NAME, + TRANSPORT_NAME, + null, + "Transport Destination", + null, + "Data Management"); + verifyNoMoreInteractions(mBackupManagerServiceMock); } @Test - public void describeTransport_forwarded() throws RemoteException { + public void updateTransportAttributesForUser_forwarded() throws RemoteException { when(mBackupManagerServiceMock.getTransportWhitelist()).thenReturn(TRANSPORTS); - mTrampoline.initializeService(UserHandle.USER_SYSTEM); - mTrampoline.updateTransportAttributes(TRANSPORT_COMPONENT_NAME, TRANSPORT_NAME, null, - "Transport Destination", null, "Data Management"); - verify(mBackupManagerServiceMock).updateTransportAttributes(TRANSPORT_COMPONENT_NAME, - TRANSPORT_NAME, null, "Transport Destination", null, "Data Management"); + + mTrampoline.updateTransportAttributesForUser( + mUserId, + TRANSPORT_COMPONENT_NAME, + TRANSPORT_NAME, + null, + "Transport Destination", + null, + "Data Management"); + + verify(mBackupManagerServiceMock) + .updateTransportAttributes( + mUserId, + TRANSPORT_COMPONENT_NAME, + TRANSPORT_NAME, + null, + "Transport Destination", + null, + "Data Management"); } @Test @@ -635,16 +783,31 @@ public class TrampolineTest { } @Test + public void selectBackupTransportForUser_forwarded() throws RemoteException { + mTrampoline.initializeService(UserHandle.USER_SYSTEM); + + mTrampoline.selectBackupTransportForUser(mUserId, TRANSPORT_NAME); + + verify(mBackupManagerServiceMock).selectBackupTransport(mUserId, TRANSPORT_NAME); + } + + @Test public void selectBackupTransport_forwarded() throws RemoteException { + TrampolineTestable.sCallingUserId = mUserId; mTrampoline.initializeService(UserHandle.USER_SYSTEM); + mTrampoline.selectBackupTransport(TRANSPORT_NAME); - verify(mBackupManagerServiceMock).selectBackupTransport(TRANSPORT_NAME); + + verify(mBackupManagerServiceMock).selectBackupTransport(mUserId, TRANSPORT_NAME); } @Test - public void selectBackupTransportAsync_calledBeforeInitialize_ignored() throws Exception { + public void selectBackupTransportAsyncForUser_calledBeforeInitialize_ignored() + throws Exception { LinkedBlockingQueue<Integer> q = new LinkedBlockingQueue(); - mTrampoline.selectBackupTransportAsync( + + mTrampoline.selectBackupTransportAsyncForUser( + mUserId, TRANSPORT_COMPONENT_NAME, new ISelectBackupTransportCallback() { @Override @@ -662,6 +825,7 @@ public class TrampolineTest { return null; } }); + verifyNoMoreInteractions(mBackupManagerServiceMock); Integer errorCode = q.poll(5, TimeUnit.SECONDS); assertNotNull(errorCode); @@ -669,17 +833,19 @@ public class TrampolineTest { } @Test - public void selectBackupTransportAsync_calledBeforeInitialize_ignored_nullListener() + public void selectBackupTransportAsyncForUser_calledBeforeInitialize_ignored_nullListener() throws Exception { - mTrampoline.selectBackupTransportAsync(TRANSPORT_COMPONENT_NAME, null); + mTrampoline.selectBackupTransportAsyncForUser(mUserId, TRANSPORT_COMPONENT_NAME, null); + verifyNoMoreInteractions(mBackupManagerServiceMock); // No crash. } @Test - public void selectBackupTransportAsync_calledBeforeInitialize_ignored_listenerThrowException() + public void selectBackupTransportAsyncForUser_calledBeforeInitialize_ignored_listenerThrows() throws Exception { - mTrampoline.selectBackupTransportAsync( + mTrampoline.selectBackupTransportAsyncForUser( + mUserId, TRANSPORT_COMPONENT_NAME, new ISelectBackupTransportCallback() { @Override @@ -697,16 +863,19 @@ public class TrampolineTest { return null; } }); + verifyNoMoreInteractions(mBackupManagerServiceMock); // No crash. } @Test - public void selectBackupTransportAsync_forwarded() throws RemoteException { + public void selectBackupTransportAsyncForUser_forwarded() throws RemoteException { mTrampoline.initializeService(UserHandle.USER_SYSTEM); - mTrampoline.selectBackupTransportAsync(TRANSPORT_COMPONENT_NAME, null); - verify(mBackupManagerServiceMock).selectBackupTransportAsync(TRANSPORT_COMPONENT_NAME, - null); + + mTrampoline.selectBackupTransportAsyncForUser(mUserId, TRANSPORT_COMPONENT_NAME, null); + + verify(mBackupManagerServiceMock) + .selectBackupTransportAsync(mUserId, TRANSPORT_COMPONENT_NAME, null); } @Test @@ -716,14 +885,28 @@ public class TrampolineTest { } @Test - public void getConfigurationIntent_forwarded() throws RemoteException { + public void getConfigurationIntentForUser_forwarded() throws RemoteException { Intent configurationIntentStub = new Intent(); - when(mBackupManagerServiceMock.getConfigurationIntent(TRANSPORT_NAME)).thenReturn( + when(mBackupManagerServiceMock.getConfigurationIntent(mUserId, TRANSPORT_NAME)).thenReturn( configurationIntentStub); + mTrampoline.initializeService(UserHandle.USER_SYSTEM); + + assertEquals( + configurationIntentStub, + mTrampoline.getConfigurationIntentForUser(mUserId, TRANSPORT_NAME)); + verify(mBackupManagerServiceMock).getConfigurationIntent(mUserId, TRANSPORT_NAME); + } + @Test + public void getConfigurationIntent_forwarded() throws RemoteException { + TrampolineTestable.sCallingUserId = mUserId; + Intent configurationIntentStub = new Intent(); + when(mBackupManagerServiceMock.getConfigurationIntent(mUserId, TRANSPORT_NAME)).thenReturn( + configurationIntentStub); mTrampoline.initializeService(UserHandle.USER_SYSTEM); + assertEquals(configurationIntentStub, mTrampoline.getConfigurationIntent(TRANSPORT_NAME)); - verify(mBackupManagerServiceMock).getConfigurationIntent(TRANSPORT_NAME); + verify(mBackupManagerServiceMock).getConfigurationIntent(mUserId, TRANSPORT_NAME); } @Test @@ -733,13 +916,26 @@ public class TrampolineTest { } @Test + public void getDestinationStringForUser_forwarded() throws RemoteException { + when(mBackupManagerServiceMock.getDestinationString(mUserId, TRANSPORT_NAME)).thenReturn( + DESTINATION_STRING); + mTrampoline.initializeService(UserHandle.USER_SYSTEM); + + assertEquals( + DESTINATION_STRING, + mTrampoline.getDestinationStringForUser(mUserId, TRANSPORT_NAME)); + verify(mBackupManagerServiceMock).getDestinationString(mUserId, TRANSPORT_NAME); + } + + @Test public void getDestinationString_forwarded() throws RemoteException { - when(mBackupManagerServiceMock.getDestinationString(TRANSPORT_NAME)).thenReturn( + TrampolineTestable.sCallingUserId = mUserId; + when(mBackupManagerServiceMock.getDestinationString(mUserId, TRANSPORT_NAME)).thenReturn( DESTINATION_STRING); mTrampoline.initializeService(UserHandle.USER_SYSTEM); assertEquals(DESTINATION_STRING, mTrampoline.getDestinationString(TRANSPORT_NAME)); - verify(mBackupManagerServiceMock).getDestinationString(TRANSPORT_NAME); + verify(mBackupManagerServiceMock).getDestinationString(mUserId, TRANSPORT_NAME); } @Test @@ -749,14 +945,28 @@ public class TrampolineTest { } @Test - public void getDataManagementIntent_forwarded() throws RemoteException { + public void getDataManagementIntentForUser_forwarded() throws RemoteException { Intent dataManagementIntent = new Intent(); - when(mBackupManagerServiceMock.getDataManagementIntent(TRANSPORT_NAME)).thenReturn( + when(mBackupManagerServiceMock.getDataManagementIntent(mUserId, TRANSPORT_NAME)).thenReturn( dataManagementIntent); + mTrampoline.initializeService(UserHandle.USER_SYSTEM); + + assertEquals( + dataManagementIntent, + mTrampoline.getDataManagementIntentForUser(mUserId, TRANSPORT_NAME)); + verify(mBackupManagerServiceMock).getDataManagementIntent(mUserId, TRANSPORT_NAME); + } + @Test + public void getDataManagementIntent_forwarded() throws RemoteException { + TrampolineTestable.sCallingUserId = mUserId; + Intent dataManagementIntent = new Intent(); + when(mBackupManagerServiceMock.getDataManagementIntent(mUserId, TRANSPORT_NAME)).thenReturn( + dataManagementIntent); mTrampoline.initializeService(UserHandle.USER_SYSTEM); + assertEquals(dataManagementIntent, mTrampoline.getDataManagementIntent(TRANSPORT_NAME)); - verify(mBackupManagerServiceMock).getDataManagementIntent(TRANSPORT_NAME); + verify(mBackupManagerServiceMock).getDataManagementIntent(mUserId, TRANSPORT_NAME); } @Test @@ -766,26 +976,42 @@ public class TrampolineTest { } @Test - public void getDataManagementLabel_forwarded() throws RemoteException { - when(mBackupManagerServiceMock.getDataManagementLabel(TRANSPORT_NAME)).thenReturn( + public void getDataManagementLabelForUser_forwarded() throws RemoteException { + when(mBackupManagerServiceMock.getDataManagementLabel(mUserId, TRANSPORT_NAME)).thenReturn( DATA_MANAGEMENT_LABEL); + mTrampoline.initializeService(UserHandle.USER_SYSTEM); + + assertEquals( + DATA_MANAGEMENT_LABEL, + mTrampoline.getDataManagementLabelForUser(mUserId, TRANSPORT_NAME)); + verify(mBackupManagerServiceMock).getDataManagementLabel(mUserId, TRANSPORT_NAME); + } + @Test + public void getDataManagementLabel_forwarded() throws RemoteException { + TrampolineTestable.sCallingUserId = mUserId; + when(mBackupManagerServiceMock.getDataManagementLabel(mUserId, TRANSPORT_NAME)).thenReturn( + DATA_MANAGEMENT_LABEL); mTrampoline.initializeService(UserHandle.USER_SYSTEM); + assertEquals(DATA_MANAGEMENT_LABEL, mTrampoline.getDataManagementLabel(TRANSPORT_NAME)); - verify(mBackupManagerServiceMock).getDataManagementLabel(TRANSPORT_NAME); + verify(mBackupManagerServiceMock).getDataManagementLabel(mUserId, TRANSPORT_NAME); } @Test public void beginRestoreSession_calledBeforeInitialize_ignored() throws RemoteException { - mTrampoline.beginRestoreSession(PACKAGE_NAME, TRANSPORT_NAME); + mTrampoline.beginRestoreSessionForUser(mUserId, PACKAGE_NAME, TRANSPORT_NAME); verifyNoMoreInteractions(mBackupManagerServiceMock); } @Test - public void beginRestoreSession_forwarded() throws RemoteException { + public void beginRestoreSessionForUser_forwarded() throws RemoteException { mTrampoline.initializeService(UserHandle.USER_SYSTEM); - mTrampoline.beginRestoreSession(PACKAGE_NAME, TRANSPORT_NAME); - verify(mBackupManagerServiceMock).beginRestoreSession(PACKAGE_NAME, TRANSPORT_NAME); + + mTrampoline.beginRestoreSessionForUser(mUserId, PACKAGE_NAME, TRANSPORT_NAME); + + verify(mBackupManagerServiceMock) + .beginRestoreSession(mUserId, PACKAGE_NAME, TRANSPORT_NAME); } @Test @@ -796,39 +1022,46 @@ public class TrampolineTest { @Test public void opComplete_forwarded() throws RemoteException { + TrampolineTestable.sCallingUserId = mUserId; mTrampoline.initializeService(UserHandle.USER_SYSTEM); + mTrampoline.opComplete(1, 2); - verify(mBackupManagerServiceMock).opComplete(1, 2); + + verify(mBackupManagerServiceMock).opComplete(mUserId, 1, 2); } @Test - public void getAvailableRestoreToken_calledBeforeInitialize_ignored() throws RemoteException { - assertEquals(0, mTrampoline.getAvailableRestoreToken(PACKAGE_NAME)); + public void getAvailableRestoreTokenForUser_calledBeforeInitialize_ignored() + throws RemoteException { + assertEquals(0, mTrampoline.getAvailableRestoreTokenForUser(mUserId, PACKAGE_NAME)); verifyNoMoreInteractions(mBackupManagerServiceMock); } @Test - public void getAvailableRestoreToken_forwarded() throws RemoteException { - when(mBackupManagerServiceMock.getAvailableRestoreToken(PACKAGE_NAME)).thenReturn(123L); - + public void getAvailableRestoreTokenForUser_forwarded() throws RemoteException { + when(mBackupManagerServiceMock.getAvailableRestoreToken(mUserId, PACKAGE_NAME)) + .thenReturn(123L); mTrampoline.initializeService(UserHandle.USER_SYSTEM); - assertEquals(123, mTrampoline.getAvailableRestoreToken(PACKAGE_NAME)); - verify(mBackupManagerServiceMock).getAvailableRestoreToken(PACKAGE_NAME); + + assertEquals(123, mTrampoline.getAvailableRestoreTokenForUser(mUserId, PACKAGE_NAME)); + verify(mBackupManagerServiceMock).getAvailableRestoreToken(mUserId, PACKAGE_NAME); } @Test - public void isAppEligibleForBackup_calledBeforeInitialize_ignored() throws RemoteException { - assertFalse(mTrampoline.isAppEligibleForBackup(PACKAGE_NAME)); + public void isAppEligibleForBackupForUser_calledBeforeInitialize_ignored() + throws RemoteException { + assertFalse(mTrampoline.isAppEligibleForBackupForUser(mUserId, PACKAGE_NAME)); verifyNoMoreInteractions(mBackupManagerServiceMock); } @Test - public void isAppEligibleForBackup_forwarded() throws RemoteException { - when(mBackupManagerServiceMock.isAppEligibleForBackup(PACKAGE_NAME)).thenReturn(true); - + public void isAppEligibleForBackupForUser_forwarded() throws RemoteException { + when(mBackupManagerServiceMock.isAppEligibleForBackup(mUserId, PACKAGE_NAME)) + .thenReturn(true); mTrampoline.initializeService(UserHandle.USER_SYSTEM); - assertTrue(mTrampoline.isAppEligibleForBackup(PACKAGE_NAME)); - verify(mBackupManagerServiceMock).isAppEligibleForBackup(PACKAGE_NAME); + + assertTrue(mTrampoline.isAppEligibleForBackupForUser(mUserId, PACKAGE_NAME)); + verify(mBackupManagerServiceMock).isAppEligibleForBackup(mUserId, PACKAGE_NAME); } @Test diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java index e9bfa8f4e0c8..abf90402250c 100644 --- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -149,12 +149,11 @@ public class DisplayManagerServiceTest { verify(mMockInputManagerInternal).setDisplayViewports(viewportCaptor.capture()); List<DisplayViewport> viewports = viewportCaptor.getValue(); - // Expect to receive 3 viewports: internal, external, and virtual - assertEquals(3, viewports.size()); + // Expect to receive 2 viewports: internal, and virtual + assertEquals(2, viewports.size()); DisplayViewport virtualViewport = null; DisplayViewport internalViewport = null; - DisplayViewport externalViewport = null; for (int i = 0; i < viewports.size(); i++) { DisplayViewport v = viewports.get(i); switch (v.type) { @@ -163,7 +162,7 @@ public class DisplayManagerServiceTest { break; } case DisplayViewport.VIEWPORT_EXTERNAL: { - externalViewport = v; + fail("EXTERNAL viewport should not exist."); break; } case DisplayViewport.VIEWPORT_VIRTUAL: { @@ -172,14 +171,12 @@ public class DisplayManagerServiceTest { } } } - // INTERNAL and EXTERNAL viewports get created upon access + // INTERNAL viewport gets created upon access. assertNotNull(internalViewport); - assertNotNull(externalViewport); assertNotNull(virtualViewport); - // INTERNAL and EXTERNAL + // INTERNAL assertTrue(internalViewport.valid); - assertTrue(externalViewport.valid); // VIRTUAL assertEquals(height, virtualViewport.deviceHeight); @@ -216,39 +213,16 @@ public class DisplayManagerServiceTest { verify(mMockInputManagerInternal).setDisplayViewports(viewportCaptor.capture()); List<DisplayViewport> viewports = viewportCaptor.getValue(); - // Expect to receive 2 viewports: 1 internal, 1 external - assertEquals(2, viewports.size()); + // Expect to receive actual viewports: 1 internal + assertEquals(1, viewports.size()); - DisplayViewport internalViewport = null; - DisplayViewport externalViewport = null; - for (int i = 0; i < viewports.size(); i++) { - DisplayViewport v = viewports.get(i); - switch (v.type) { - case DisplayViewport.VIEWPORT_INTERNAL: { - internalViewport = v; - break; - } - case DisplayViewport.VIEWPORT_EXTERNAL: { - externalViewport = v; - break; - } - default: { - fail("Unexpected viewport type: " + DisplayViewport.typeToString(v.type)); - break; - } - } - } - // INTERNAL and EXTERNAL viewports get created upon access + DisplayViewport internalViewport = viewports.get(0); + + // INTERNAL is the only one actual display. assertNotNull(internalViewport); - assertNotNull(externalViewport); + assertEquals(DisplayViewport.VIEWPORT_INTERNAL, internalViewport.type); assertTrue(internalViewport.valid); assertEquals(displayId, internalViewport.displayId); - - // To simplify comparison, override the type for external Viewport - // TODO (b/116850516) remove this - externalViewport.type = internalViewport.type; - assertEquals(internalViewport, externalViewport); - externalViewport.type = DisplayViewport.VIEWPORT_EXTERNAL; // undo the changes above } @Test 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 527a1eef7b0f..2aed35f32d6c 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -208,6 +208,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { private static class TestableNotificationManagerService extends NotificationManagerService { int countSystemChecks = 0; boolean isSystemUid = true; + int countLogSmartSuggestionsVisible = 0; public TestableNotificationManagerService(Context context) { super(context); @@ -244,6 +245,14 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { protected void handleSavePolicyFile() { return; } + + @Override + void logSmartSuggestionsVisible(NotificationRecord r) { + super.logSmartSuggestionsVisible(r); + countLogSmartSuggestionsVisible++; + } + + } private class TestableToastCallback extends ITransientNotification.Stub { @@ -3507,6 +3516,12 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void testAppOverlay() throws Exception { + mBinderService.setAppOverlaysAllowed(PKG, mUid, false); + assertFalse(mBinderService.areAppOverlaysAllowedForPackage(PKG, mUid)); + } + + @Test public void testIsCallerInstantApp_primaryUser() throws Exception { ApplicationInfo info = new ApplicationInfo(); info.privateFlags = ApplicationInfo.PRIVATE_FLAG_INSTANT; @@ -3777,4 +3792,43 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { verify(mAssistants).notifyAssistantActionClicked( eq(r.sbn), eq(actionIndex), eq(action), eq(generatedByAssistant)); } + + @Test + public void testLogSmartSuggestionsVisible_triggerOnExpandAndVisible() { + NotificationRecord r = generateNotificationRecord(mTestNotificationChannel); + mService.addNotification(r); + + mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), false, true); + NotificationVisibility[] notificationVisibility = new NotificationVisibility[] { + NotificationVisibility.obtain(r.getKey(), 0, 0, true) + }; + mService.mNotificationDelegate.onNotificationVisibilityChanged(notificationVisibility, + new NotificationVisibility[0]); + + assertEquals(1, mService.countLogSmartSuggestionsVisible); + } + + @Test + public void testLogSmartSuggestionsVisible_noTriggerOnExpand() { + NotificationRecord r = generateNotificationRecord(mTestNotificationChannel); + mService.addNotification(r); + + mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), false, true); + + assertEquals(0, mService.countLogSmartSuggestionsVisible); + } + + @Test + public void testLogSmartSuggestionsVisible_noTriggerOnVisible() { + NotificationRecord r = generateNotificationRecord(mTestNotificationChannel); + mService.addNotification(r); + + NotificationVisibility[] notificationVisibility = new NotificationVisibility[] { + NotificationVisibility.obtain(r.getKey(), 0, 0, true) + }; + mService.mNotificationDelegate.onNotificationVisibilityChanged(notificationVisibility, + new NotificationVisibility[0]); + + assertEquals(0, mService.countLogSmartSuggestionsVisible); + } } 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 b0279357d372..0b7348194b26 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -1584,39 +1584,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { } @Test - public void testUpdateGroup_fromSystem_appOverlay() { - NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1"); - mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, ncg, true); - - // from system, allowed - NotificationChannelGroup update = ncg.clone(); - update.setAllowAppOverlay(false); - - mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, update, false); - NotificationChannelGroup updated = - mHelper.getNotificationChannelGroup("group1", PKG_N_MR1, UID_N_MR1); - assertFalse(updated.canOverlayApps()); - assertEquals(NotificationChannelGroup.USER_LOCKED_ALLOW_APP_OVERLAY, - updated.getUserLockedFields()); - } - - @Test - public void testUpdateGroup_fromApp_appOverlay() { - NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1"); - mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, ncg, true); - - // from app, not allowed - NotificationChannelGroup update = new NotificationChannelGroup("group1", "name1"); - update.setAllowAppOverlay(false); - - mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, ncg, true); - NotificationChannelGroup updated = - mHelper.getNotificationChannelGroup("group1", PKG_N_MR1, UID_N_MR1); - assertTrue(updated.canOverlayApps()); - assertEquals(0, updated.getUserLockedFields()); - } - - @Test public void testCannotCreateChannel_badGroup() { NotificationChannel channel1 = new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH); @@ -2192,4 +2159,32 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper.toggleNotificationDelegate(PKG_O, UID_O, true); assertEquals("other", mHelper.getNotificationDelegate(PKG_O, UID_O)); } + + @Test + public void testAllowAppOverlay_defaults() throws Exception { + assertTrue(mHelper.areAppOverlaysAllowed(PKG_O, UID_O)); + + ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false); + mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper); + loadStreamXml(baos, false); + + assertTrue(mHelper.areAppOverlaysAllowed(PKG_O, UID_O)); + assertEquals(0, mHelper.getAppLockedFields(PKG_O, UID_O)); + } + + @Test + public void testAllowAppOverlay_xml() throws Exception { + mHelper.setAppOverlaysAllowed(PKG_O, UID_O, false); + assertFalse(mHelper.areAppOverlaysAllowed(PKG_O, UID_O)); + assertEquals(PreferencesHelper.LockableAppFields.USER_LOCKED_APP_OVERLAY, + mHelper.getAppLockedFields(PKG_O, UID_O)); + + ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false); + mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper); + loadStreamXml(baos, false); + + assertFalse(mHelper.areAppOverlaysAllowed(PKG_O, UID_O)); + assertEquals(PreferencesHelper.LockableAppFields.USER_LOCKED_APP_OVERLAY, + mHelper.getAppLockedFields(PKG_O, UID_O)); + } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java index b955e56c80a8..49f134fdeac0 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java @@ -76,7 +76,7 @@ public class SnoozeHelperTest extends UiServiceTestCase { verify(mAm, times(1)).setExactAndAllowWhileIdle( anyInt(), captor.capture(), any(PendingIntent.class)); long actualSnoozedUntilDuration = captor.getValue() - SystemClock.elapsedRealtime(); - assertTrue(Math.abs(actualSnoozedUntilDuration - 1000) < 25); + assertTrue(Math.abs(actualSnoozedUntilDuration - 1000) < 250); assertTrue(mSnoozeHelper.isSnoozed( UserHandle.USER_SYSTEM, r.sbn.getPackageName(), r.getKey())); } diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java index e9889948c341..bf4b52eb72aa 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java @@ -52,7 +52,6 @@ import android.platform.test.annotations.Presubmit; import android.provider.Settings; import android.view.Surface; -import androidx.test.filters.FlakyTest; import androidx.test.filters.SmallTest; import com.android.internal.util.test.FakeSettingsProvider; @@ -81,7 +80,6 @@ import java.util.concurrent.TimeUnit; */ @SmallTest @Presubmit -@FlakyTest(detail = "Confirm stable in post-submit before removing") public class DisplayRotationTests { private static final long UI_HANDLER_WAIT_TIMEOUT_MS = 50; diff --git a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java index f3a125bf79e4..86353643c128 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java @@ -40,7 +40,6 @@ import android.platform.test.annotations.Presubmit; import android.view.DisplayInfo; import androidx.test.InstrumentationRegistry; -import androidx.test.filters.FlakyTest; import androidx.test.filters.MediumTest; import com.android.server.LocalServices; @@ -65,7 +64,6 @@ import java.util.function.Predicate; */ @MediumTest @Presubmit -@FlakyTest(detail = "Confirm stable in post-submit before removing") public class LaunchParamsPersisterTests extends ActivityTestsBase { private static final int TEST_USER_ID = 3; private static final int ALTERNATIVE_USER_ID = 0; diff --git a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java index 9b18388b5305..58302d6b8b75 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java @@ -23,8 +23,8 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.content.pm.ActivityInfo.FLAG_ALWAYS_FOCUSABLE; -import static android.content.pm.ActivityInfo.LAUNCH_MULTIPLE; import static android.view.Display.DEFAULT_DISPLAY; + import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; @@ -36,7 +36,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.server.wm.ActivityDisplay.POSITION_TOP; import static com.android.server.wm.ActivityStack.REMOVE_TASK_MODE_DESTROYING; import static com.android.server.wm.RootActivityContainer.MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE; -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.assertNotNull; @@ -47,18 +47,27 @@ import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.contains; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.refEq; import android.app.ActivityOptions; +import android.content.ComponentName; +import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; +import android.content.pm.ResolveInfo; import android.graphics.Rect; -import android.os.Build; import android.platform.test.annotations.Presubmit; +import android.util.Pair; + import androidx.test.filters.MediumTest; + +import com.android.internal.app.ResolverActivity; + import org.junit.Before; import org.junit.Test; import java.util.ArrayList; +import java.util.List; /** * Tests for the {@link ActivityStackSupervisor} class. @@ -385,31 +394,10 @@ public class RootActivityContainerTests extends ActivityTestsBase { } /** - * Tests home activities that targeted sdk before Q cannot start on secondary display. - */ - @Test - public void testStartHomeTargetSdkBeforeQ() throws Exception { - final TestActivityDisplay secondDisplay = spy(createNewActivityDisplay()); - mRootActivityContainer.addChild(secondDisplay, POSITION_TOP); - doReturn(true).when(secondDisplay).supportsSystemDecorations(); - - final ActivityInfo info = new ActivityInfo(); - info.launchMode = LAUNCH_MULTIPLE; - info.applicationInfo = new ApplicationInfo(); - info.applicationInfo.targetSdkVersion = Build.VERSION_CODES.Q; - assertTrue(mRootActivityContainer.canStartHomeOnDisplay(info, secondDisplay.mDisplayId, - false /* allowInstrumenting */)); - - info.applicationInfo.targetSdkVersion = Build.VERSION_CODES.P; - assertFalse(mRootActivityContainer.canStartHomeOnDisplay(info, secondDisplay.mDisplayId, - false /* allowInstrumenting */)); - } - - /** * Tests that home activities can be started on the displays that supports system decorations. */ - @Test - public void testStartHomeOnAllDisplays() { + // TODO (b/118206886): Will add it back once launcher's patch is merged into master. + private void testStartHomeOnAllDisplays() { // Create secondary displays. final TestActivityDisplay secondDisplay = spy(createNewActivityDisplay()); mRootActivityContainer.addChild(secondDisplay, POSITION_TOP); @@ -477,4 +465,142 @@ public class RootActivityContainerTests extends ActivityTestsBase { assertTrue(mRootActivityContainer.canStartHomeOnDisplay(info, DEFAULT_DISPLAY, true /* allowInstrumenting*/)); } + + /** + * Tests that secondary home should be selected if default home not set. + */ + @Test + public void testResolveSecondaryHomeActivityWhenDefaultHomeNotSet() { + final Intent defaultHomeIntent = mService.getHomeIntent(); + final ActivityInfo aInfoDefault = new ActivityInfo(); + aInfoDefault.name = ResolverActivity.class.getName(); + doReturn(aInfoDefault).when(mRootActivityContainer).resolveHomeActivity(anyInt(), + refEq(defaultHomeIntent)); + + final String secondaryHomeComponent = mService.mContext.getResources().getString( + com.android.internal.R.string.config_secondaryHomeComponent); + final ComponentName comp = ComponentName.unflattenFromString(secondaryHomeComponent); + final Intent secondaryHomeIntent = mService.getSecondaryHomeIntent(null); + final ActivityInfo aInfoSecondary = new ActivityInfo(); + aInfoSecondary.name = comp.getClassName(); + doReturn(aInfoSecondary).when(mRootActivityContainer).resolveHomeActivity(anyInt(), + refEq(secondaryHomeIntent)); + + // Should fallback to secondary home if default home not set. + final Pair<ActivityInfo, Intent> resolvedInfo = mRootActivityContainer + .resolveSecondaryHomeActivity(0 /* userId */, 1 /* displayId */); + + assertEquals(comp.getClassName(), resolvedInfo.first.name); + } + + /** + * Tests that secondary home should be selected if default home not support secondary displays + * or there is no matched activity in the same package as selected default home. + */ + @Test + public void testResolveSecondaryHomeActivityWhenDefaultHomeNotSupportMultiDisplay() { + final Intent defaultHomeIntent = mService.getHomeIntent(); + final ActivityInfo aInfoDefault = new ActivityInfo(); + aInfoDefault.name = "fakeHomeActivity"; + aInfoDefault.applicationInfo = new ApplicationInfo(); + aInfoDefault.applicationInfo.packageName = "fakeHomePackage"; + doReturn(aInfoDefault).when(mRootActivityContainer).resolveHomeActivity(anyInt(), + refEq(defaultHomeIntent)); + + final List<ResolveInfo> resolutions = new ArrayList<>(); + doReturn(resolutions).when(mRootActivityContainer).resolveActivities(anyInt(), any()); + + final String secondaryHomeComponent = mService.mContext.getResources().getString( + com.android.internal.R.string.config_secondaryHomeComponent); + final ComponentName comp = ComponentName.unflattenFromString(secondaryHomeComponent); + final Intent secondaryHomeIntent = mService.getSecondaryHomeIntent(null); + final ActivityInfo aInfoSecondary = new ActivityInfo(); + aInfoSecondary.name = comp.getClassName(); + doReturn(aInfoSecondary).when(mRootActivityContainer).resolveHomeActivity(anyInt(), + refEq(secondaryHomeIntent)); + + // Should fallback to secondary home if selected default home not support secondary displays + // or there is no matched activity in the same package as selected default home. + final Pair<ActivityInfo, Intent> resolvedInfo = mRootActivityContainer + .resolveSecondaryHomeActivity(0 /* userId */, 1 /* displayId */); + + assertEquals(comp.getClassName(), resolvedInfo.first.name); + } + + /** + * Tests that default home activity should be selected if it already support secondary displays. + */ + @Test + public void testResolveSecondaryHomeActivityWhenDefaultHomeSupportMultiDisplay() { + final Intent homeIntent = mService.getHomeIntent(); + final ActivityInfo aInfoDefault = new ActivityInfo(); + aInfoDefault.name = "fakeHomeActivity"; + aInfoDefault.applicationInfo = new ApplicationInfo(); + aInfoDefault.applicationInfo.packageName = "fakeHomePackage"; + doReturn(aInfoDefault).when(mRootActivityContainer).resolveHomeActivity(anyInt(), + refEq(homeIntent)); + + final List<ResolveInfo> resolutions = new ArrayList<>(); + final ResolveInfo infoFake1 = new ResolveInfo(); + infoFake1.activityInfo = new ActivityInfo(); + infoFake1.activityInfo.name = "fakeActivity1"; + infoFake1.activityInfo.applicationInfo = new ApplicationInfo(); + infoFake1.activityInfo.applicationInfo.packageName = "fakePackage1"; + final ResolveInfo infoFake2 = new ResolveInfo(); + infoFake2.activityInfo = aInfoDefault; + resolutions.add(infoFake1); + resolutions.add(infoFake2); + doReturn(resolutions).when(mRootActivityContainer).resolveActivities(anyInt(), any()); + + doReturn(true).when(mRootActivityContainer).canStartHomeOnDisplay( + any(), anyInt(), anyBoolean()); + + // Use default home activity if it support secondary displays. + final Pair<ActivityInfo, Intent> resolvedInfo = mRootActivityContainer + .resolveSecondaryHomeActivity(0 /* userId */, 1 /* displayId */); + + assertEquals(aInfoDefault.applicationInfo.packageName, + resolvedInfo.first.applicationInfo.packageName); + assertEquals(aInfoDefault.name, resolvedInfo.first.name); + } + + /** + * Tests that the first one that matches should be selected if there are multiple activities. + */ + @Test + public void testResolveSecondaryHomeActivityWhenOtherActivitySupportMultiDisplay() { + final Intent homeIntent = mService.getHomeIntent(); + final ActivityInfo aInfoDefault = new ActivityInfo(); + aInfoDefault.name = "fakeHomeActivity"; + aInfoDefault.applicationInfo = new ApplicationInfo(); + aInfoDefault.applicationInfo.packageName = "fakeHomePackage"; + doReturn(aInfoDefault).when(mRootActivityContainer).resolveHomeActivity(anyInt(), + refEq(homeIntent)); + + final List<ResolveInfo> resolutions = new ArrayList<>(); + final ResolveInfo infoFake1 = new ResolveInfo(); + infoFake1.activityInfo = new ActivityInfo(); + infoFake1.activityInfo.name = "fakeActivity1"; + infoFake1.activityInfo.applicationInfo = new ApplicationInfo(); + infoFake1.activityInfo.applicationInfo.packageName = "fakePackage1"; + final ResolveInfo infoFake2 = new ResolveInfo(); + infoFake2.activityInfo = new ActivityInfo(); + infoFake2.activityInfo.name = "fakeActivity2"; + infoFake2.activityInfo.applicationInfo = new ApplicationInfo(); + infoFake2.activityInfo.applicationInfo.packageName = "fakePackage2"; + resolutions.add(infoFake1); + resolutions.add(infoFake2); + doReturn(resolutions).when(mRootActivityContainer).resolveActivities(anyInt(), any()); + + doReturn(true).when(mRootActivityContainer).canStartHomeOnDisplay( + any(), anyInt(), anyBoolean()); + + // Use the first one of matched activities in the same package as selected default home. + final Pair<ActivityInfo, Intent> resolvedInfo = mRootActivityContainer + .resolveSecondaryHomeActivity(0 /* userId */, 1 /* displayId */); + + assertEquals(infoFake1.activityInfo.applicationInfo.packageName, + resolvedInfo.first.applicationInfo.packageName); + assertEquals(infoFake1.activityInfo.name, resolvedInfo.first.name); + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java index 0bd681bd69a2..7186e22cefef 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java @@ -47,7 +47,6 @@ import android.platform.test.annotations.Presubmit; import android.view.Display; import android.view.Gravity; -import androidx.test.filters.FlakyTest; import androidx.test.filters.SmallTest; import com.android.server.wm.LaunchParamsController.LaunchParams; @@ -64,7 +63,6 @@ import java.util.Locale; * atest WmTests:TaskLaunchParamsModifierTests */ @SmallTest -@FlakyTest(detail = "Confirm stable in post-submit before removing") @Presubmit public class TaskLaunchParamsModifierTests extends ActivityTestsBase { diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java index 3991e06d6f96..c343fe7d0675 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java @@ -20,6 +20,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static com.android.dx.mockito.inline.extended.ExtendedMockito.any; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; import static org.junit.Assert.assertFalse; @@ -66,6 +67,10 @@ public class TaskPositioningControllerTests extends WindowTestsBase { synchronized (mWm.mGlobalLock) { mWm.mWindowMap.put(mWindow.mClient.asBinder(), mWindow); } + + spyOn(mDisplayContent); + InputMonitor inputMonitor = mock(InputMonitor.class); + when(mDisplayContent.getInputMonitor()).thenReturn(inputMonitor); } @Test diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java index 60cb08f00f89..294b7509698b 100644 --- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java +++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java @@ -16,6 +16,12 @@ package com.android.server.usb; +import static android.hardware.usb.UsbPortStatus.DATA_ROLE_DEVICE; +import static android.hardware.usb.UsbPortStatus.DATA_ROLE_HOST; +import static android.hardware.usb.UsbPortStatus.MODE_AUDIO_ACCESSORY; +import static android.hardware.usb.UsbPortStatus.POWER_ROLE_SINK; +import static android.hardware.usb.UsbPortStatus.POWER_ROLE_SOURCE; + import static com.android.internal.usb.DumpUtils.writeAccessory; import static com.android.internal.util.dump.DumpUtils.writeStringIfNotNull; @@ -36,6 +42,7 @@ import android.content.pm.PackageManager; import android.content.res.Resources; import android.debug.AdbManagerInternal; import android.debug.IAdbTransport; +import android.hardware.usb.ParcelableUsbPort; import android.hardware.usb.UsbAccessory; import android.hardware.usb.UsbConfiguration; import android.hardware.usb.UsbConstants; @@ -294,9 +301,10 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser BroadcastReceiver portReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - UsbPort port = intent.getParcelableExtra(UsbManager.EXTRA_PORT); + ParcelableUsbPort port = intent.getParcelableExtra(UsbManager.EXTRA_PORT); UsbPortStatus status = intent.getParcelableExtra(UsbManager.EXTRA_PORT_STATUS); - mHandler.updateHostState(port, status); + mHandler.updateHostState( + port.getUsbPort(context.getSystemService(UsbManager.class)), status); } }; @@ -821,23 +829,20 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser boolean prevHostConnected = mHostConnected; UsbPort port = (UsbPort) args.arg1; UsbPortStatus status = (UsbPortStatus) args.arg2; - mHostConnected = status.getCurrentDataRole() == UsbPort.DATA_ROLE_HOST; - mSourcePower = status.getCurrentPowerRole() == UsbPort.POWER_ROLE_SOURCE; - mSinkPower = status.getCurrentPowerRole() == UsbPort.POWER_ROLE_SINK; - mAudioAccessoryConnected = - (status.getCurrentMode() == UsbPort.MODE_AUDIO_ACCESSORY); - mAudioAccessorySupported = port.isModeSupported(UsbPort.MODE_AUDIO_ACCESSORY); + mHostConnected = status.getCurrentDataRole() == DATA_ROLE_HOST; + mSourcePower = status.getCurrentPowerRole() == POWER_ROLE_SOURCE; + mSinkPower = status.getCurrentPowerRole() == POWER_ROLE_SINK; + mAudioAccessoryConnected = (status.getCurrentMode() == MODE_AUDIO_ACCESSORY); + mAudioAccessorySupported = port.isModeSupported(MODE_AUDIO_ACCESSORY); // Ideally we want to see if PR_SWAP and DR_SWAP is supported. // But, this should be suffice, since, all four combinations are only supported // when PR_SWAP and DR_SWAP are supported. mSupportsAllCombinations = status.isRoleCombinationSupported( - UsbPort.POWER_ROLE_SOURCE, UsbPort.DATA_ROLE_HOST) - && status.isRoleCombinationSupported(UsbPort.POWER_ROLE_SINK, - UsbPort.DATA_ROLE_HOST) - && status.isRoleCombinationSupported(UsbPort.POWER_ROLE_SOURCE, - UsbPort.DATA_ROLE_DEVICE) - && status.isRoleCombinationSupported(UsbPort.POWER_ROLE_SINK, - UsbPort.DATA_ROLE_HOST); + POWER_ROLE_SOURCE, DATA_ROLE_HOST) + && status.isRoleCombinationSupported(POWER_ROLE_SINK, DATA_ROLE_HOST) + && status.isRoleCombinationSupported(POWER_ROLE_SOURCE, + DATA_ROLE_DEVICE) + && status.isRoleCombinationSupported(POWER_ROLE_SINK, DATA_ROLE_HOST); args.recycle(); updateUsbNotification(false); diff --git a/services/usb/java/com/android/server/usb/UsbPortManager.java b/services/usb/java/com/android/server/usb/UsbPortManager.java index 96618f569928..6f210e37d6d1 100644 --- a/services/usb/java/com/android/server/usb/UsbPortManager.java +++ b/services/usb/java/com/android/server/usb/UsbPortManager.java @@ -16,12 +16,22 @@ package com.android.server.usb; +import static android.hardware.usb.UsbPortStatus.DATA_ROLE_DEVICE; +import static android.hardware.usb.UsbPortStatus.DATA_ROLE_HOST; +import static android.hardware.usb.UsbPortStatus.MODE_DFP; +import static android.hardware.usb.UsbPortStatus.MODE_DUAL; +import static android.hardware.usb.UsbPortStatus.MODE_UFP; +import static android.hardware.usb.UsbPortStatus.POWER_ROLE_SINK; +import static android.hardware.usb.UsbPortStatus.POWER_ROLE_SOURCE; + import static com.android.internal.usb.DumpUtils.writePort; import static com.android.internal.usb.DumpUtils.writePortStatus; +import android.Manifest; import android.annotation.NonNull; import android.content.Context; import android.content.Intent; +import android.hardware.usb.ParcelableUsbPort; import android.hardware.usb.UsbManager; import android.hardware.usb.UsbPort; import android.hardware.usb.UsbPortStatus; @@ -78,13 +88,13 @@ public class UsbPortManager { // All non-trivial role combinations. private static final int COMBO_SOURCE_HOST = - UsbPort.combineRolesAsBit(UsbPort.POWER_ROLE_SOURCE, UsbPort.DATA_ROLE_HOST); - private static final int COMBO_SOURCE_DEVICE = - UsbPort.combineRolesAsBit(UsbPort.POWER_ROLE_SOURCE, UsbPort.DATA_ROLE_DEVICE); + UsbPort.combineRolesAsBit(POWER_ROLE_SOURCE, DATA_ROLE_HOST); + private static final int COMBO_SOURCE_DEVICE = UsbPort.combineRolesAsBit( + POWER_ROLE_SOURCE, DATA_ROLE_DEVICE); private static final int COMBO_SINK_HOST = - UsbPort.combineRolesAsBit(UsbPort.POWER_ROLE_SINK, UsbPort.DATA_ROLE_HOST); - private static final int COMBO_SINK_DEVICE = - UsbPort.combineRolesAsBit(UsbPort.POWER_ROLE_SINK, UsbPort.DATA_ROLE_DEVICE); + UsbPort.combineRolesAsBit(POWER_ROLE_SINK, DATA_ROLE_HOST); + private static final int COMBO_SINK_DEVICE = UsbPort.combineRolesAsBit( + POWER_ROLE_SINK, DATA_ROLE_DEVICE); // The system context. private final Context mContext; @@ -217,12 +227,12 @@ public class UsbPortManager { final int newMode; if ((!canChangePowerRole && currentPowerRole != newPowerRole) || (!canChangeDataRole && currentDataRole != newDataRole)) { - if (canChangeMode && newPowerRole == UsbPort.POWER_ROLE_SOURCE - && newDataRole == UsbPort.DATA_ROLE_HOST) { - newMode = UsbPort.MODE_DFP; - } else if (canChangeMode && newPowerRole == UsbPort.POWER_ROLE_SINK - && newDataRole == UsbPort.DATA_ROLE_DEVICE) { - newMode = UsbPort.MODE_UFP; + if (canChangeMode && newPowerRole == POWER_ROLE_SOURCE + && newDataRole == DATA_ROLE_HOST) { + newMode = MODE_DFP; + } else if (canChangeMode && newPowerRole == POWER_ROLE_SINK + && newDataRole == DATA_ROLE_DEVICE) { + newMode = MODE_UFP; } else { logAndPrint(Log.ERROR, pw, "Found mismatch in supported USB role combinations " + "while attempting to change role: " + portInfo @@ -607,7 +617,7 @@ public class UsbPortManager { IndentingPrintWriter pw) { // Only allow mode switch capability for dual role ports. // Validate that the current mode matches the supported modes we expect. - if ((supportedModes & UsbPort.MODE_DUAL) != UsbPort.MODE_DUAL) { + if ((supportedModes & MODE_DUAL) != MODE_DUAL) { canChangeMode = false; if (currentMode != 0 && currentMode != supportedModes) { logAndPrint(Log.WARN, pw, "Ignoring inconsistent current mode from USB " @@ -633,16 +643,16 @@ public class UsbPortManager { // Can only change power role. // Assume data role must remain at its current value. supportedRoleCombinations |= UsbPort.combineRolesAsBit( - UsbPort.POWER_ROLE_SOURCE, currentDataRole); + POWER_ROLE_SOURCE, currentDataRole); supportedRoleCombinations |= UsbPort.combineRolesAsBit( - UsbPort.POWER_ROLE_SINK, currentDataRole); + POWER_ROLE_SINK, currentDataRole); } else if (canChangeDataRole) { // Can only change data role. // Assume power role must remain at its current value. supportedRoleCombinations |= UsbPort.combineRolesAsBit( - currentPowerRole, UsbPort.DATA_ROLE_HOST); + currentPowerRole, DATA_ROLE_HOST); supportedRoleCombinations |= UsbPort.combineRolesAsBit( - currentPowerRole, UsbPort.DATA_ROLE_DEVICE); + currentPowerRole, DATA_ROLE_DEVICE); } else if (canChangeMode) { // Can only change the mode. // Assume both standard UFP and DFP configurations will become available @@ -654,7 +664,8 @@ public class UsbPortManager { // Update the port data structures. PortInfo portInfo = mPorts.get(portId); if (portInfo == null) { - portInfo = new PortInfo(portId, supportedModes); + portInfo = new PortInfo(mContext.getSystemService(UsbManager.class), portId, + supportedModes); portInfo.setStatus(currentMode, canChangeMode, currentPowerRole, canChangePowerRole, currentDataRole, canChangeDataRole, @@ -701,12 +712,13 @@ public class UsbPortManager { intent.addFlags( Intent.FLAG_RECEIVER_FOREGROUND | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); - intent.putExtra(UsbManager.EXTRA_PORT, portInfo.mUsbPort); + intent.putExtra(UsbManager.EXTRA_PORT, ParcelableUsbPort.of(portInfo.mUsbPort)); intent.putExtra(UsbManager.EXTRA_PORT_STATUS, portInfo.mUsbPortStatus); // Guard against possible reentrance by posting the broadcast from the handler // instead of from within the critical section. - mHandler.post(() -> mContext.sendBroadcastAsUser(intent, UserHandle.ALL)); + mHandler.post(() -> mContext.sendBroadcastAsUser(intent, UserHandle.ALL, + Manifest.permission.MANAGE_USB)); // Log to statsd if (!mConnected.containsKey(portInfo.mUsbPort.getId()) @@ -772,8 +784,8 @@ public class UsbPortManager { // 0 when port is connected. Else reports the last connected duration public long mLastConnectDurationMillis; - public PortInfo(String portId, int supportedModes) { - mUsbPort = new UsbPort(portId, supportedModes); + PortInfo(@NonNull UsbManager usbManager, @NonNull String portId, int supportedModes) { + mUsbPort = new UsbPort(usbManager, portId, supportedModes); } public boolean setStatus(int currentMode, boolean canChangeMode, diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java index f9abedfbf586..911547720a8f 100644 --- a/services/usb/java/com/android/server/usb/UsbService.java +++ b/services/usb/java/com/android/server/usb/UsbService.java @@ -16,6 +16,14 @@ package com.android.server.usb; +import static android.hardware.usb.UsbPortStatus.DATA_ROLE_DEVICE; +import static android.hardware.usb.UsbPortStatus.DATA_ROLE_HOST; +import static android.hardware.usb.UsbPortStatus.MODE_DFP; +import static android.hardware.usb.UsbPortStatus.MODE_DUAL; +import static android.hardware.usb.UsbPortStatus.MODE_UFP; +import static android.hardware.usb.UsbPortStatus.POWER_ROLE_SINK; +import static android.hardware.usb.UsbPortStatus.POWER_ROLE_SOURCE; + import android.annotation.NonNull; import android.annotation.UserIdInt; import android.app.PendingIntent; @@ -27,6 +35,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.hardware.usb.IUsbManager; +import android.hardware.usb.ParcelableUsbPort; import android.hardware.usb.UsbAccessory; import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbManager; @@ -52,7 +61,9 @@ import com.android.server.SystemService; import java.io.File; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.util.ArrayList; import java.util.Collections; +import java.util.List; /** * UsbService manages all USB related state, including both host and device support. @@ -489,12 +500,25 @@ public class UsbService extends IUsbManager.Stub { } @Override - public UsbPort[] getPorts() { + public List<ParcelableUsbPort> getPorts() { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); final long ident = Binder.clearCallingIdentity(); try { - return mPortManager != null ? mPortManager.getPorts() : null; + if (mPortManager == null) { + return null; + } else { + final UsbPort[] ports = mPortManager.getPorts(); + + final int numPorts = ports.length; + ArrayList<ParcelableUsbPort> parcelablePorts = new ArrayList<>(); + for (int i = 0; i < numPorts; i++) { + parcelablePorts.add(ParcelableUsbPort.of(ports[i])); + } + + return parcelablePorts; + } + } finally { Binder.restoreCallingIdentity(ident); } @@ -588,10 +612,10 @@ public class UsbService extends IUsbManager.Stub { final int powerRole; switch (args[2]) { case "source": - powerRole = UsbPort.POWER_ROLE_SOURCE; + powerRole = POWER_ROLE_SOURCE; break; case "sink": - powerRole = UsbPort.POWER_ROLE_SINK; + powerRole = POWER_ROLE_SINK; break; case "no-power": powerRole = 0; @@ -603,10 +627,10 @@ public class UsbService extends IUsbManager.Stub { final int dataRole; switch (args[3]) { case "host": - dataRole = UsbPort.DATA_ROLE_HOST; + dataRole = DATA_ROLE_HOST; break; case "device": - dataRole = UsbPort.DATA_ROLE_DEVICE; + dataRole = DATA_ROLE_DEVICE; break; case "no-data": dataRole = 0; @@ -631,13 +655,13 @@ public class UsbService extends IUsbManager.Stub { final int supportedModes; switch (args[2]) { case "ufp": - supportedModes = UsbPort.MODE_UFP; + supportedModes = MODE_UFP; break; case "dfp": - supportedModes = UsbPort.MODE_DFP; + supportedModes = MODE_DFP; break; case "dual": - supportedModes = UsbPort.MODE_DUAL; + supportedModes = MODE_DUAL; break; case "none": supportedModes = 0; @@ -658,10 +682,10 @@ public class UsbService extends IUsbManager.Stub { final boolean canChangeMode = args[2].endsWith("?"); switch (canChangeMode ? removeLastChar(args[2]) : args[2]) { case "ufp": - mode = UsbPort.MODE_UFP; + mode = MODE_UFP; break; case "dfp": - mode = UsbPort.MODE_DFP; + mode = MODE_DFP; break; default: pw.println("Invalid mode: " + args[2]); @@ -671,10 +695,10 @@ public class UsbService extends IUsbManager.Stub { final boolean canChangePowerRole = args[3].endsWith("?"); switch (canChangePowerRole ? removeLastChar(args[3]) : args[3]) { case "source": - powerRole = UsbPort.POWER_ROLE_SOURCE; + powerRole = POWER_ROLE_SOURCE; break; case "sink": - powerRole = UsbPort.POWER_ROLE_SINK; + powerRole = POWER_ROLE_SINK; break; default: pw.println("Invalid power role: " + args[3]); @@ -684,10 +708,10 @@ public class UsbService extends IUsbManager.Stub { final boolean canChangeDataRole = args[4].endsWith("?"); switch (canChangeDataRole ? removeLastChar(args[4]) : args[4]) { case "host": - dataRole = UsbPort.DATA_ROLE_HOST; + dataRole = DATA_ROLE_HOST; break; case "device": - dataRole = UsbPort.DATA_ROLE_DEVICE; + dataRole = DATA_ROLE_DEVICE; break; default: pw.println("Invalid data role: " + args[4]); diff --git a/startop/view_compiler/dex_builder.cc b/startop/view_compiler/dex_builder.cc index 94879d0bbcc4..a78f7d53d135 100644 --- a/startop/view_compiler/dex_builder.cc +++ b/startop/view_compiler/dex_builder.cc @@ -79,6 +79,9 @@ std::ostream& operator<<(std::ostream& out, const Instruction::Op& opcode) { case Instruction::Op::kNew: out << "kNew"; return out; + case Instruction::Op::kCheckCast: + out << "kCheckCast"; + return out; } } @@ -329,6 +332,8 @@ void MethodBuilder::EncodeInstruction(const Instruction& instruction) { return EncodeBranch(art::Instruction::IF_NEZ, instruction); case Instruction::Op::kNew: return EncodeNew(instruction); + case Instruction::Op::kCheckCast: + return EncodeCast(instruction); } } @@ -422,6 +427,18 @@ void MethodBuilder::EncodeNew(const Instruction& instruction) { Encode21c(::art::Instruction::NEW_INSTANCE, RegisterValue(*instruction.dest()), type.value()); } +void MethodBuilder::EncodeCast(const Instruction& instruction) { + DCHECK_EQ(Instruction::Op::kCheckCast, instruction.opcode()); + DCHECK(instruction.dest().has_value()); + DCHECK(instruction.dest()->is_variable()); + DCHECK_EQ(1, instruction.args().size()); + + const Value& type = instruction.args()[0]; + DCHECK_LT(RegisterValue(*instruction.dest()), 256); + DCHECK(type.is_type()); + Encode21c(::art::Instruction::CHECK_CAST, RegisterValue(*instruction.dest()), type.value()); +} + size_t MethodBuilder::RegisterValue(const Value& value) const { if (value.is_register()) { return value.value(); diff --git a/startop/view_compiler/dex_builder.h b/startop/view_compiler/dex_builder.h index 45596acfdc44..06059c8c6e56 100644 --- a/startop/view_compiler/dex_builder.h +++ b/startop/view_compiler/dex_builder.h @@ -142,17 +142,18 @@ class Instruction { // The operation performed by this instruction. These are virtual instructions that do not // correspond exactly to DEX instructions. enum class Op { - kReturn, - kReturnObject, - kMove, - kInvokeVirtual, - kInvokeDirect, - kInvokeStatic, - kInvokeInterface, kBindLabel, kBranchEqz, kBranchNEqz, - kNew + kCheckCast, + kInvokeDirect, + kInvokeInterface, + kInvokeStatic, + kInvokeVirtual, + kMove, + kNew, + kReturn, + kReturnObject, }; //////////////////////// @@ -168,6 +169,13 @@ class Instruction { static inline Instruction OpWithArgs(Op opcode, std::optional<const Value> dest, T... args) { return Instruction{opcode, /*method_id=*/0, /*result_is_object=*/false, dest, args...}; } + + // A cast instruction. Basically, `(type)val` + static inline Instruction Cast(Value val, Value type) { + DCHECK(type.is_type()); + return OpWithArgs(Op::kCheckCast, val, type); + } + // For method calls. template <typename... T> static inline Instruction InvokeVirtual(size_t method_id, std::optional<const Value> dest, @@ -302,6 +310,7 @@ class MethodBuilder { void EncodeInvoke(const Instruction& instruction, ::art::Instruction::Code opcode); void EncodeBranch(art::Instruction::Code op, const Instruction& instruction); void EncodeNew(const Instruction& instruction); + void EncodeCast(const Instruction& instruction); // Low-level instruction format encoding. See // https://source.android.com/devices/tech/dalvik/instruction-formats for documentation of diff --git a/startop/view_compiler/dex_builder_test/src/android/startop/test/DexBuilderTest.java b/startop/view_compiler/dex_builder_test/src/android/startop/test/DexBuilderTest.java index 1508ad9ee56b..42d4161ee81e 100644 --- a/startop/view_compiler/dex_builder_test/src/android/startop/test/DexBuilderTest.java +++ b/startop/view_compiler/dex_builder_test/src/android/startop/test/DexBuilderTest.java @@ -20,6 +20,7 @@ import com.google.common.io.ByteStreams; import dalvik.system.InMemoryDexClassLoader; import dalvik.system.PathClassLoader; import java.io.InputStream; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.nio.ByteBuffer; import org.junit.Assert; @@ -151,4 +152,23 @@ public class DexBuilderTest { Method method = clazz.getMethod("invokeVirtualReturnObject", String.class, int.class); Assert.assertEquals("bc", method.invoke(null, "abc", 1)); } + + @Test + public void castObjectToString() throws Exception { + ClassLoader loader = loadDexFile("simple.dex"); + Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests"); + Method method = clazz.getMethod("castObjectToString", Object.class); + Assert.assertEquals("abc", method.invoke(null, "abc")); + boolean castFailed = false; + try { + method.invoke(null, 5); + } catch (InvocationTargetException e) { + if (e.getCause() instanceof ClassCastException) { + castFailed = true; + } else { + throw e; + } + } + Assert.assertTrue(castFailed); + } } diff --git a/startop/view_compiler/dex_testcase_generator.cc b/startop/view_compiler/dex_testcase_generator.cc index 2781aa55d1df..f62ec5dde85e 100644 --- a/startop/view_compiler/dex_testcase_generator.cc +++ b/startop/view_compiler/dex_testcase_generator.cc @@ -269,6 +269,19 @@ void GenerateSimpleTestCases(const string& outdir) { method.Encode(); }(invokeVirtualReturnObject); + // Make sure we can cast objects + // String castObjectToString(Object o) { return (String)o; } + MethodBuilder castObjectToString{cbuilder.CreateMethod( + "castObjectToString", + Prototype{string_type, TypeDescriptor::FromClassname("java.lang.Object")})}; + [&](MethodBuilder& method) { + const ir::Type* type_def = dex_file.GetOrAddType(string_type.descriptor()); + method.AddInstruction( + Instruction::Cast(Value::Parameter(0), Value::Type(type_def->orig_index))); + method.BuildReturn(Value::Parameter(0), /*is_object=*/true); + method.Encode(); + }(castObjectToString); + slicer::MemView image{dex_file.CreateImage()}; std::ofstream out_file(outdir + "/simple.dex"); out_file.write(image.ptr<const char>(), image.size()); diff --git a/telecomm/java/android/telecom/PhoneAccountSuggestion.aidl b/telecomm/java/android/telecom/PhoneAccountSuggestion.aidl new file mode 100644 index 000000000000..e2fa7e4032c1 --- /dev/null +++ b/telecomm/java/android/telecom/PhoneAccountSuggestion.aidl @@ -0,0 +1,22 @@ +/* + * 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; + +/** + * {@hide} + */ +parcelable PhoneAccountSuggestion;
\ No newline at end of file diff --git a/telecomm/java/android/telecom/PhoneAccountSuggestion.java b/telecomm/java/android/telecom/PhoneAccountSuggestion.java index 4e6a178c8170..b401bcf0f876 100644 --- a/telecomm/java/android/telecom/PhoneAccountSuggestion.java +++ b/telecomm/java/android/telecom/PhoneAccountSuggestion.java @@ -24,6 +24,7 @@ import android.os.Parcelable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Objects; public final class PhoneAccountSuggestion implements Parcelable { @@ -132,4 +133,19 @@ public final class PhoneAccountSuggestion implements Parcelable { dest.writeInt(mReason); dest.writeByte((byte) (mShouldAutoSelect ? 1 : 0)); } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + PhoneAccountSuggestion that = (PhoneAccountSuggestion) o; + return mReason == that.mReason + && mShouldAutoSelect == that.mShouldAutoSelect + && Objects.equals(mHandle, that.mHandle); + } + + @Override + public int hashCode() { + return Objects.hash(mHandle, mReason, mShouldAutoSelect); + } } diff --git a/telecomm/java/android/telecom/PhoneAccountSuggestionService.java b/telecomm/java/android/telecom/PhoneAccountSuggestionService.java new file mode 100644 index 000000000000..ba3822cb9951 --- /dev/null +++ b/telecomm/java/android/telecom/PhoneAccountSuggestionService.java @@ -0,0 +1,123 @@ +/* + * 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.NonNull; +import android.annotation.SdkConstant; +import android.annotation.SystemApi; +import android.annotation.TestApi; +import android.app.Service; +import android.content.Intent; +import android.os.IBinder; +import android.os.RemoteException; + +import com.android.internal.telecom.IPhoneAccountSuggestionCallback; +import com.android.internal.telecom.IPhoneAccountSuggestionService; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Base class for service that allows system apps to suggest phone accounts for outgoing calls. + * + * Phone account suggestions allow OEMs to intelligently select phone accounts based on knowledge + * about the user's past behavior, carrier billing patterns, or other factors unknown to the AOSP + * Telecom system. + * OEMs who wish to provide a phone account suggestion service on their device should implement this + * service in an app that resides in the /system/priv-app/ directory on their device. For security + * reasons, the service's entry {@code AndroidManifest.xml} file must declare the + * {@link android.Manifest.permission.BIND_PHONE_ACCOUNT_SUGGESTION_SERVICE} permission: + * <pre> + * {@code + * <service android:name="your.package.YourServiceName" + * android:permission="android.permission.BIND_PHONE_ACCOUNT_SUGGESTION_SERVICE"> + * <intent-filter> + * <action android:name="android.telecom.PhoneAccountSuggestionService"/> + * </intent-filter> + * </service> + * } + * </pre> + * Only one system app on each device may implement this service. If multiple system apps implement + * this service, none of them will be queried for suggestions. + * @hide + */ +@SystemApi +@TestApi +public class PhoneAccountSuggestionService extends Service { + /** + * The {@link Intent} that must be declared in the {@code intent-filter} element of the + * service's manifest entry. + */ + @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) + public static final String SERVICE_INTERFACE = "android.telecom.PhoneAccountSuggestionService"; + + private IPhoneAccountSuggestionService mInterface = new IPhoneAccountSuggestionService.Stub() { + @Override + public void onAccountSuggestionRequest(IPhoneAccountSuggestionCallback callback, + String number) { + mCallbackMap.put(number, callback); + PhoneAccountSuggestionService.this.onAccountSuggestionRequest(number); + } + }; + + private final Map<String, IPhoneAccountSuggestionCallback> mCallbackMap = + new HashMap<>(); + + @Override + public IBinder onBind(Intent intent) { + return mInterface.asBinder(); + } + + /** + * The system calls this method during the outgoing call flow if it needs account suggestions. + * + * The implementer of this service must override this method to implement its account suggestion + * logic. After preparing the suggestions, the implementation of the service must call + * {@link #suggestPhoneAccounts(String, List)} to deliver the suggestions back to the system. + * + * Note that the system will suspend the outgoing call process after it calls this method until + * this service calls {@link #suggestPhoneAccounts}. + * + * @param number The phone number to provide suggestions for. + */ + public void onAccountSuggestionRequest(@NonNull String number) {} + + /** + * The implementation of this service calls this method to deliver suggestions to the system. + * + * The implementation of this service must call this method after receiving a call to + * {@link #onAccountSuggestionRequest(String)}. If no suggestions are available, pass an empty + * list as the {@code suggestions} argument. + * + * @param number The phone number to provide suggestions for. + * @param suggestions The list of suggestions. + */ + public final void suggestPhoneAccounts(@NonNull String number, + @NonNull List<PhoneAccountSuggestion> suggestions) { + IPhoneAccountSuggestionCallback callback = mCallbackMap.remove(number); + if (callback == null) { + Log.w(this, "No suggestions requested for the number %s", Log.pii(number)); + return; + } + try { + callback.suggestPhoneAccounts(number, suggestions); + } catch (RemoteException e) { + Log.w(this, "Remote exception calling suggestPhoneAccounts"); + } + } +} diff --git a/telecomm/java/com/android/internal/telecom/IPhoneAccountSuggestionCallback.aidl b/telecomm/java/com/android/internal/telecom/IPhoneAccountSuggestionCallback.aidl new file mode 100644 index 000000000000..cb142417451c --- /dev/null +++ b/telecomm/java/com/android/internal/telecom/IPhoneAccountSuggestionCallback.aidl @@ -0,0 +1,26 @@ +/* + * 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.internal.telecom; + +import android.telecom.PhoneAccountSuggestion; +/** + * Internal remote callback interface for a phone acct suggestion service. + * @hide + */ +oneway interface IPhoneAccountSuggestionCallback{ + void suggestPhoneAccounts(in String number, in List<PhoneAccountSuggestion> suggestions); +} diff --git a/telecomm/java/com/android/internal/telecom/IPhoneAccountSuggestionService.aidl b/telecomm/java/com/android/internal/telecom/IPhoneAccountSuggestionService.aidl new file mode 100644 index 000000000000..0ffab93d9f1b --- /dev/null +++ b/telecomm/java/com/android/internal/telecom/IPhoneAccountSuggestionService.aidl @@ -0,0 +1,28 @@ +/* + * 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.internal.telecom; + +import com.android.internal.telecom.IPhoneAccountSuggestionCallback; + +/** + * Internal remote interface for a phone acct suggestion service. + * @hide + */ +oneway interface IPhoneAccountSuggestionService { + void onAccountSuggestionRequest(in IPhoneAccountSuggestionCallback callback, + in String number); +} diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl index 50204e70f63d..954a7098f6be 100644 --- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl +++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl @@ -292,6 +292,8 @@ interface ITelecomService { void setTestDefaultCallRedirectionApp(String packageName); + void setTestPhoneAcctSuggestionComponent(String flattenedComponentName); + void setTestDefaultCallScreeningApp(String packageName); void addOrRemoveTestCallCompanionApp(String packageName, boolean isAdded); diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index fd14916ecac9..f87472d9ec38 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -2400,6 +2400,34 @@ public class CarrierConfigManager { public static final String KEY_5G_ICON_CONFIGURATION_STRING = "5g_icon_configuration_string"; + /** + * Controls RSRP threshold at which AlternativeNetworkService will decide whether + * the opportunistic network is good enough for internet data. + */ + public static final String KEY_OPPORTUNISTIC_NETWORK_ENTRY_THRESHOLD_RSRP_INT = + "opportunistic_network_entry_threshold_rsrp_int"; + + /** + * Controls RSSNR threshold at which AlternativeNetworkService will decide whether + * the opportunistic network is good enough for internet data. + */ + public static final String KEY_OPPORTUNISTIC_NETWORK_ENTRY_THRESHOLD_RSSNR_INT = + "opportunistic_network_entry_threshold_rssnr_int"; + + /** + * Controls RSRP threshold below which AlternativeNetworkService will decide whether + * the opportunistic network available is not good enough for internet data. + */ + public static final String KEY_OPPORTUNISTIC_NETWORK_EXIT_THRESHOLD_RSRP_INT = + "opportunistic_network_exit_threshold_rsrp_int"; + + /** + * Controls RSSNR threshold below which AlternativeNetworkService will decide whether + * the opportunistic network available is not good enough for internet data. + */ + public static final String KEY_OPPORTUNISTIC_NETWORK_EXIT_THRESHOLD_RSSNR_INT = + "opportunistic_network_exit_threshold_rssnr_int"; + /** The default value for every variable. */ private final static PersistableBundle sDefaults; @@ -2761,6 +2789,14 @@ public class CarrierConfigManager { sDefaults.putInt(KEY_CALL_WAITING_SERVICE_CLASS_INT, 1 /* SERVICE_CLASS_VOICE */); sDefaults.putString(KEY_5G_ICON_CONFIGURATION_STRING, "connected_mmwave:None,connected:5G,not_restricted:None,restricted:None"); + /* Default value is minimum RSRP level needed for SIGNAL_STRENGTH_GOOD */ + sDefaults.putInt(KEY_OPPORTUNISTIC_NETWORK_ENTRY_THRESHOLD_RSRP_INT, -108); + /* Default value is minimum RSRP level needed for SIGNAL_STRENGTH_MODERATE */ + sDefaults.putInt(KEY_OPPORTUNISTIC_NETWORK_EXIT_THRESHOLD_RSRP_INT, -118); + /* Default value is minimum RSSNR level needed for SIGNAL_STRENGTH_GOOD */ + sDefaults.putInt(KEY_OPPORTUNISTIC_NETWORK_ENTRY_THRESHOLD_RSSNR_INT, 45); + /* Default value is minimum RSSNR level needed for SIGNAL_STRENGTH_MODERATE */ + sDefaults.putInt(KEY_OPPORTUNISTIC_NETWORK_EXIT_THRESHOLD_RSSNR_INT, 10); } /** diff --git a/telephony/java/android/telephony/CellIdentityGsm.java b/telephony/java/android/telephony/CellIdentityGsm.java index 04c28e5211c8..c8a899b339c6 100644 --- a/telephony/java/android/telephony/CellIdentityGsm.java +++ b/telephony/java/android/telephony/CellIdentityGsm.java @@ -16,6 +16,7 @@ package android.telephony; +import android.annotation.Nullable; import android.annotation.UnsupportedAppUsage; import android.os.Parcel; import android.telephony.gsm.GsmCellLocation; @@ -169,6 +170,7 @@ public final class CellIdentityGsm extends CellIdentity { /** * @return a 5 or 6 character string (MCC+MNC), null if any field is unknown. */ + @Nullable public String getMobileNetworkOperator() { return (mMccStr == null || mMncStr == null) ? null : mMccStr + mMncStr; } diff --git a/telephony/java/android/telephony/CellIdentityLte.java b/telephony/java/android/telephony/CellIdentityLte.java index 04b6a6ca7fea..8e1877d8e35f 100644 --- a/telephony/java/android/telephony/CellIdentityLte.java +++ b/telephony/java/android/telephony/CellIdentityLte.java @@ -16,6 +16,7 @@ package android.telephony; +import android.annotation.Nullable; import android.annotation.UnsupportedAppUsage; import android.os.Parcel; import android.telephony.gsm.GsmCellLocation; @@ -197,6 +198,7 @@ public final class CellIdentityLte extends CellIdentity { /** * @return a 5 or 6 character string (MCC+MNC), null if any field is unknown. */ + @Nullable public String getMobileNetworkOperator() { return (mMccStr == null || mMncStr == null) ? null : mMccStr + mMncStr; } diff --git a/telephony/java/android/telephony/CellIdentityTdscdma.java b/telephony/java/android/telephony/CellIdentityTdscdma.java index 8b1c1b9f024c..f77c468d2f5e 100644 --- a/telephony/java/android/telephony/CellIdentityTdscdma.java +++ b/telephony/java/android/telephony/CellIdentityTdscdma.java @@ -16,6 +16,7 @@ package android.telephony; +import android.annotation.Nullable; import android.os.Parcel; import android.telephony.gsm.GsmCellLocation; @@ -116,6 +117,7 @@ public final class CellIdentityTdscdma extends CellIdentity { /** * @return a 5 or 6 character string (MCC+MNC), null if any field is unknown */ + @Nullable public String getMobileNetworkOperator() { return (mMccStr == null || mMncStr == null) ? null : mMccStr + mMncStr; } diff --git a/telephony/java/android/telephony/CellIdentityWcdma.java b/telephony/java/android/telephony/CellIdentityWcdma.java index 3416ffe0b8f4..31f9e6d5bb90 100644 --- a/telephony/java/android/telephony/CellIdentityWcdma.java +++ b/telephony/java/android/telephony/CellIdentityWcdma.java @@ -16,6 +16,7 @@ package android.telephony; +import android.annotation.Nullable; import android.annotation.UnsupportedAppUsage; import android.os.Parcel; import android.telephony.gsm.GsmCellLocation; @@ -173,6 +174,7 @@ public final class CellIdentityWcdma extends CellIdentity { /** * @return a 5 or 6 character string (MCC+MNC), null if any field is unknown */ + @Nullable public String getMobileNetworkOperator() { return (mMccStr == null || mMncStr == null) ? null : mMccStr + mMncStr; } diff --git a/telephony/java/android/telephony/DataFailCause.java b/telephony/java/android/telephony/DataFailCause.java index c6f7d0e458db..c53b37d8ae6a 100644 --- a/telephony/java/android/telephony/DataFailCause.java +++ b/telephony/java/android/telephony/DataFailCause.java @@ -15,151 +15,383 @@ */ package android.telephony; +import android.annotation.IntDef; +import android.annotation.NonNull; import android.content.Context; import android.os.PersistableBundle; +import com.android.internal.util.ArrayUtils; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; +import java.util.Map; +import java.util.Set; /** - * Returned as the reason for a connection failure as defined - * by RIL_DataCallFailCause in ril.h and some local errors. + * Returned as the reason for a data connection failure as defined by modem and some local errors. * @hide */ -public enum DataFailCause { - NONE(0), +public final class DataFailCause { + /** There is no failure */ + public static final int NONE = 0; // This series of errors as specified by the standards // specified in ril.h - OPERATOR_BARRED(0x08), /* no retry */ - NAS_SIGNALLING(0x0E), - LLC_SNDCP(0x19), - INSUFFICIENT_RESOURCES(0x1A), - MISSING_UNKNOWN_APN(0x1B), /* no retry */ - UNKNOWN_PDP_ADDRESS_TYPE(0x1C), /* no retry */ - USER_AUTHENTICATION(0x1D), /* no retry */ - ACTIVATION_REJECT_GGSN(0x1E), /* no retry */ - ACTIVATION_REJECT_UNSPECIFIED(0x1F), - SERVICE_OPTION_NOT_SUPPORTED(0x20), /* no retry */ - SERVICE_OPTION_NOT_SUBSCRIBED(0x21), /* no retry */ - SERVICE_OPTION_OUT_OF_ORDER(0x22), - NSAPI_IN_USE(0x23), /* no retry */ - REGULAR_DEACTIVATION(0x24), /* possibly restart radio, based on config */ - QOS_NOT_ACCEPTED(0x25), - NETWORK_FAILURE(0x26), - UMTS_REACTIVATION_REQ(0x27), - FEATURE_NOT_SUPP(0x28), - TFT_SEMANTIC_ERROR(0x29), - TFT_SYTAX_ERROR(0x2A), - UNKNOWN_PDP_CONTEXT(0x2B), - FILTER_SEMANTIC_ERROR(0x2C), - FILTER_SYTAX_ERROR(0x2D), - PDP_WITHOUT_ACTIVE_TFT(0x2E), - ONLY_IPV4_ALLOWED(0x32), /* no retry */ - ONLY_IPV6_ALLOWED(0x33), /* no retry */ - ONLY_SINGLE_BEARER_ALLOWED(0x34), - ESM_INFO_NOT_RECEIVED(0x35), - PDN_CONN_DOES_NOT_EXIST(0x36), - MULTI_CONN_TO_SAME_PDN_NOT_ALLOWED(0x37), - MAX_ACTIVE_PDP_CONTEXT_REACHED(0x41), - UNSUPPORTED_APN_IN_CURRENT_PLMN(0x42), - INVALID_TRANSACTION_ID(0x51), - MESSAGE_INCORRECT_SEMANTIC(0x5F), - INVALID_MANDATORY_INFO(0x60), - MESSAGE_TYPE_UNSUPPORTED(0x61), - MSG_TYPE_NONCOMPATIBLE_STATE(0x62), - UNKNOWN_INFO_ELEMENT(0x63), - CONDITIONAL_IE_ERROR(0x64), - MSG_AND_PROTOCOL_STATE_UNCOMPATIBLE(0x65), - PROTOCOL_ERRORS(0x6F), /* no retry */ - APN_TYPE_CONFLICT(0x70), - INVALID_PCSCF_ADDR(0x71), - INTERNAL_CALL_PREEMPT_BY_HIGH_PRIO_APN(0x72), - EMM_ACCESS_BARRED(0x73), - EMERGENCY_IFACE_ONLY(0x74), - IFACE_MISMATCH(0x75), - COMPANION_IFACE_IN_USE(0x76), - IP_ADDRESS_MISMATCH(0x77), - IFACE_AND_POL_FAMILY_MISMATCH(0x78), - EMM_ACCESS_BARRED_INFINITE_RETRY(0x79), - AUTH_FAILURE_ON_EMERGENCY_CALL(0x7A), + /** Operator determined barring. */ + public static final int OPERATOR_BARRED = 0x08; + /** NAS signalling. */ + public static final int NAS_SIGNALLING = 0x0E; + /** Logical Link Control (LLC) Sub Network Dependent Convergence Protocol (SNDCP). */ + public static final int LLC_SNDCP = 0x19; + /** Insufficient resources. */ + public static final int INSUFFICIENT_RESOURCES = 0x1A; + /** Missing or unknown APN. */ + public static final int MISSING_UNKNOWN_APN = 0x1B; /* no retry */ + /** Unknown Packet Data Protocol (PDP) address type. */ + public static final int UNKNOWN_PDP_ADDRESS_TYPE = 0x1C; /* no retry */ + /** User authentication. */ + public static final int USER_AUTHENTICATION = 0x1D; /* no retry */ + /** Activation rejected by Gateway GPRS Support Node (GGSN), Serving Gateway or PDN Gateway. */ + public static final int ACTIVATION_REJECT_GGSN = 0x1E; /* no retry */ + /** Activation rejected, unspecified. */ + public static final int ACTIVATION_REJECT_UNSPECIFIED = 0x1F; + /** Service option not supported. */ + public static final int SERVICE_OPTION_NOT_SUPPORTED = 0x20; /* no retry */ + /** Requested service option not subscribed. */ + public static final int SERVICE_OPTION_NOT_SUBSCRIBED = 0x21; /* no retry */ + /** Service option temporarily out of order. */ + public static final int SERVICE_OPTION_OUT_OF_ORDER = 0x22; + /** The Network Service Access Point Identifier (NSAPI) is in use. */ + public static final int NSAPI_IN_USE = 0x23; /* no retry */ + /* possibly restart radio, based on config */ + /** Regular deactivation. */ + public static final int REGULAR_DEACTIVATION = 0x24; + /** Quality of service (QoS) is not accepted. */ + public static final int QOS_NOT_ACCEPTED = 0x25; + /** Network Failure. */ + public static final int NETWORK_FAILURE = 0x26; + /** Universal Mobile Telecommunications System (UMTS) reactivation request. */ + public static final int UMTS_REACTIVATION_REQ = 0x27; + /** Feature not supported. */ + public static final int FEATURE_NOT_SUPP = 0x28; + /** Semantic error in the Traffic flow templates (TFT) operation. */ + public static final int TFT_SEMANTIC_ERROR = 0x29; + /** Syntactical error in the Traffic flow templates (TFT) operation. */ + public static final int TFT_SYTAX_ERROR = 0x2A; + /** Unknown Packet Data Protocol (PDP) context. */ + public static final int UNKNOWN_PDP_CONTEXT = 0x2B; + /** Semantic errors in packet filter. */ + public static final int FILTER_SEMANTIC_ERROR = 0x2C; + /** Syntactical errors in packet filter(s). */ + public static final int FILTER_SYTAX_ERROR = 0x2D; + /** Packet Data Protocol (PDP) without active traffic flow template (TFT). */ + public static final int PDP_WITHOUT_ACTIVE_TFT = 0x2E; + /** Packet Data Protocol (PDP) type IPv4 only allowed. */ + public static final int ONLY_IPV4_ALLOWED = 0x32; /* no retry */ + /** Packet Data Protocol (PDP) type IPv6 only allowed. */ + public static final int ONLY_IPV6_ALLOWED = 0x33; /* no retry */ + /** Single address bearers only allowed. */ + public static final int ONLY_SINGLE_BEARER_ALLOWED = 0x34; + /** EPS Session Management (ESM) information is not received. */ + public static final int ESM_INFO_NOT_RECEIVED = 0x35; + /** PDN connection does not exist. */ + public static final int PDN_CONN_DOES_NOT_EXIST = 0x36; + /** Multiple connections to a same PDN is not allowed. */ + public static final int MULTI_CONN_TO_SAME_PDN_NOT_ALLOWED = 0x37; + /** Packet Data Protocol (PDP) */ + public static final int MAX_ACTIVE_PDP_CONTEXT_REACHED = 0x41; + /** Unsupported APN in current public land mobile network (PLMN). */ + public static final int UNSUPPORTED_APN_IN_CURRENT_PLMN = 0x42; + /** Invalid transaction id. */ + public static final int INVALID_TRANSACTION_ID = 0x51; + /** Incorrect message semantic. */ + public static final int MESSAGE_INCORRECT_SEMANTIC = 0x5F; + /** Invalid mandatory information. */ + public static final int INVALID_MANDATORY_INFO = 0x60; + /** Unsupported message type. */ + public static final int MESSAGE_TYPE_UNSUPPORTED = 0x61; + /** Message type uncompatible. */ + public static final int MSG_TYPE_NONCOMPATIBLE_STATE = 0x62; + /** Unknown info element. */ + public static final int UNKNOWN_INFO_ELEMENT = 0x63; + /** Conditional Information Element (IE) error. */ + public static final int CONDITIONAL_IE_ERROR = 0x64; + /** Message and protocol state uncompatible. */ + public static final int MSG_AND_PROTOCOL_STATE_UNCOMPATIBLE = 0x65; + /** Protocol errors. */ + public static final int PROTOCOL_ERRORS = 0x6F; /* no retry */ + /** APN type conflict. */ + public static final int APN_TYPE_CONFLICT = 0x70; + /** Invalid Proxy-Call Session Control Function (P-CSCF) address. */ + public static final int INVALID_PCSCF_ADDR = 0x71; + /** Internal data call preempt by high priority APN. */ + public static final int INTERNAL_CALL_PREEMPT_BY_HIGH_PRIO_APN = 0x72; + /** EPS (Evolved Packet System) Mobility Management (EMM) access barred. */ + public static final int EMM_ACCESS_BARRED = 0x73; + /** Emergency interface only. */ + public static final int EMERGENCY_IFACE_ONLY = 0x74; + /** Interface mismatch. */ + public static final int IFACE_MISMATCH = 0x75; + /** Companion interface in use. */ + public static final int COMPANION_IFACE_IN_USE = 0x76; + /** IP address mismatch. */ + public static final int IP_ADDRESS_MISMATCH = 0x77; + public static final int IFACE_AND_POL_FAMILY_MISMATCH = 0x78; + /** EPS (Evolved Packet System) Mobility Management (EMM) access barred infinity retry. **/ + public static final int EMM_ACCESS_BARRED_INFINITE_RETRY = 0x79; + /** Authentication failure on emergency call. */ + public static final int AUTH_FAILURE_ON_EMERGENCY_CALL = 0x7A; // OEM sepecific error codes. To be used by OEMs when they don't // want to reveal error code which would be replaced by ERROR_UNSPECIFIED - OEM_DCFAILCAUSE_1(0x1001), - OEM_DCFAILCAUSE_2(0x1002), - OEM_DCFAILCAUSE_3(0x1003), - OEM_DCFAILCAUSE_4(0x1004), - OEM_DCFAILCAUSE_5(0x1005), - OEM_DCFAILCAUSE_6(0x1006), - OEM_DCFAILCAUSE_7(0x1007), - OEM_DCFAILCAUSE_8(0x1008), - OEM_DCFAILCAUSE_9(0x1009), - OEM_DCFAILCAUSE_10(0x100A), - OEM_DCFAILCAUSE_11(0x100B), - OEM_DCFAILCAUSE_12(0x100C), - OEM_DCFAILCAUSE_13(0x100D), - OEM_DCFAILCAUSE_14(0x100E), - OEM_DCFAILCAUSE_15(0x100F), + public static final int OEM_DCFAILCAUSE_1 = 0x1001; + public static final int OEM_DCFAILCAUSE_2 = 0x1002; + public static final int OEM_DCFAILCAUSE_3 = 0x1003; + public static final int OEM_DCFAILCAUSE_4 = 0x1004; + public static final int OEM_DCFAILCAUSE_5 = 0x1005; + public static final int OEM_DCFAILCAUSE_6 = 0x1006; + public static final int OEM_DCFAILCAUSE_7 = 0x1007; + public static final int OEM_DCFAILCAUSE_8 = 0x1008; + public static final int OEM_DCFAILCAUSE_9 = 0x1009; + public static final int OEM_DCFAILCAUSE_10 = 0x100A; + public static final int OEM_DCFAILCAUSE_11 = 0x100B; + public static final int OEM_DCFAILCAUSE_12 = 0x100C; + public static final int OEM_DCFAILCAUSE_13 = 0x100D; + public static final int OEM_DCFAILCAUSE_14 = 0x100E; + public static final int OEM_DCFAILCAUSE_15 = 0x100F; // Local errors generated by Vendor RIL // specified in ril.h - REGISTRATION_FAIL(-1), - GPRS_REGISTRATION_FAIL(-2), - SIGNAL_LOST(-3), /* no retry */ - PREF_RADIO_TECH_CHANGED(-4), - RADIO_POWER_OFF(-5), /* no retry */ - TETHERED_CALL_ACTIVE(-6), /* no retry */ - ERROR_UNSPECIFIED(0xFFFF), + public static final int REGISTRATION_FAIL = -1; + public static final int GPRS_REGISTRATION_FAIL = -2; + public static final int SIGNAL_LOST = -3; /* no retry */ + public static final int PREF_RADIO_TECH_CHANGED = -4; + public static final int RADIO_POWER_OFF = -5; /* no retry */ + public static final int TETHERED_CALL_ACTIVE = -6; /* no retry */ + public static final int ERROR_UNSPECIFIED = 0xFFFF; // Errors generated by the Framework // specified here - UNKNOWN(0x10000), - RADIO_NOT_AVAILABLE(0x10001), /* no retry */ - UNACCEPTABLE_NETWORK_PARAMETER(0x10002), /* no retry */ - CONNECTION_TO_DATACONNECTIONAC_BROKEN(0x10003), - LOST_CONNECTION(0x10004), - RESET_BY_FRAMEWORK(0x10005); + public static final int UNKNOWN = 0x10000; + public static final int RADIO_NOT_AVAILABLE = 0x10001; /* no retry */ + public static final int UNACCEPTABLE_NETWORK_PARAMETER = 0x10002; /* no retry */ + public static final int CONNECTION_TO_DATACONNECTIONAC_BROKEN = 0x10003; + public static final int LOST_CONNECTION = 0x10004; + /** Data was reset by framework. */ + public static final int RESET_BY_FRAMEWORK = 0x10005; + + /** @hide */ + @IntDef(value = { + NONE, + OPERATOR_BARRED, + NAS_SIGNALLING, + LLC_SNDCP, + INSUFFICIENT_RESOURCES, + MISSING_UNKNOWN_APN, + UNKNOWN_PDP_ADDRESS_TYPE, + USER_AUTHENTICATION, + ACTIVATION_REJECT_GGSN, + ACTIVATION_REJECT_UNSPECIFIED, + SERVICE_OPTION_NOT_SUPPORTED, + SERVICE_OPTION_NOT_SUBSCRIBED, + SERVICE_OPTION_OUT_OF_ORDER, + NSAPI_IN_USE, + REGULAR_DEACTIVATION, + QOS_NOT_ACCEPTED, + NETWORK_FAILURE, + UMTS_REACTIVATION_REQ, + FEATURE_NOT_SUPP, + TFT_SEMANTIC_ERROR, + TFT_SYTAX_ERROR, + UNKNOWN_PDP_CONTEXT, + FILTER_SEMANTIC_ERROR, + FILTER_SYTAX_ERROR, + PDP_WITHOUT_ACTIVE_TFT, + ONLY_IPV4_ALLOWED, + ONLY_IPV6_ALLOWED, + ONLY_SINGLE_BEARER_ALLOWED, + ESM_INFO_NOT_RECEIVED, + PDN_CONN_DOES_NOT_EXIST, + MULTI_CONN_TO_SAME_PDN_NOT_ALLOWED, + MAX_ACTIVE_PDP_CONTEXT_REACHED, + UNSUPPORTED_APN_IN_CURRENT_PLMN, + INVALID_TRANSACTION_ID, + MESSAGE_INCORRECT_SEMANTIC, + INVALID_MANDATORY_INFO, + MESSAGE_TYPE_UNSUPPORTED, + MSG_TYPE_NONCOMPATIBLE_STATE, + UNKNOWN_INFO_ELEMENT, + CONDITIONAL_IE_ERROR, + MSG_AND_PROTOCOL_STATE_UNCOMPATIBLE, + PROTOCOL_ERRORS, /* no retry */ + APN_TYPE_CONFLICT, + INVALID_PCSCF_ADDR, + INTERNAL_CALL_PREEMPT_BY_HIGH_PRIO_APN, + EMM_ACCESS_BARRED, + EMERGENCY_IFACE_ONLY, + IFACE_MISMATCH, + COMPANION_IFACE_IN_USE, + IP_ADDRESS_MISMATCH, + IFACE_AND_POL_FAMILY_MISMATCH, + EMM_ACCESS_BARRED_INFINITE_RETRY, + AUTH_FAILURE_ON_EMERGENCY_CALL, + OEM_DCFAILCAUSE_1, + OEM_DCFAILCAUSE_2, + OEM_DCFAILCAUSE_3, + OEM_DCFAILCAUSE_4, + OEM_DCFAILCAUSE_5, + OEM_DCFAILCAUSE_6, + OEM_DCFAILCAUSE_7, + OEM_DCFAILCAUSE_8, + OEM_DCFAILCAUSE_9, + OEM_DCFAILCAUSE_10, + OEM_DCFAILCAUSE_11, + OEM_DCFAILCAUSE_12, + OEM_DCFAILCAUSE_13, + OEM_DCFAILCAUSE_14, + OEM_DCFAILCAUSE_15, + REGISTRATION_FAIL, + GPRS_REGISTRATION_FAIL, + SIGNAL_LOST, + PREF_RADIO_TECH_CHANGED, + RADIO_POWER_OFF, + TETHERED_CALL_ACTIVE, + ERROR_UNSPECIFIED, + UNKNOWN, + RADIO_NOT_AVAILABLE, + UNACCEPTABLE_NETWORK_PARAMETER, + CONNECTION_TO_DATACONNECTIONAC_BROKEN, + LOST_CONNECTION, + RESET_BY_FRAMEWORK + }) + @Retention(RetentionPolicy.SOURCE) + public @interface FailCause{} - private final int mErrorCode; - private static final HashMap<Integer, DataFailCause> sErrorCodeToFailCauseMap; + private static final Map<Integer, String> sFailCauseMap; static { - sErrorCodeToFailCauseMap = new HashMap<Integer, DataFailCause>(); - for (DataFailCause fc : values()) { - sErrorCodeToFailCauseMap.put(fc.getErrorCode(), fc); - } + sFailCauseMap = new HashMap<>(); + sFailCauseMap.put(NONE, "NONE"); + sFailCauseMap.put(OPERATOR_BARRED, "OPERATOR_BARRED"); + sFailCauseMap.put(NAS_SIGNALLING, "NAS_SIGNALLING"); + sFailCauseMap.put(LLC_SNDCP, "LLC_SNDCP"); + sFailCauseMap.put(INSUFFICIENT_RESOURCES, "INSUFFICIENT_RESOURCES"); + sFailCauseMap.put(MISSING_UNKNOWN_APN, "MISSING_UNKNOWN_APN"); + sFailCauseMap.put(UNKNOWN_PDP_ADDRESS_TYPE, "UNKNOWN_PDP_ADDRESS_TYPE"); + sFailCauseMap.put(USER_AUTHENTICATION, "USER_AUTHENTICATION"); + sFailCauseMap.put(ACTIVATION_REJECT_GGSN, "ACTIVATION_REJECT_GGSN"); + sFailCauseMap.put(ACTIVATION_REJECT_UNSPECIFIED, + "ACTIVATION_REJECT_UNSPECIFIED"); + sFailCauseMap.put(SERVICE_OPTION_NOT_SUPPORTED, + "SERVICE_OPTION_NOT_SUPPORTED"); + sFailCauseMap.put(SERVICE_OPTION_NOT_SUBSCRIBED, + "SERVICE_OPTION_NOT_SUBSCRIBED"); + sFailCauseMap.put(SERVICE_OPTION_OUT_OF_ORDER, "SERVICE_OPTION_OUT_OF_ORDER"); + sFailCauseMap.put(NSAPI_IN_USE, "NSAPI_IN_USE"); + sFailCauseMap.put(REGULAR_DEACTIVATION, "REGULAR_DEACTIVATION"); + sFailCauseMap.put(QOS_NOT_ACCEPTED, "QOS_NOT_ACCEPTED"); + sFailCauseMap.put(NETWORK_FAILURE, "NETWORK_FAILURE"); + sFailCauseMap.put(UMTS_REACTIVATION_REQ, "UMTS_REACTIVATION_REQ"); + sFailCauseMap.put(FEATURE_NOT_SUPP, "FEATURE_NOT_SUPP"); + sFailCauseMap.put(TFT_SEMANTIC_ERROR, "TFT_SEMANTIC_ERROR"); + sFailCauseMap.put(TFT_SYTAX_ERROR, "TFT_SYTAX_ERROR"); + sFailCauseMap.put(UNKNOWN_PDP_CONTEXT, "UNKNOWN_PDP_CONTEXT"); + sFailCauseMap.put(FILTER_SEMANTIC_ERROR, "FILTER_SEMANTIC_ERROR"); + sFailCauseMap.put(FILTER_SYTAX_ERROR, "FILTER_SYTAX_ERROR"); + sFailCauseMap.put(PDP_WITHOUT_ACTIVE_TFT, "PDP_WITHOUT_ACTIVE_TFT"); + sFailCauseMap.put(ONLY_IPV4_ALLOWED, "ONLY_IPV4_ALLOWED"); + sFailCauseMap.put(ONLY_IPV6_ALLOWED, "ONLY_IPV6_ALLOWED"); + sFailCauseMap.put(ONLY_SINGLE_BEARER_ALLOWED, "ONLY_SINGLE_BEARER_ALLOWED"); + sFailCauseMap.put(ESM_INFO_NOT_RECEIVED, "ESM_INFO_NOT_RECEIVED"); + sFailCauseMap.put(PDN_CONN_DOES_NOT_EXIST, "PDN_CONN_DOES_NOT_EXIST"); + sFailCauseMap.put(MULTI_CONN_TO_SAME_PDN_NOT_ALLOWED, + "MULTI_CONN_TO_SAME_PDN_NOT_ALLOWED"); + sFailCauseMap.put(MAX_ACTIVE_PDP_CONTEXT_REACHED, + "MAX_ACTIVE_PDP_CONTEXT_REACHED"); + sFailCauseMap.put(UNSUPPORTED_APN_IN_CURRENT_PLMN, + "UNSUPPORTED_APN_IN_CURRENT_PLMN"); + sFailCauseMap.put(INVALID_TRANSACTION_ID, "INVALID_TRANSACTION_ID"); + sFailCauseMap.put(MESSAGE_INCORRECT_SEMANTIC, "MESSAGE_INCORRECT_SEMANTIC"); + sFailCauseMap.put(INVALID_MANDATORY_INFO, "INVALID_MANDATORY_INFO"); + sFailCauseMap.put(MESSAGE_TYPE_UNSUPPORTED, "MESSAGE_TYPE_UNSUPPORTED"); + sFailCauseMap.put(MSG_TYPE_NONCOMPATIBLE_STATE, "MSG_TYPE_NONCOMPATIBLE_STATE"); + sFailCauseMap.put(UNKNOWN_INFO_ELEMENT, "UNKNOWN_INFO_ELEMENT"); + sFailCauseMap.put(CONDITIONAL_IE_ERROR, "CONDITIONAL_IE_ERROR"); + sFailCauseMap.put(MSG_AND_PROTOCOL_STATE_UNCOMPATIBLE, + "MSG_AND_PROTOCOL_STATE_UNCOMPATIBLE"); + sFailCauseMap.put(PROTOCOL_ERRORS, "PROTOCOL_ERRORS"); + sFailCauseMap.put(APN_TYPE_CONFLICT, "APN_TYPE_CONFLICT"); + sFailCauseMap.put(INVALID_PCSCF_ADDR, "INVALID_PCSCF_ADDR"); + sFailCauseMap.put(INTERNAL_CALL_PREEMPT_BY_HIGH_PRIO_APN, + "INTERNAL_CALL_PREEMPT_BY_HIGH_PRIO_APN"); + sFailCauseMap.put(EMM_ACCESS_BARRED, "EMM_ACCESS_BARRED"); + sFailCauseMap.put(EMERGENCY_IFACE_ONLY, "EMERGENCY_IFACE_ONLY"); + sFailCauseMap.put(IFACE_MISMATCH, "IFACE_MISMATCH"); + sFailCauseMap.put(COMPANION_IFACE_IN_USE, "COMPANION_IFACE_IN_USE"); + sFailCauseMap.put(IP_ADDRESS_MISMATCH, "IP_ADDRESS_MISMATCH"); + sFailCauseMap.put(IFACE_AND_POL_FAMILY_MISMATCH, + "IFACE_AND_POL_FAMILY_MISMATCH"); + sFailCauseMap.put(EMM_ACCESS_BARRED_INFINITE_RETRY, + "EMM_ACCESS_BARRED_INFINITE_RETRY"); + sFailCauseMap.put(AUTH_FAILURE_ON_EMERGENCY_CALL, + "AUTH_FAILURE_ON_EMERGENCY_CALL"); + sFailCauseMap.put(OEM_DCFAILCAUSE_1, "OEM_DCFAILCAUSE_1"); + sFailCauseMap.put(OEM_DCFAILCAUSE_2, "OEM_DCFAILCAUSE_2"); + sFailCauseMap.put(OEM_DCFAILCAUSE_3, "OEM_DCFAILCAUSE_3"); + sFailCauseMap.put(OEM_DCFAILCAUSE_4, "OEM_DCFAILCAUSE_4"); + sFailCauseMap.put(OEM_DCFAILCAUSE_5, "OEM_DCFAILCAUSE_5"); + sFailCauseMap.put(OEM_DCFAILCAUSE_6, "OEM_DCFAILCAUSE_6"); + sFailCauseMap.put(OEM_DCFAILCAUSE_7, "OEM_DCFAILCAUSE_7"); + sFailCauseMap.put(OEM_DCFAILCAUSE_8, "OEM_DCFAILCAUSE_8"); + sFailCauseMap.put(OEM_DCFAILCAUSE_9, "OEM_DCFAILCAUSE_9"); + sFailCauseMap.put(OEM_DCFAILCAUSE_10, "OEM_DCFAILCAUSE_10"); + sFailCauseMap.put(OEM_DCFAILCAUSE_11, "OEM_DCFAILCAUSE_11"); + sFailCauseMap.put(OEM_DCFAILCAUSE_12, "OEM_DCFAILCAUSE_12"); + sFailCauseMap.put(OEM_DCFAILCAUSE_13, "OEM_DCFAILCAUSE_13"); + sFailCauseMap.put(OEM_DCFAILCAUSE_14, "OEM_DCFAILCAUSE_14"); + sFailCauseMap.put(OEM_DCFAILCAUSE_15, "OEM_DCFAILCAUSE_15"); + sFailCauseMap.put(REGISTRATION_FAIL, "REGISTRATION_FAIL"); + sFailCauseMap.put(GPRS_REGISTRATION_FAIL, "GPRS_REGISTRATION_FAIL"); + sFailCauseMap.put(SIGNAL_LOST, "SIGNAL_LOST"); + sFailCauseMap.put(PREF_RADIO_TECH_CHANGED, "PREF_RADIO_TECH_CHANGED"); + sFailCauseMap.put(RADIO_POWER_OFF, "RADIO_POWER_OFF"); + sFailCauseMap.put(TETHERED_CALL_ACTIVE, "TETHERED_CALL_ACTIVE"); + sFailCauseMap.put(ERROR_UNSPECIFIED, "ERROR_UNSPECIFIED"); + sFailCauseMap.put(UNKNOWN, "UNKNOWN"); + sFailCauseMap.put(RADIO_NOT_AVAILABLE, "RADIO_NOT_AVAILABLE"); + sFailCauseMap.put(UNACCEPTABLE_NETWORK_PARAMETER, + "UNACCEPTABLE_NETWORK_PARAMETER"); + sFailCauseMap.put(CONNECTION_TO_DATACONNECTIONAC_BROKEN, + "CONNECTION_TO_DATACONNECTIONAC_BROKEN"); + sFailCauseMap.put(LOST_CONNECTION, "LOST_CONNECTION"); + sFailCauseMap.put(RESET_BY_FRAMEWORK, "RESET_BY_FRAMEWORK"); } /** * Map of subId -> set of data call setup permanent failure for the carrier. */ - private static final HashMap<Integer, HashSet<DataFailCause>> sPermanentFailureCache = + private static final HashMap<Integer, Set<Integer>> sPermanentFailureCache = new HashMap<>(); - DataFailCause(int errorCode) { - mErrorCode = errorCode; - } - - public int getErrorCode() { - return mErrorCode; - } - /** * Returns whether or not the fail cause is a failure that requires a modem restart * * @param context device context + * @param cause data disconnect cause * @param subId subscription index * @return true if the fail cause code needs platform to trigger a modem restart. */ - public boolean isRadioRestartFailure(Context context, int subId) { + public static boolean isRadioRestartFailure(@NonNull Context context, @FailCause int cause, + int subId) { CarrierConfigManager configManager = (CarrierConfigManager) context.getSystemService(Context.CARRIER_CONFIG_SERVICE); if (configManager != null) { PersistableBundle b = configManager.getConfigForSubId(subId); if (b != null) { - if (this == REGULAR_DEACTIVATION + if (cause == REGULAR_DEACTIVATION && b.getBoolean(CarrierConfigManager .KEY_RESTART_RADIO_ON_PDP_FAIL_REGULAR_DEACTIVATION_BOOL)) { // This is for backward compatibility support. We need to continue support this @@ -170,7 +402,7 @@ public enum DataFailCause { int[] causeCodes = b.getIntArray(CarrierConfigManager .KEY_RADIO_RESTART_FAILURE_CAUSES_INT_ARRAY); if (causeCodes != null) { - return Arrays.stream(causeCodes).anyMatch(i -> i == getErrorCode()); + return Arrays.stream(causeCodes).anyMatch(i -> i == cause); } } } @@ -178,11 +410,11 @@ public enum DataFailCause { return false; } - public boolean isPermanentFailure(Context context, int subId) { - + public static boolean isPermanentFailure(@NonNull Context context, @FailCause int failCause, + int subId) { synchronized (sPermanentFailureCache) { - HashSet<DataFailCause> permanentFailureSet = sPermanentFailureCache.get(subId); + Set<Integer> permanentFailureSet = sPermanentFailureCache.get(subId); // In case of cache miss, we need to look up the settings from carrier config. if (permanentFailureSet == null) { @@ -194,11 +426,12 @@ public enum DataFailCause { if (b != null) { String[] permanentFailureStrings = b.getStringArray(CarrierConfigManager. KEY_CARRIER_DATA_CALL_PERMANENT_FAILURE_STRINGS); - if (permanentFailureStrings != null) { permanentFailureSet = new HashSet<>(); - for (String failure : permanentFailureStrings) { - permanentFailureSet.add(DataFailCause.valueOf(failure)); + for (Map.Entry<Integer, String> e : sFailCauseMap.entrySet()) { + if (ArrayUtils.contains(permanentFailureStrings, e.getValue())) { + permanentFailureSet.add(e.getKey()); + } } } } @@ -207,7 +440,7 @@ public enum DataFailCause { // If we are not able to find the configuration from carrier config, use the default // ones. if (permanentFailureSet == null) { - permanentFailureSet = new HashSet<DataFailCause>() { + permanentFailureSet = new HashSet<Integer>() { { add(OPERATOR_BARRED); add(MISSING_UNKNOWN_APN); @@ -232,28 +465,39 @@ public enum DataFailCause { sPermanentFailureCache.put(subId, permanentFailureSet); } - return permanentFailureSet.contains(this); + return permanentFailureSet.contains(failCause); } } - public boolean isEventLoggable() { - return (this == OPERATOR_BARRED) || (this == INSUFFICIENT_RESOURCES) || - (this == UNKNOWN_PDP_ADDRESS_TYPE) || (this == USER_AUTHENTICATION) || - (this == ACTIVATION_REJECT_GGSN) || (this == ACTIVATION_REJECT_UNSPECIFIED) || - (this == SERVICE_OPTION_NOT_SUBSCRIBED) || - (this == SERVICE_OPTION_NOT_SUPPORTED) || - (this == SERVICE_OPTION_OUT_OF_ORDER) || (this == NSAPI_IN_USE) || - (this == ONLY_IPV4_ALLOWED) || (this == ONLY_IPV6_ALLOWED) || - (this == PROTOCOL_ERRORS) || (this == SIGNAL_LOST) || - (this == RADIO_POWER_OFF) || (this == TETHERED_CALL_ACTIVE) || - (this == UNACCEPTABLE_NETWORK_PARAMETER); + public static boolean isEventLoggable(@FailCause int dataFailCause) { + return (dataFailCause == OPERATOR_BARRED) || (dataFailCause == INSUFFICIENT_RESOURCES) + || (dataFailCause == UNKNOWN_PDP_ADDRESS_TYPE) + || (dataFailCause == USER_AUTHENTICATION) + || (dataFailCause == ACTIVATION_REJECT_GGSN) + || (dataFailCause == ACTIVATION_REJECT_UNSPECIFIED) + || (dataFailCause == SERVICE_OPTION_NOT_SUBSCRIBED) + || (dataFailCause == SERVICE_OPTION_NOT_SUPPORTED) + || (dataFailCause == SERVICE_OPTION_OUT_OF_ORDER) + || (dataFailCause == NSAPI_IN_USE) + || (dataFailCause == ONLY_IPV4_ALLOWED) + || (dataFailCause == ONLY_IPV6_ALLOWED) + || (dataFailCause == PROTOCOL_ERRORS) + || (dataFailCause == SIGNAL_LOST) + || (dataFailCause == RADIO_POWER_OFF) + || (dataFailCause == TETHERED_CALL_ACTIVE) + || (dataFailCause == UNACCEPTABLE_NETWORK_PARAMETER); + } + + public static String toString(@FailCause int dataFailCause) { + int cause = getFailCause(dataFailCause); + return (cause == UNKNOWN) ? "UNKNOWN(" + dataFailCause + ")" : sFailCauseMap.get(cause); } - public static DataFailCause fromInt(int errorCode) { - DataFailCause fc = sErrorCodeToFailCauseMap.get(errorCode); - if (fc == null) { - fc = UNKNOWN; + public static int getFailCause(@FailCause int failCause) { + if (sFailCauseMap.containsKey(failCause)) { + return failCause; + } else { + return UNKNOWN; } - return fc; } } diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index c7df36b62a79..f241d45bf1e2 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -4908,7 +4908,7 @@ public class TelephonyManager { */ @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) public void requestCellInfoUpdate( - @NonNull Executor executor, @NonNull CellInfoCallback callback) { + @NonNull @CallbackExecutor Executor executor, @NonNull CellInfoCallback callback) { try { ITelephony telephony = getITelephony(); if (telephony == null) return; @@ -6382,8 +6382,9 @@ public class TelephonyManager { public @PrefNetworkMode int getPreferredNetworkType(int subId) { try { ITelephony telephony = getITelephony(); - if (telephony != null) + if (telephony != null) { return telephony.getPreferredNetworkType(subId); + } } catch (RemoteException ex) { Rlog.e(TAG, "getPreferredNetworkType RemoteException", ex); } catch (NullPointerException ex) { @@ -6393,6 +6394,37 @@ public class TelephonyManager { } /** + * Get the preferred network type bitmap. + * + * <p>If this object has been created with {@link #createForSubscriptionId}, applies to the + * given subId. Otherwise, applies to {@link SubscriptionManager#getDefaultSubscriptionId()} + * + * <p>Requires Permission: + * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE} + * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}). + * + * @return a 32-bit bitmap. + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + @SystemApi + public @NetworkTypeBitMask int getPreferredNetworkTypeBitmap() { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + return RadioAccessFamily.getRafFromNetworkType( + telephony.getPreferredNetworkType(getSubId())); + } + } catch (RemoteException ex) { + Rlog.e(TAG, "getPreferredNetworkTypeBitmap RemoteException", ex); + } catch (NullPointerException ex) { + Rlog.e(TAG, "getPreferredNetworkTypeBitmap NPE", ex); + } + return 0; + } + + /** * Sets the network selection mode to automatic. * * <p>If this object has been created with {@link #createForSubscriptionId}, applies to the @@ -6607,6 +6639,37 @@ public class TelephonyManager { } /** + * Set the preferred network type bitmap. + * + * <p>If this object has been created with {@link #createForSubscriptionId}, applies to the + * given subId. Otherwise, applies to {@link SubscriptionManager#getDefaultSubscriptionId()} + * + * <p>Requires Permission: + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling + * app has carrier privileges (see {@link #hasCarrierPrivileges}). + * + * @param networkTypeBitmap a 32-bit bitmap. + * @return true on success; false on any failure. + * @hide + */ + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + @SystemApi + public boolean setPreferredNetworkTypeBitmap(@NetworkTypeBitMask int networkTypeBitmap) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + return telephony.setPreferredNetworkType( + getSubId(), RadioAccessFamily.getNetworkTypeFromRaf(networkTypeBitmap)); + } + } catch (RemoteException ex) { + Rlog.e(TAG, "setPreferredNetworkType RemoteException", ex); + } catch (NullPointerException ex) { + Rlog.e(TAG, "setPreferredNetworkType NPE", ex); + } + return false; + } + + /** * Set the preferred network type to global mode which includes LTE, CDMA, EvDo and GSM/WCDMA. * * <p>Requires that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}). diff --git a/tests/testables/src/android/testing/BaseFragmentTest.java b/tests/testables/src/android/testing/BaseFragmentTest.java index 5fa065a9135a..d18c126a96c1 100644 --- a/tests/testables/src/android/testing/BaseFragmentTest.java +++ b/tests/testables/src/android/testing/BaseFragmentTest.java @@ -21,7 +21,9 @@ import android.app.Fragment; import android.app.FragmentController; import android.app.FragmentHostCallback; import android.app.FragmentManagerNonConfig; +import android.content.Context; import android.graphics.PixelFormat; +import android.os.Bundle; import android.os.Handler; import android.os.Parcelable; import android.support.test.InstrumentationRegistry; @@ -75,7 +77,7 @@ public abstract class BaseFragmentTest { TestableLooper.get(this).runWithLooper(() -> { mHandler = new Handler(); - mFragment = mCls.newInstance(); + mFragment = instantiate(mContext, mCls.getName(), null); mFragments = FragmentController.createController(new HostCallbacks()); mFragments.attachHost(null); mFragments.getFragmentManager().beginTransaction() @@ -187,6 +189,13 @@ public abstract class BaseFragmentTest { TestableLooper.get(this).processAllMessages(); } + /** + * Method available for override to replace fragment instantiation. + */ + protected Fragment instantiate(Context context, String className, @Nullable Bundle arguments) { + return Fragment.instantiate(context, className, arguments); + } + private View findViewById(int id) { return mView.findViewById(id); } @@ -206,6 +215,11 @@ public abstract class BaseFragmentTest { } @Override + public Fragment instantiate(Context context, String className, Bundle arguments) { + return BaseFragmentTest.this.instantiate(context, className, arguments); + } + + @Override public boolean onShouldSaveFragmentState(Fragment fragment) { return true; // True for now. } diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp index c91134c167ed..58702dc465cc 100644 --- a/tools/aapt2/ResourceParser.cpp +++ b/tools/aapt2/ResourceParser.cpp @@ -99,7 +99,7 @@ struct ParsedResource { ResourceId id; Visibility::Level visibility_level = Visibility::Level::kUndefined; bool allow_new = false; - Maybe<Overlayable> overlayable; + Maybe<OverlayableItem> overlayable_item; std::string comment; std::unique_ptr<Value> value; @@ -133,8 +133,8 @@ static bool AddResourcesToTable(ResourceTable* table, IDiagnostics* diag, Parsed } } - if (res->overlayable) { - if (!table->SetOverlayable(res->name, res->overlayable.value(), diag)) { + if (res->overlayable_item) { + if (!table->SetOverlayable(res->name, res->overlayable_item.value(), diag)) { return false; } } @@ -1059,92 +1059,119 @@ bool ResourceParser::ParseSymbol(xml::XmlPullParser* parser, ParsedResource* out bool ResourceParser::ParseOverlayable(xml::XmlPullParser* parser, ParsedResource* out_resource) { if (out_resource->config != ConfigDescription::DefaultConfig()) { diag_->Warn(DiagMessage(out_resource->source) - << "ignoring configuration '" << out_resource->config - << "' for <overlayable> tag"); + << "ignoring configuration '" << out_resource->config + << "' for <overlayable> tag"); + } + + Maybe<StringPiece> overlayable_name = xml::FindNonEmptyAttribute(parser, "name"); + if (!overlayable_name) { + diag_->Error(DiagMessage(out_resource->source) + << "<overlayable> tag must have a 'name' attribute"); + return false; } + const std::string kActorUriScheme = + android::base::StringPrintf("%s://", Overlayable::kActorScheme); + Maybe<StringPiece> overlayable_actor = xml::FindNonEmptyAttribute(parser, "actor"); + if (overlayable_actor && !util::StartsWith(overlayable_actor.value(), kActorUriScheme)) { + diag_->Error(DiagMessage(out_resource->source) + << "specified <overlayable> tag 'actor' attribute must use the scheme '" + << Overlayable::kActorScheme << "'"); + return false; + } + + // Create a overlayable entry grouping that represents this <overlayable> + auto overlayable = std::make_shared<Overlayable>( + overlayable_name.value(), (overlayable_actor) ? overlayable_actor.value() : "", + out_resource->source); + bool error = false; std::string comment; - Overlayable::PolicyFlags current_policies = Overlayable::Policy::kNone; + OverlayableItem::PolicyFlags current_policies = OverlayableItem::Policy::kNone; const size_t start_depth = parser->depth(); while (xml::XmlPullParser::IsGoodEvent(parser->Next())) { xml::XmlPullParser::Event event = parser->event(); if (event == xml::XmlPullParser::Event::kEndElement && parser->depth() == start_depth) { - // Break the loop when exiting the overlayable element + // Break the loop when exiting the <overlayable> break; } else if (event == xml::XmlPullParser::Event::kEndElement && parser->depth() == start_depth + 1) { - // Clear the current policies when exiting the policy element - current_policies = Overlayable::Policy::kNone; + // Clear the current policies when exiting the <policy> tags + current_policies = OverlayableItem::Policy::kNone; continue; } else if (event == xml::XmlPullParser::Event::kComment) { - // Get the comment of individual item elements + // Retrieve the comment of individual <item> tags comment = parser->comment(); continue; } else if (event != xml::XmlPullParser::Event::kStartElement) { - // Skip to the next element + // Skip to the start of the next element continue; } - const Source item_source = source_.WithLine(parser->line_number()); + const Source element_source = source_.WithLine(parser->line_number()); const std::string& element_name = parser->element_name(); const std::string& element_namespace = parser->element_namespace(); if (element_namespace.empty() && element_name == "item") { // Items specify the name and type of resource that should be overlayable - Maybe<StringPiece> maybe_name = xml::FindNonEmptyAttribute(parser, "name"); - if (!maybe_name) { - diag_->Error(DiagMessage(item_source) - << "<item> within an <overlayable> tag must have a 'name' attribute"); + Maybe<StringPiece> item_name = xml::FindNonEmptyAttribute(parser, "name"); + if (!item_name) { + diag_->Error(DiagMessage(element_source) + << "<item> within an <overlayable> must have a 'name' attribute"); error = true; continue; } - Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type"); - if (!maybe_type) { - diag_->Error(DiagMessage(item_source) - << "<item> within an <overlayable> tag must have a 'type' attribute"); + Maybe<StringPiece> item_type = xml::FindNonEmptyAttribute(parser, "type"); + if (!item_type) { + diag_->Error(DiagMessage(element_source) + << "<item> within an <overlayable> must have a 'type' attribute"); error = true; continue; } - const ResourceType* type = ParseResourceType(maybe_type.value()); + const ResourceType* type = ParseResourceType(item_type.value()); if (type == nullptr) { - diag_->Error(DiagMessage(item_source) - << "invalid resource type '" << maybe_type.value() + diag_->Error(DiagMessage(element_source) + << "invalid resource type '" << item_type.value() << "' in <item> within an <overlayable>"); error = true; continue; } - ParsedResource child_resource; + OverlayableItem overlayable_item(overlayable); + overlayable_item.policies = current_policies; + overlayable_item.comment = comment; + overlayable_item.source = element_source; + + ParsedResource child_resource{}; child_resource.name.type = *type; - child_resource.name.entry = maybe_name.value().to_string(); - child_resource.overlayable = Overlayable{current_policies, item_source, comment}; + child_resource.name.entry = item_name.value().to_string(); + child_resource.overlayable_item = overlayable_item; out_resource->child_resources.push_back(std::move(child_resource)); } else if (element_namespace.empty() && element_name == "policy") { - if (current_policies != Overlayable::Policy::kNone) { + if (current_policies != OverlayableItem::Policy::kNone) { // If the policy list is not empty, then we are currently inside a policy element - diag_->Error(DiagMessage(item_source) << "<policy> blocks cannot be recursively nested"); + diag_->Error(DiagMessage(element_source) << "<policy> blocks cannot be recursively nested"); error = true; break; } else if (Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type")) { // Parse the polices separated by vertical bar characters to allow for specifying multiple - // policies + // policies. Items within the policy tag will have the specified policy. for (StringPiece part : util::Tokenize(maybe_type.value(), '|')) { StringPiece trimmed_part = util::TrimWhitespace(part); if (trimmed_part == "public") { - current_policies |= Overlayable::Policy::kPublic; + current_policies |= OverlayableItem::Policy::kPublic; } else if (trimmed_part == "product") { - current_policies |= Overlayable::Policy::kProduct; + current_policies |= OverlayableItem::Policy::kProduct; } else if (trimmed_part == "product_services") { - current_policies |= Overlayable::Policy::kProductServices; + current_policies |= OverlayableItem::Policy::kProductServices; } else if (trimmed_part == "system") { - current_policies |= Overlayable::Policy::kSystem; + current_policies |= OverlayableItem::Policy::kSystem; } else if (trimmed_part == "vendor") { - current_policies |= Overlayable::Policy::kVendor; + current_policies |= OverlayableItem::Policy::kVendor; } else { - diag_->Error(DiagMessage(item_source) + diag_->Error(DiagMessage(element_source) << "<policy> has unsupported type '" << trimmed_part << "'"); error = true; continue; @@ -1152,11 +1179,13 @@ bool ResourceParser::ParseOverlayable(xml::XmlPullParser* parser, ParsedResource } } } else if (!ShouldIgnoreElement(element_namespace, element_name)) { - diag_->Error(DiagMessage(item_source) << "invalid element <" << element_name << "> " + diag_->Error(DiagMessage(element_source) << "invalid element <" << element_name << "> " << " in <overlayable>"); error = true; break; } + + comment.clear(); } return !error; diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp index 03e6197027cb..debca9c1e1ba 100644 --- a/tools/aapt2/ResourceParser_test.cpp +++ b/tools/aapt2/ResourceParser_test.cpp @@ -892,11 +892,8 @@ TEST_F(ResourceParserTest, ParsePlatformIndependentNewline) { } TEST_F(ResourceParserTest, ParseOverlayable) { - std::string input = R"(<overlayable />)"; - EXPECT_TRUE(TestParse(input)); - - input = R"( - <overlayable> + std::string input = R"( + <overlayable name="Name" actor="overlay://theme"> <item type="string" name="foo" /> <item type="drawable" name="bar" /> </overlayable>)"; @@ -905,24 +902,35 @@ TEST_F(ResourceParserTest, ParseOverlayable) { auto search_result = table_.FindResource(test::ParseNameOrDie("string/foo")); ASSERT_TRUE(search_result); ASSERT_THAT(search_result.value().entry, NotNull()); - ASSERT_TRUE(search_result.value().entry->overlayable); - EXPECT_THAT(search_result.value().entry->overlayable.value().policies, - Eq(Overlayable::Policy::kNone)); + ASSERT_TRUE(search_result.value().entry->overlayable_item); + OverlayableItem& result_overlayable_item = search_result.value().entry->overlayable_item.value(); + EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name")); + EXPECT_THAT(result_overlayable_item.overlayable->actor, Eq("overlay://theme")); + EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kNone)); search_result = table_.FindResource(test::ParseNameOrDie("drawable/bar")); ASSERT_TRUE(search_result); ASSERT_THAT(search_result.value().entry, NotNull()); - ASSERT_TRUE(search_result.value().entry->overlayable); - EXPECT_THAT(search_result.value().entry->overlayable.value().policies, - Eq(Overlayable::Policy::kNone)); + ASSERT_TRUE(search_result.value().entry->overlayable_item); + result_overlayable_item = search_result.value().entry->overlayable_item.value(); + EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name")); + EXPECT_THAT(result_overlayable_item.overlayable->actor, Eq("overlay://theme")); + EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kNone)); } -TEST_F(ResourceParserTest, ParseOverlayablePolicy) { - std::string input = R"(<overlayable />)"; - EXPECT_TRUE(TestParse(input)); +TEST_F(ResourceParserTest, ParseOverlayableRequiresName) { + EXPECT_FALSE(TestParse(R"(<overlayable actor="overlay://theme" />)")); + EXPECT_TRUE(TestParse(R"(<overlayable name="Name" />)")); + EXPECT_TRUE(TestParse(R"(<overlayable name="Name" actor="overlay://theme" />)")); +} - input = R"( - <overlayable> +TEST_F(ResourceParserTest, ParseOverlayableBadActorFail) { + EXPECT_FALSE(TestParse(R"(<overlayable name="Name" actor="overley://theme" />)")); +} + +TEST_F(ResourceParserTest, ParseOverlayablePolicy) { + std::string input = R"( + <overlayable name="Name"> <item type="string" name="foo" /> <policy type="product"> <item type="string" name="bar" /> @@ -945,49 +953,55 @@ TEST_F(ResourceParserTest, ParseOverlayablePolicy) { auto search_result = table_.FindResource(test::ParseNameOrDie("string/foo")); ASSERT_TRUE(search_result); ASSERT_THAT(search_result.value().entry, NotNull()); - ASSERT_TRUE(search_result.value().entry->overlayable); - Overlayable& overlayable = search_result.value().entry->overlayable.value(); - EXPECT_THAT(overlayable.policies, Eq(Overlayable::Policy::kNone)); + ASSERT_TRUE(search_result.value().entry->overlayable_item); + OverlayableItem result_overlayable_item = search_result.value().entry->overlayable_item.value(); + EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name")); + EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kNone)); search_result = table_.FindResource(test::ParseNameOrDie("string/bar")); ASSERT_TRUE(search_result); ASSERT_THAT(search_result.value().entry, NotNull()); - ASSERT_TRUE(search_result.value().entry->overlayable); - overlayable = search_result.value().entry->overlayable.value(); - EXPECT_THAT(overlayable.policies, Eq(Overlayable::Policy::kProduct)); + ASSERT_TRUE(search_result.value().entry->overlayable_item); + result_overlayable_item = search_result.value().entry->overlayable_item.value(); + EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name")); + EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kProduct)); search_result = table_.FindResource(test::ParseNameOrDie("string/baz")); ASSERT_TRUE(search_result); ASSERT_THAT(search_result.value().entry, NotNull()); - ASSERT_TRUE(search_result.value().entry->overlayable); - overlayable = search_result.value().entry->overlayable.value(); - EXPECT_THAT(overlayable.policies, Eq(Overlayable::Policy::kProductServices)); + ASSERT_TRUE(search_result.value().entry->overlayable_item); + result_overlayable_item = search_result.value().entry->overlayable_item.value(); + EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name")); + EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kProductServices)); search_result = table_.FindResource(test::ParseNameOrDie("string/fiz")); ASSERT_TRUE(search_result); ASSERT_THAT(search_result.value().entry, NotNull()); - ASSERT_TRUE(search_result.value().entry->overlayable); - overlayable = search_result.value().entry->overlayable.value(); - EXPECT_THAT(overlayable.policies, Eq(Overlayable::Policy::kSystem)); + ASSERT_TRUE(search_result.value().entry->overlayable_item); + result_overlayable_item = search_result.value().entry->overlayable_item.value(); + EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name")); + EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kSystem)); search_result = table_.FindResource(test::ParseNameOrDie("string/fuz")); ASSERT_TRUE(search_result); ASSERT_THAT(search_result.value().entry, NotNull()); - ASSERT_TRUE(search_result.value().entry->overlayable); - overlayable = search_result.value().entry->overlayable.value(); - EXPECT_THAT(overlayable.policies, Eq(Overlayable::Policy::kVendor)); + ASSERT_TRUE(search_result.value().entry->overlayable_item); + result_overlayable_item = search_result.value().entry->overlayable_item.value(); + EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name")); + EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kVendor)); search_result = table_.FindResource(test::ParseNameOrDie("string/faz")); ASSERT_TRUE(search_result); ASSERT_THAT(search_result.value().entry, NotNull()); - ASSERT_TRUE(search_result.value().entry->overlayable); - overlayable = search_result.value().entry->overlayable.value(); - EXPECT_THAT(overlayable.policies, Eq(Overlayable::Policy::kPublic)); + ASSERT_TRUE(search_result.value().entry->overlayable_item); + result_overlayable_item = search_result.value().entry->overlayable_item.value(); + EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name")); + EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kPublic)); } TEST_F(ResourceParserTest, ParseOverlayableBadPolicyError) { std::string input = R"( - <overlayable> + <overlayable name="Name"> <policy type="illegal_policy"> <item type="string" name="foo" /> </policy> @@ -995,7 +1009,7 @@ TEST_F(ResourceParserTest, ParseOverlayableBadPolicyError) { EXPECT_FALSE(TestParse(input)); input = R"( - <overlayable> + <overlayable name="Name"> <policy type="product"> <item name="foo" /> </policy> @@ -1003,7 +1017,7 @@ TEST_F(ResourceParserTest, ParseOverlayableBadPolicyError) { EXPECT_FALSE(TestParse(input)); input = R"( - <overlayable> + <overlayable name="Name"> <policy type="vendor"> <item type="string" /> </policy> @@ -1013,7 +1027,7 @@ TEST_F(ResourceParserTest, ParseOverlayableBadPolicyError) { TEST_F(ResourceParserTest, ParseOverlayableMultiplePolicy) { std::string input = R"( - <overlayable> + <overlayable name="Name"> <policy type="vendor|product_services"> <item type="string" name="foo" /> </policy> @@ -1026,39 +1040,59 @@ TEST_F(ResourceParserTest, ParseOverlayableMultiplePolicy) { auto search_result = table_.FindResource(test::ParseNameOrDie("string/foo")); ASSERT_TRUE(search_result); ASSERT_THAT(search_result.value().entry, NotNull()); - ASSERT_TRUE(search_result.value().entry->overlayable); - Overlayable& overlayable = search_result.value().entry->overlayable.value(); - EXPECT_THAT(overlayable.policies, Eq(Overlayable::Policy::kVendor - | Overlayable::Policy::kProductServices)); + ASSERT_TRUE(search_result.value().entry->overlayable_item); + OverlayableItem result_overlayable_item = search_result.value().entry->overlayable_item.value(); + EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name")); + EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kVendor + | OverlayableItem::Policy::kProductServices)); search_result = table_.FindResource(test::ParseNameOrDie("string/bar")); ASSERT_TRUE(search_result); ASSERT_THAT(search_result.value().entry, NotNull()); - ASSERT_TRUE(search_result.value().entry->overlayable); - overlayable = search_result.value().entry->overlayable.value(); - EXPECT_THAT(overlayable.policies, Eq(Overlayable::Policy::kProduct - | Overlayable::Policy::kSystem)); + ASSERT_TRUE(search_result.value().entry->overlayable_item); + result_overlayable_item = search_result.value().entry->overlayable_item.value(); + EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name")); + EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kProduct + | OverlayableItem::Policy::kSystem)); } TEST_F(ResourceParserTest, DuplicateOverlayableIsError) { std::string input = R"( - <overlayable> + <overlayable name="Name"> + <item type="string" name="foo" /> + <item type="string" name="foo" /> + </overlayable>)"; + EXPECT_FALSE(TestParse(input)); + + input = R"( + <overlayable name="Name"> + <item type="string" name="foo" /> + </overlayable> + <overlayable name="Name"> <item type="string" name="foo" /> + </overlayable>)"; + EXPECT_FALSE(TestParse(input)); + + input = R"( + <overlayable name="Name"> + <item type="string" name="foo" /> + </overlayable> + <overlayable name="Other"> <item type="string" name="foo" /> </overlayable>)"; EXPECT_FALSE(TestParse(input)); input = R"( - <overlayable> + <overlayable name="Name" actor="overlay://my.actor.one"> <item type="string" name="foo" /> </overlayable> - <overlayable> + <overlayable name="Other" actor="overlay://my.actor.two"> <item type="string" name="foo" /> </overlayable>)"; EXPECT_FALSE(TestParse(input)); input = R"( - <overlayable> + <overlayable name="Name"> <policy type="product"> <item type="string" name="foo" /> <item type="string" name="foo" /> @@ -1067,7 +1101,7 @@ TEST_F(ResourceParserTest, DuplicateOverlayableIsError) { EXPECT_FALSE(TestParse(input)); input = R"( - <overlayable> + <overlayable name="Name"> <policy type="product"> <item type="string" name="foo" /> </policy> @@ -1076,7 +1110,7 @@ TEST_F(ResourceParserTest, DuplicateOverlayableIsError) { EXPECT_FALSE(TestParse(input)); input = R"( - <overlayable> + <overlayable name="Name"> <policy type="product"> <item type="string" name="foo" /> </policy> @@ -1087,13 +1121,13 @@ TEST_F(ResourceParserTest, DuplicateOverlayableIsError) { EXPECT_FALSE(TestParse(input)); input = R"( - <overlayable> + <overlayable name="Name"> <policy type="product"> <item type="string" name="foo" /> </policy> </overlayable> - <overlayable> + <overlayable name="Name"> <policy type="product"> <item type="string" name="foo" /> </policy> @@ -1103,7 +1137,7 @@ TEST_F(ResourceParserTest, DuplicateOverlayableIsError) { TEST_F(ResourceParserTest, NestPolicyInOverlayableError) { std::string input = R"( - <overlayable> + <overlayable name="Name"> <policy type="vendor|product"> <policy type="product_services"> <item type="string" name="foo" /> diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp index 54633ad5c5e3..dbd0a0ca1799 100644 --- a/tools/aapt2/ResourceTable.cpp +++ b/tools/aapt2/ResourceTable.cpp @@ -40,6 +40,8 @@ using ::android::base::StringPrintf; namespace aapt { +const char* Overlayable::kActorScheme = "overlay"; + static bool less_than_type_and_id(const std::unique_ptr<ResourceTableType>& lhs, const std::pair<ResourceType, Maybe<uint8_t>>& rhs) { return lhs->type < rhs.first || (lhs->type == rhs.first && rhs.second && lhs->id < rhs.second); @@ -625,17 +627,18 @@ bool ResourceTable::SetAllowNewImpl(const ResourceNameRef& name, const AllowNew& return true; } -bool ResourceTable::SetOverlayable(const ResourceNameRef& name, const Overlayable& overlayable, +bool ResourceTable::SetOverlayable(const ResourceNameRef& name, const OverlayableItem& overlayable, IDiagnostics* diag) { return SetOverlayableImpl(name, overlayable, ResourceNameValidator, diag); } bool ResourceTable::SetOverlayableMangled(const ResourceNameRef& name, - const Overlayable& overlayable, IDiagnostics* diag) { + const OverlayableItem& overlayable, IDiagnostics* diag) { return SetOverlayableImpl(name, overlayable, SkipNameValidator, diag); } -bool ResourceTable::SetOverlayableImpl(const ResourceNameRef& name, const Overlayable& overlayable, +bool ResourceTable::SetOverlayableImpl(const ResourceNameRef& name, + const OverlayableItem& overlayable, NameValidator name_validator, IDiagnostics *diag) { CHECK(diag != nullptr); @@ -647,14 +650,15 @@ bool ResourceTable::SetOverlayableImpl(const ResourceNameRef& name, const Overla ResourceTableType* type = package->FindOrCreateType(name.type); ResourceEntry* entry = type->FindOrCreateEntry(name.entry); - if (entry->overlayable) { + if (entry->overlayable_item) { diag->Error(DiagMessage(overlayable.source) - << "duplicate overlayable declaration for resource '" << name << "'"); - diag->Error(DiagMessage(entry->overlayable.value().source) << "previous declaration here"); + << "duplicate overlayable declaration for resource '" << name << "'"); + diag->Error(DiagMessage(entry->overlayable_item.value().source) + << "previous declaration here"); return false; } - entry->overlayable = overlayable; + entry->overlayable_item = overlayable; return true; } @@ -690,7 +694,7 @@ std::unique_ptr<ResourceTable> ResourceTable::Clone() const { new_entry->id = entry->id; new_entry->visibility = entry->visibility; new_entry->allow_new = entry->allow_new; - new_entry->overlayable = entry->overlayable; + new_entry->overlayable_item = entry->overlayable_item; for (const auto& config_value : entry->values) { ResourceConfigValue* new_value = diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h index e646f5be43c7..eaf6a47a15fd 100644 --- a/tools/aapt2/ResourceTable.h +++ b/tools/aapt2/ResourceTable.h @@ -57,10 +57,27 @@ struct AllowNew { std::string comment; }; -// Represents a declaration that a resource is overlayable at runtime. struct Overlayable { + Overlayable() = default; + Overlayable(const android::StringPiece& name, const android::StringPiece& actor) + : name(name.to_string()), actor(actor.to_string()) {} + Overlayable(const android::StringPiece& name, const android::StringPiece& actor, + const Source& source) + : name(name.to_string()), actor(actor.to_string()), source(source ){} + + static const char* kActorScheme; + std::string name; + std::string actor; + Source source; +}; + +// Represents a declaration that a resource is overlayable at runtime. +struct OverlayableItem { + explicit OverlayableItem(const std::shared_ptr<Overlayable>& overlayable) + : overlayable(overlayable) {} // Represents the types overlays that are allowed to overlay the resource. + typedef uint32_t PolicyFlags; enum Policy : uint32_t { kNone = 0x00, @@ -80,11 +97,10 @@ struct Overlayable { kProductServices = 0x10 }; - typedef uint32_t PolicyFlags; + std::shared_ptr<Overlayable> overlayable; PolicyFlags policies = Policy::kNone; - - Source source; std::string comment; + Source source; }; class ResourceConfigValue { @@ -121,7 +137,7 @@ class ResourceEntry { Maybe<AllowNew> allow_new; // The declarations of this resource as overlayable for RROs - Maybe<Overlayable> overlayable; + Maybe<OverlayableItem> overlayable_item; // The resource's values for each configuration. std::vector<std::unique_ptr<ResourceConfigValue>> values; @@ -251,9 +267,9 @@ class ResourceTable { bool SetVisibilityWithIdMangled(const ResourceNameRef& name, const Visibility& visibility, const ResourceId& res_id, IDiagnostics* diag); - bool SetOverlayable(const ResourceNameRef& name, const Overlayable& overlayable, + bool SetOverlayable(const ResourceNameRef& name, const OverlayableItem& overlayable, IDiagnostics *diag); - bool SetOverlayableMangled(const ResourceNameRef& name, const Overlayable& overlayable, + bool SetOverlayableMangled(const ResourceNameRef& name, const OverlayableItem& overlayable, IDiagnostics* diag); bool SetAllowNew(const ResourceNameRef& name, const AllowNew& allow_new, IDiagnostics* diag); @@ -328,7 +344,7 @@ class ResourceTable { bool SetAllowNewImpl(const ResourceNameRef& name, const AllowNew& allow_new, NameValidator name_validator, IDiagnostics* diag); - bool SetOverlayableImpl(const ResourceNameRef &name, const Overlayable &overlayable, + bool SetOverlayableImpl(const ResourceNameRef &name, const OverlayableItem& overlayable, NameValidator name_validator, IDiagnostics *diag); bool SetSymbolStateImpl(const ResourceNameRef& name, const ResourceId& res_id, diff --git a/tools/aapt2/ResourceTable_test.cpp b/tools/aapt2/ResourceTable_test.cpp index 31095c4d88c8..a733134f123c 100644 --- a/tools/aapt2/ResourceTable_test.cpp +++ b/tools/aapt2/ResourceTable_test.cpp @@ -244,48 +244,90 @@ TEST(ResourceTableTest, SetAllowNew) { TEST(ResourceTableTest, SetOverlayable) { ResourceTable table; - Overlayable overlayable{}; - overlayable.policies |= Overlayable::Policy::kProduct; - overlayable.policies |= Overlayable::Policy::kProductServices; - overlayable.comment = "comment"; + auto overlayable = std::make_shared<Overlayable>("Name", "overlay://theme", + Source("res/values/overlayable.xml", 40)); + OverlayableItem overlayable_item(overlayable); + overlayable_item.policies |= OverlayableItem::Policy::kProduct; + overlayable_item.policies |= OverlayableItem::Policy::kProductServices; + overlayable_item.comment = "comment"; + overlayable_item.source = Source("res/values/overlayable.xml", 42); const ResourceName name = test::ParseNameOrDie("android:string/foo"); - ASSERT_TRUE(table.SetOverlayable(name, overlayable, test::GetDiagnostics())); + ASSERT_TRUE(table.SetOverlayable(name, overlayable_item, test::GetDiagnostics())); Maybe<ResourceTable::SearchResult> search_result = table.FindResource(name); ASSERT_TRUE(search_result); - ASSERT_TRUE(search_result.value().entry->overlayable); + ASSERT_TRUE(search_result.value().entry->overlayable_item); + + OverlayableItem& result_overlayable_item = search_result.value().entry->overlayable_item.value(); + EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name")); + EXPECT_THAT(result_overlayable_item.overlayable->actor, Eq("overlay://theme")); + EXPECT_THAT(result_overlayable_item.overlayable->source.path, Eq("res/values/overlayable.xml")); + EXPECT_THAT(result_overlayable_item.overlayable->source.line, 40); + EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kProduct + | OverlayableItem::Policy::kProductServices)); + ASSERT_THAT(result_overlayable_item.comment, StrEq("comment")); + EXPECT_THAT(result_overlayable_item.source.path, Eq("res/values/overlayable.xml")); + EXPECT_THAT(result_overlayable_item.source.line, 42); +} + +TEST(ResourceTableTest, SetMultipleOverlayableResources) { + ResourceTable table; + + const ResourceName foo = test::ParseNameOrDie("android:string/foo"); + auto group = std::make_shared<Overlayable>("Name", "overlay://theme"); + OverlayableItem overlayable(group); + overlayable.policies = OverlayableItem::Policy::kProduct; + ASSERT_TRUE(table.SetOverlayable(foo, overlayable, test::GetDiagnostics())); + + const ResourceName bar = test::ParseNameOrDie("android:string/bar"); + OverlayableItem overlayable2(group); + overlayable2.policies = OverlayableItem::Policy::kProduct; + ASSERT_TRUE(table.SetOverlayable(bar, overlayable2, test::GetDiagnostics())); + + const ResourceName baz = test::ParseNameOrDie("android:string/baz"); + OverlayableItem overlayable3(group); + overlayable3.policies = OverlayableItem::Policy::kVendor; + ASSERT_TRUE(table.SetOverlayable(baz, overlayable3, test::GetDiagnostics())); +} + +TEST(ResourceTableTest, SetOverlayableDifferentResourcesDifferentName) { + ResourceTable table; + + const ResourceName foo = test::ParseNameOrDie("android:string/foo"); + OverlayableItem overlayable_item(std::make_shared<Overlayable>("Name", "overlay://theme")); + overlayable_item.policies = OverlayableItem::Policy::kProduct; + ASSERT_TRUE(table.SetOverlayable(foo, overlayable_item, test::GetDiagnostics())); - Overlayable& result_overlayable = search_result.value().entry->overlayable.value(); - ASSERT_THAT(result_overlayable.comment, StrEq("comment")); - EXPECT_THAT(result_overlayable.policies, Eq(Overlayable::Policy::kProduct - | Overlayable::Policy::kProductServices)); + const ResourceName bar = test::ParseNameOrDie("android:string/bar"); + OverlayableItem overlayable_item2(std::make_shared<Overlayable>("Name2", "overlay://theme")); + overlayable_item2.policies = OverlayableItem::Policy::kProduct; + ASSERT_TRUE(table.SetOverlayable(bar, overlayable_item2, test::GetDiagnostics())); } -TEST(ResourceTableTest, AddDuplicateOverlayableSamePolicyFail) { +TEST(ResourceTableTest, SetOverlayableSameResourcesFail) { ResourceTable table; const ResourceName name = test::ParseNameOrDie("android:string/foo"); - Overlayable overlayable{}; - overlayable.policies = Overlayable::Policy::kProduct; - ASSERT_TRUE(table.SetOverlayable(name, overlayable, test::GetDiagnostics())); + auto overlayable = std::make_shared<Overlayable>("Name", "overlay://theme"); + OverlayableItem overlayable_item(overlayable); + ASSERT_TRUE(table.SetOverlayable(name, overlayable_item, test::GetDiagnostics())); - Overlayable overlayable2{}; - overlayable2.policies = Overlayable::Policy::kProduct; - ASSERT_FALSE(table.SetOverlayable(name, overlayable2, test::GetDiagnostics())); + OverlayableItem overlayable_item2(overlayable); + ASSERT_FALSE(table.SetOverlayable(name, overlayable_item2, test::GetDiagnostics())); } -TEST(ResourceTableTest, AddDuplicateOverlayableDifferentPolicyFail) { +TEST(ResourceTableTest, SetOverlayableSameResourcesDifferentNameFail) { ResourceTable table; const ResourceName name = test::ParseNameOrDie("android:string/foo"); - Overlayable overlayable{}; - overlayable.policies = Overlayable::Policy::kProduct; - ASSERT_TRUE(table.SetOverlayable(name, overlayable, test::GetDiagnostics())); + auto overlayable = std::make_shared<Overlayable>("Name", "overlay://theme"); + OverlayableItem overlayable_item(overlayable); + ASSERT_TRUE(table.SetOverlayable(name, overlayable_item, test::GetDiagnostics())); - Overlayable overlayable2{}; - overlayable2.policies = Overlayable::Policy::kVendor; - ASSERT_FALSE(table.SetOverlayable(name, overlayable2, test::GetDiagnostics())); + auto overlayable2 = std::make_shared<Overlayable>("Other", "overlay://theme"); + OverlayableItem overlayable_item2(overlayable2); + ASSERT_FALSE(table.SetOverlayable(name, overlayable_item2, test::GetDiagnostics())); } TEST(ResourceTableTest, AllowDuplictaeResourcesNames) { diff --git a/tools/aapt2/Resources.proto b/tools/aapt2/Resources.proto index 81a2c2e5cc02..da541be9502b 100644 --- a/tools/aapt2/Resources.proto +++ b/tools/aapt2/Resources.proto @@ -49,6 +49,9 @@ message ResourceTable { // Resource definitions corresponding to an Android package. repeated Package package = 2; + + // The <overlayable> declarations within the resource table. + repeated Overlayable overlayable = 3; } // A package ID in the range [0x00, 0xff]. @@ -133,8 +136,20 @@ message AllowNew { string comment = 2; } -// Represents a declaration that a resource is overayable at runtime. +// Represents a set of overlayable resources. message Overlayable { + // The name of the <overlyabale>. + string name = 1; + + // The location of the <overlyabale> declaration in the source. + Source source = 2; + + // The component responsible for enabling and disabling overlays targeting this <overlayable>. + string actor = 3; +} + +// Represents an overlayable <item> declaration within an <overlayable> tag. +message OverlayableItem { enum Policy { PUBLIC = 0; SYSTEM = 1; @@ -143,14 +158,18 @@ message Overlayable { PRODUCT_SERVICES = 4; } - // Where this declaration was defined in source. + // The location of the <item> declaration in source. Source source = 1; // Any comment associated with the declaration. string comment = 2; - // The policy defined in the overlayable declaration. + // The policy defined by the enclosing <policy> tag of this <item>. repeated Policy policy = 3; + + // The index into overlayable list that points to the <overlayable> tag that contains + // this <item>. + uint32 overlayable_idx = 4; } // An entry ID in the range [0x0000, 0xffff]. @@ -180,7 +199,7 @@ message Entry { AllowNew allow_new = 4; // Whether this resource can be overlaid by a runtime resource overlay (RRO). - Overlayable overlayable = 5; + OverlayableItem overlayable_item = 5; // The set of values defined for this entry, each corresponding to a different // configuration/variant. diff --git a/tools/aapt2/dump/DumpManifest.cpp b/tools/aapt2/dump/DumpManifest.cpp index 11a4074cd3cd..e17fb4783a45 100644 --- a/tools/aapt2/dump/DumpManifest.cpp +++ b/tools/aapt2/dump/DumpManifest.cpp @@ -43,8 +43,10 @@ enum { PERMISSION_ATTR = 0x01010006, EXPORTED_ATTR = 0x01010010, GRANT_URI_PERMISSIONS_ATTR = 0x0101001b, + PRIORITY_ATTR = 0x0101001c, RESOURCE_ATTR = 0x01010025, DEBUGGABLE_ATTR = 0x0101000f, + TARGET_PACKAGE_ATTR = 0x01010021, VALUE_ATTR = 0x01010024, VERSION_CODE_ATTR = 0x0101021b, VERSION_NAME_ATTR = 0x0101021c, @@ -77,8 +79,11 @@ enum { ISGAME_ATTR = 0x10103f4, VERSION_ATTR = 0x01010519, CERT_DIGEST_ATTR = 0x01010548, - REQUIRED_FEATURE_ATTR = 0x1010557, - REQUIRED_NOT_FEATURE_ATTR = 0x1010558, + REQUIRED_FEATURE_ATTR = 0x01010557, + REQUIRED_NOT_FEATURE_ATTR = 0x01010558, + IS_STATIC_ATTR = 0x0101055a, + REQUIRED_SYSTEM_PROPERTY_NAME_ATTR = 0x01010565, + REQUIRED_SYSTEM_PROPERTY_VALUE_ATTR = 0x01010566, COMPILE_SDK_VERSION_ATTR = 0x01010572, COMPILE_SDK_VERSION_CODENAME_ATTR = 0x01010573, VERSION_MAJOR_ATTR = 0x01010577, @@ -1586,6 +1591,44 @@ class OriginalPackage : public ManifestExtractor::Element { } }; + +/** Represents <overlay> elements. **/ +class Overlay : public ManifestExtractor::Element { + public: + Overlay() = default; + const std::string* target_package = nullptr; + int priority; + bool is_static; + const std::string* required_property_name = nullptr; + const std::string* required_property_value = nullptr; + + void Extract(xml::Element* element) override { + target_package = GetAttributeString(FindAttribute(element, TARGET_PACKAGE_ATTR)); + priority = GetAttributeIntegerDefault(FindAttribute(element, PRIORITY_ATTR), 0); + is_static = GetAttributeIntegerDefault(FindAttribute(element, IS_STATIC_ATTR), false) != 0; + required_property_name = GetAttributeString( + FindAttribute(element, REQUIRED_SYSTEM_PROPERTY_NAME_ATTR)); + required_property_value = GetAttributeString( + FindAttribute(element, REQUIRED_SYSTEM_PROPERTY_VALUE_ATTR)); + } + + void Print(text::Printer* printer) override { + printer->Print(StringPrintf("overlay:")); + if (target_package) { + printer->Print(StringPrintf(" targetPackage='%s'", target_package->c_str())); + } + printer->Print(StringPrintf(" priority='%d'", priority)); + printer->Print(StringPrintf(" isStatic='%s'", is_static ? "true" : "false")); + if (required_property_name) { + printer->Print(StringPrintf(" requiredPropertyName='%s'", required_property_name->c_str())); + } + if (required_property_value) { + printer->Print(StringPrintf(" requiredPropertyValue='%s'", required_property_value->c_str())); + } + printer->Print("\n"); + } +}; + /** * Represents <package-verifier> elements. **/ class PackageVerifier : public ManifestExtractor::Element { public: @@ -2166,6 +2209,7 @@ T* ElementCast(ManifestExtractor::Element* element) { {"meta-data", std::is_base_of<MetaData, T>::value}, {"manifest", std::is_base_of<Manifest, T>::value}, {"original-package", std::is_base_of<OriginalPackage, T>::value}, + {"overlay", std::is_base_of<Overlay, T>::value}, {"package-verifier", std::is_base_of<PackageVerifier, T>::value}, {"permission", std::is_base_of<Permission, T>::value}, {"provider", std::is_base_of<Provider, T>::value}, @@ -2215,6 +2259,7 @@ std::unique_ptr<ManifestExtractor::Element> ManifestExtractor::Element::Inflate( {"manifest", &CreateType<Manifest>}, {"meta-data", &CreateType<MetaData>}, {"original-package", &CreateType<OriginalPackage>}, + {"overlay", &CreateType<Overlay>}, {"package-verifier", &CreateType<PackageVerifier>}, {"permission", &CreateType<Permission>}, {"provider", &CreateType<Provider>}, diff --git a/tools/aapt2/format/binary/BinaryResourceParser.cpp b/tools/aapt2/format/binary/BinaryResourceParser.cpp index 61ebd4ee26ca..c496ff0e159b 100644 --- a/tools/aapt2/format/binary/BinaryResourceParser.cpp +++ b/tools/aapt2/format/binary/BinaryResourceParser.cpp @@ -434,6 +434,8 @@ bool BinaryResourceParser::ParseOverlayable(const ResChunk_header* chunk) { return false; } + auto overlayable = std::make_shared<Overlayable>(); + ResChunkPullParser parser(GetChunkData(chunk), GetChunkDataLen(chunk)); while (ResChunkPullParser::IsGoodEvent(parser.Next())) { @@ -441,25 +443,25 @@ bool BinaryResourceParser::ParseOverlayable(const ResChunk_header* chunk) { const ResTable_overlayable_policy_header* policy_header = ConvertTo<ResTable_overlayable_policy_header>(parser.chunk()); - Overlayable::PolicyFlags policies = Overlayable::Policy::kNone; + OverlayableItem::PolicyFlags policies = OverlayableItem::Policy::kNone; if (policy_header->policy_flags & ResTable_overlayable_policy_header::POLICY_PUBLIC) { - policies |= Overlayable::Policy::kPublic; + policies |= OverlayableItem::Policy::kPublic; } if (policy_header->policy_flags & ResTable_overlayable_policy_header::POLICY_SYSTEM_PARTITION) { - policies |= Overlayable::Policy::kSystem; + policies |= OverlayableItem::Policy::kSystem; } if (policy_header->policy_flags & ResTable_overlayable_policy_header::POLICY_VENDOR_PARTITION) { - policies |= Overlayable::Policy::kVendor; + policies |= OverlayableItem::Policy::kVendor; } if (policy_header->policy_flags & ResTable_overlayable_policy_header::POLICY_PRODUCT_PARTITION) { - policies |= Overlayable::Policy::kProduct; + policies |= OverlayableItem::Policy::kProduct; } if (policy_header->policy_flags & ResTable_overlayable_policy_header::POLICY_PRODUCT_SERVICES_PARTITION) { - policies |= Overlayable::Policy::kProductServices; + policies |= OverlayableItem::Policy::kProductServices; } const ResTable_ref* const ref_begin = reinterpret_cast<const ResTable_ref*>( @@ -478,10 +480,10 @@ bool BinaryResourceParser::ParseOverlayable(const ResChunk_header* chunk) { return false; } - Overlayable overlayable{}; - overlayable.source = source_.WithLine(0); - overlayable.policies = policies; - if (!table_->SetOverlayable(iter->second, overlayable, diag_)) { + OverlayableItem overlayable_item(overlayable); + overlayable_item.source = source_.WithLine(0); + overlayable_item.policies = policies; + if (!table_->SetOverlayable(iter->second, overlayable_item, diag_)) { return false; } } diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp index 200e2d468500..931d57b1c08a 100644 --- a/tools/aapt2/format/binary/TableFlattener.cpp +++ b/tools/aapt2/format/binary/TableFlattener.cpp @@ -429,29 +429,29 @@ class PackageFlattener { CHECK(bool(type->id)) << "type must have an ID set when flattening <overlayable>"; for (auto& entry : type->entries) { CHECK(bool(type->id)) << "entry must have an ID set when flattening <overlayable>"; - if (!entry->overlayable) { + if (!entry->overlayable_item) { continue; } - Overlayable overlayable = entry->overlayable.value(); - uint32_t policy_flags = Overlayable::Policy::kNone; - if (overlayable.policies & Overlayable::Policy::kPublic) { + OverlayableItem& overlayable = entry->overlayable_item.value(); + uint32_t policy_flags = OverlayableItem::Policy::kNone; + if (overlayable.policies & OverlayableItem::Policy::kPublic) { policy_flags |= ResTable_overlayable_policy_header::POLICY_PUBLIC; } - if (overlayable.policies & Overlayable::Policy::kSystem) { + if (overlayable.policies & OverlayableItem::Policy::kSystem) { policy_flags |= ResTable_overlayable_policy_header::POLICY_SYSTEM_PARTITION; } - if (overlayable.policies & Overlayable::Policy::kVendor) { + if (overlayable.policies & OverlayableItem::Policy::kVendor) { policy_flags |= ResTable_overlayable_policy_header::POLICY_VENDOR_PARTITION; } - if (overlayable.policies & Overlayable::Policy::kProduct) { + if (overlayable.policies & OverlayableItem::Policy::kProduct) { policy_flags |= ResTable_overlayable_policy_header::POLICY_PRODUCT_PARTITION; } - if (overlayable.policies & Overlayable::Policy::kProductServices) { + if (overlayable.policies & OverlayableItem::Policy::kProductServices) { policy_flags |= ResTable_overlayable_policy_header::POLICY_PRODUCT_SERVICES_PARTITION; } - if (overlayable.policies == Overlayable::Policy::kNone) { + if (overlayable.policies == OverlayableItem::Policy::kNone) { // Encode overlayable entries defined without a policy as publicly overlayable policy_flags |= ResTable_overlayable_policy_header::POLICY_PUBLIC; } diff --git a/tools/aapt2/format/binary/TableFlattener_test.cpp b/tools/aapt2/format/binary/TableFlattener_test.cpp index e99ab1f37761..a5fb6fd6d7aa 100644 --- a/tools/aapt2/format/binary/TableFlattener_test.cpp +++ b/tools/aapt2/format/binary/TableFlattener_test.cpp @@ -628,17 +628,17 @@ TEST_F(TableFlattenerTest, ObfuscatingResourceNamesWithWhitelistSucceeds) { } TEST_F(TableFlattenerTest, FlattenOverlayable) { - Overlayable overlayable{}; - overlayable.policies |= Overlayable::Policy::kProduct; - overlayable.policies |= Overlayable::Policy::kSystem; - overlayable.policies |= Overlayable::Policy::kVendor; + OverlayableItem overlayable_item(std::make_shared<Overlayable>("TestName", "overlay://theme")); + overlayable_item.policies |= OverlayableItem::Policy::kProduct; + overlayable_item.policies |= OverlayableItem::Policy::kSystem; + overlayable_item.policies |= OverlayableItem::Policy::kVendor; std::string name = "com.app.test:integer/overlayable"; std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() .SetPackageId("com.app.test", 0x7f) .AddSimple(name, ResourceId(0x7f020000)) - .SetOverlayable(name, overlayable) + .SetOverlayable(name, overlayable_item) .Build(); ResourceTable output_table; @@ -647,45 +647,46 @@ TEST_F(TableFlattenerTest, FlattenOverlayable) { auto search_result = output_table.FindResource(test::ParseNameOrDie(name)); ASSERT_TRUE(search_result); ASSERT_THAT(search_result.value().entry, NotNull()); - ASSERT_TRUE(search_result.value().entry->overlayable); - Overlayable& result_overlayable = search_result.value().entry->overlayable.value(); - EXPECT_EQ(result_overlayable.policies, Overlayable::Policy::kSystem - | Overlayable::Policy::kVendor - | Overlayable::Policy::kProduct); + ASSERT_TRUE(search_result.value().entry->overlayable_item); + OverlayableItem& result_overlayable_item = search_result.value().entry->overlayable_item.value(); + EXPECT_EQ(result_overlayable_item.policies, OverlayableItem::Policy::kSystem + | OverlayableItem::Policy::kVendor + | OverlayableItem::Policy::kProduct); } TEST_F(TableFlattenerTest, FlattenMultipleOverlayablePolicies) { - std::string name_zero = "com.app.test:integer/overlayable_zero"; - Overlayable overlayable_zero{}; - overlayable_zero.policies |= Overlayable::Policy::kProduct; - overlayable_zero.policies |= Overlayable::Policy::kSystem; - overlayable_zero.policies |= Overlayable::Policy::kProductServices; - - std::string name_one = "com.app.test:integer/overlayable_one"; - Overlayable overlayable_one{}; - overlayable_one.policies |= Overlayable::Policy::kPublic; - overlayable_one.policies |= Overlayable::Policy::kProductServices; - - std::string name_two = "com.app.test:integer/overlayable_two"; - Overlayable overlayable_two{}; - overlayable_two.policies |= Overlayable::Policy::kProduct; - overlayable_two.policies |= Overlayable::Policy::kSystem; - overlayable_two.policies |= Overlayable::Policy::kVendor; - - std::string name_three = "com.app.test:integer/overlayable_three"; - Overlayable overlayable_three{}; + auto overlayable = std::make_shared<Overlayable>("TestName", "overlay://theme"); + std::string name_zero = "com.app.test:integer/overlayable_zero_item"; + OverlayableItem overlayable_zero_item(overlayable); + overlayable_zero_item.policies |= OverlayableItem::Policy::kProduct; + overlayable_zero_item.policies |= OverlayableItem::Policy::kSystem; + overlayable_zero_item.policies |= OverlayableItem::Policy::kProductServices; + + std::string name_one = "com.app.test:integer/overlayable_one_item"; + OverlayableItem overlayable_one_item(overlayable); + overlayable_one_item.policies |= OverlayableItem::Policy::kPublic; + overlayable_one_item.policies |= OverlayableItem::Policy::kProductServices; + + std::string name_two = "com.app.test:integer/overlayable_two_item"; + OverlayableItem overlayable_two_item(overlayable); + overlayable_two_item.policies |= OverlayableItem::Policy::kProduct; + overlayable_two_item.policies |= OverlayableItem::Policy::kSystem; + overlayable_two_item.policies |= OverlayableItem::Policy::kVendor; + + std::string name_three = "com.app.test:integer/overlayable_three_item"; + OverlayableItem overlayable_three_item(overlayable); std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() .SetPackageId("com.app.test", 0x7f) .AddSimple(name_zero, ResourceId(0x7f020000)) - .SetOverlayable(name_zero, overlayable_zero) + .SetOverlayable(name_zero, overlayable_zero_item) .AddSimple(name_one, ResourceId(0x7f020001)) - .SetOverlayable(name_one, overlayable_one) + .SetOverlayable(name_one, overlayable_one_item) .AddSimple(name_two, ResourceId(0x7f020002)) - .SetOverlayable(name_two, overlayable_two) + .SetOverlayable(name_two, overlayable_two_item) .AddSimple(name_three, ResourceId(0x7f020003)) - .SetOverlayable(name_three, overlayable_three) + .SetOverlayable(name_three, overlayable_three_item) .Build(); ResourceTable output_table; @@ -694,35 +695,35 @@ TEST_F(TableFlattenerTest, FlattenMultipleOverlayablePolicies) { auto search_result = output_table.FindResource(test::ParseNameOrDie(name_zero)); ASSERT_TRUE(search_result); ASSERT_THAT(search_result.value().entry, NotNull()); - ASSERT_TRUE(search_result.value().entry->overlayable); - Overlayable& result_overlayable = search_result.value().entry->overlayable.value(); - EXPECT_EQ(result_overlayable.policies, Overlayable::Policy::kSystem - | Overlayable::Policy::kProduct - | Overlayable::Policy::kProductServices); + ASSERT_TRUE(search_result.value().entry->overlayable_item); + OverlayableItem& overlayable_item = search_result.value().entry->overlayable_item.value(); + EXPECT_EQ(overlayable_item.policies, OverlayableItem::Policy::kSystem + | OverlayableItem::Policy::kProduct + | OverlayableItem::Policy::kProductServices); search_result = output_table.FindResource(test::ParseNameOrDie(name_one)); ASSERT_TRUE(search_result); ASSERT_THAT(search_result.value().entry, NotNull()); - ASSERT_TRUE(search_result.value().entry->overlayable); - result_overlayable = search_result.value().entry->overlayable.value(); - EXPECT_EQ(result_overlayable.policies, Overlayable::Policy::kPublic - | Overlayable::Policy::kProductServices); + ASSERT_TRUE(search_result.value().entry->overlayable_item); + overlayable_item = search_result.value().entry->overlayable_item.value(); + EXPECT_EQ(overlayable_item.policies, OverlayableItem::Policy::kPublic + | OverlayableItem::Policy::kProductServices); search_result = output_table.FindResource(test::ParseNameOrDie(name_two)); ASSERT_TRUE(search_result); ASSERT_THAT(search_result.value().entry, NotNull()); - ASSERT_TRUE(search_result.value().entry->overlayable); - result_overlayable = search_result.value().entry->overlayable.value(); - EXPECT_EQ(result_overlayable.policies, Overlayable::Policy::kSystem - | Overlayable::Policy::kProduct - | Overlayable::Policy::kVendor); + ASSERT_TRUE(search_result.value().entry->overlayable_item); + overlayable_item = search_result.value().entry->overlayable_item.value(); + EXPECT_EQ(overlayable_item.policies, OverlayableItem::Policy::kSystem + | OverlayableItem::Policy::kProduct + | OverlayableItem::Policy::kVendor); search_result = output_table.FindResource(test::ParseNameOrDie(name_three)); ASSERT_TRUE(search_result); ASSERT_THAT(search_result.value().entry, NotNull()); - ASSERT_TRUE(search_result.value().entry->overlayable); - result_overlayable = search_result.value().entry->overlayable.value(); - EXPECT_EQ(result_overlayable.policies, Overlayable::Policy::kPublic); + ASSERT_TRUE(search_result.value().entry->overlayable_item); + overlayable_item = search_result.value().entry->overlayable_item.value(); + EXPECT_EQ(overlayable_item.policies, OverlayableItem::Policy::kPublic); } } // namespace aapt diff --git a/tools/aapt2/format/proto/ProtoDeserialize.cpp b/tools/aapt2/format/proto/ProtoDeserialize.cpp index cf2ab0f45ad6..6b5746d63bf8 100644 --- a/tools/aapt2/format/proto/ProtoDeserialize.cpp +++ b/tools/aapt2/format/proto/ProtoDeserialize.cpp @@ -373,9 +373,44 @@ static Visibility::Level DeserializeVisibilityFromPb(const pb::Visibility::Level return Visibility::Level::kUndefined; } +bool DeserializeOverlayableItemFromPb(const pb::OverlayableItem& pb_overlayable, + const android::ResStringPool& src_pool, + OverlayableItem* out_overlayable, std::string* out_error) { + for (const int policy : pb_overlayable.policy()) { + switch (policy) { + case pb::OverlayableItem::PUBLIC: + out_overlayable->policies |= OverlayableItem::Policy::kPublic; + break; + case pb::OverlayableItem::SYSTEM: + out_overlayable->policies |= OverlayableItem::Policy::kSystem; + break; + case pb::OverlayableItem::VENDOR: + out_overlayable->policies |= OverlayableItem::Policy::kVendor; + break; + case pb::OverlayableItem::PRODUCT: + out_overlayable->policies |= OverlayableItem::Policy::kProduct; + break; + case pb::OverlayableItem::PRODUCT_SERVICES: + out_overlayable->policies |= OverlayableItem::Policy::kProductServices; + break; + default: + *out_error = "unknown overlayable policy"; + return false; + } + } + + if (pb_overlayable.has_source()) { + DeserializeSourceFromPb(pb_overlayable.source(), src_pool, &out_overlayable->source); + } + + out_overlayable->comment = pb_overlayable.comment(); + return true; +} + static bool DeserializePackageFromPb(const pb::Package& pb_package, const ResStringPool& src_pool, - io::IFileCollection* files, ResourceTable* out_table, - std::string* out_error) { + io::IFileCollection* files, + const std::vector<std::shared_ptr<Overlayable>>& overlayables, + ResourceTable* out_table, std::string* out_error) { Maybe<uint8_t> id; if (pb_package.has_package_id()) { id = static_cast<uint8_t>(pb_package.package_id().id()); @@ -437,39 +472,22 @@ static bool DeserializePackageFromPb(const pb::Package& pb_package, const ResStr entry->allow_new = std::move(allow_new); } - if (pb_entry.has_overlayable()) { - Overlayable overlayable{}; - - const pb::Overlayable& pb_overlayable = pb_entry.overlayable(); - for (const int policy : pb_overlayable.policy()) { - switch (policy) { - case pb::Overlayable::PUBLIC: - overlayable.policies |= Overlayable::Policy::kPublic; - break; - case pb::Overlayable::SYSTEM: - overlayable.policies |= Overlayable::Policy::kSystem; - break; - case pb::Overlayable::VENDOR: - overlayable.policies |= Overlayable::Policy::kVendor; - break; - case pb::Overlayable::PRODUCT: - overlayable.policies |= Overlayable::Policy::kProduct; - break; - case pb::Overlayable::PRODUCT_SERVICES: - overlayable.policies |= Overlayable::Policy::kProductServices; - break; - default: - *out_error = "unknown overlayable policy"; - return false; - } + if (pb_entry.has_overlayable_item()) { + // Find the overlayable to which this item belongs + pb::OverlayableItem pb_overlayable_item = pb_entry.overlayable_item(); + if (pb_overlayable_item.overlayable_idx() >= overlayables.size()) { + *out_error = android::base::StringPrintf("invalid overlayable_idx value %d", + pb_overlayable_item.overlayable_idx()); + return false; } - if (pb_overlayable.has_source()) { - DeserializeSourceFromPb(pb_overlayable.source(), src_pool, &overlayable.source); + OverlayableItem overlayable_item(overlayables[pb_overlayable_item.overlayable_idx()]); + if (!DeserializeOverlayableItemFromPb(pb_overlayable_item, src_pool, &overlayable_item, + out_error)) { + return false; } - overlayable.comment = pb_overlayable.comment(); - entry->overlayable = overlayable; + entry->overlayable_item = std::move(overlayable_item); } ResourceId resid(pb_package.package_id().id(), pb_type.type_id().id(), @@ -522,8 +540,19 @@ bool DeserializeTableFromPb(const pb::ResourceTable& pb_table, io::IFileCollecti } } + // Deserialize the overlayable groups of the table + std::vector<std::shared_ptr<Overlayable>> overlayables; + for (const pb::Overlayable& pb_overlayable : pb_table.overlayable()) { + auto group = std::make_shared<Overlayable>(pb_overlayable.name(), pb_overlayable.actor()); + if (pb_overlayable.has_source()) { + DeserializeSourceFromPb(pb_overlayable.source(), source_pool, &group->source); + } + overlayables.push_back(group); + } + for (const pb::Package& pb_package : pb_table.package()) { - if (!DeserializePackageFromPb(pb_package, source_pool, files, out_table, out_error)) { + if (!DeserializePackageFromPb(pb_package, source_pool, files, overlayables, out_table, + out_error)) { return false; } } diff --git a/tools/aapt2/format/proto/ProtoSerialize.cpp b/tools/aapt2/format/proto/ProtoSerialize.cpp index 70bf8684f8a8..76fbb464b62a 100644 --- a/tools/aapt2/format/proto/ProtoSerialize.cpp +++ b/tools/aapt2/format/proto/ProtoSerialize.cpp @@ -272,9 +272,57 @@ void SerializeConfig(const ConfigDescription& config, pb::Configuration* out_pb_ out_pb_config->set_sdk_version(config.sdkVersion); } +static void SerializeOverlayableItemToPb(const OverlayableItem& overlayable_item, + std::vector<Overlayable*>& serialized_overlayables, + StringPool* source_pool, pb::Entry* pb_entry, + pb::ResourceTable* pb_table) { + // Retrieve the index of the overlayable in the list of groups that have already been serialized. + size_t i; + for (i = 0 ; i < serialized_overlayables.size(); i++) { + if (overlayable_item.overlayable.get() == serialized_overlayables[i]) { + break; + } + } + + // Serialize the overlayable if it has not been serialized already. + if (i == serialized_overlayables.size()) { + serialized_overlayables.push_back(overlayable_item.overlayable.get()); + pb::Overlayable* pb_overlayable = pb_table->add_overlayable(); + pb_overlayable->set_name(overlayable_item.overlayable->name); + pb_overlayable->set_actor(overlayable_item.overlayable->actor); + SerializeSourceToPb(overlayable_item.overlayable->source, source_pool, + pb_overlayable->mutable_source()); + } + + pb::OverlayableItem* pb_overlayable_item = pb_entry->mutable_overlayable_item(); + pb_overlayable_item->set_overlayable_idx(i); + + if (overlayable_item.policies & OverlayableItem::Policy::kPublic) { + pb_overlayable_item->add_policy(pb::OverlayableItem::PUBLIC); + } + if (overlayable_item.policies & OverlayableItem::Policy::kProduct) { + pb_overlayable_item->add_policy(pb::OverlayableItem::PRODUCT); + } + if (overlayable_item.policies & OverlayableItem::Policy::kProductServices) { + pb_overlayable_item->add_policy(pb::OverlayableItem::PRODUCT_SERVICES); + } + if (overlayable_item.policies & OverlayableItem::Policy::kSystem) { + pb_overlayable_item->add_policy(pb::OverlayableItem::SYSTEM); + } + if (overlayable_item.policies & OverlayableItem::Policy::kVendor) { + pb_overlayable_item->add_policy(pb::OverlayableItem::VENDOR); + } + + SerializeSourceToPb(overlayable_item.source, source_pool, + pb_overlayable_item->mutable_source()); + pb_overlayable_item->set_comment(overlayable_item.comment); +} + void SerializeTableToPb(const ResourceTable& table, pb::ResourceTable* out_table, IDiagnostics* diag) { StringPool source_pool; + + std::vector<Overlayable*> overlayables; for (const std::unique_ptr<ResourceTablePackage>& package : table.packages) { pb::Package* pb_package = out_table->add_package(); if (package->id) { @@ -310,29 +358,9 @@ void SerializeTableToPb(const ResourceTable& table, pb::ResourceTable* out_table pb_allow_new->set_comment(entry->allow_new.value().comment); } - if (entry->overlayable) { - pb::Overlayable* pb_overlayable = pb_entry->mutable_overlayable(); - - Overlayable overlayable = entry->overlayable.value(); - if (overlayable.policies & Overlayable::Policy::kPublic) { - pb_overlayable->add_policy(pb::Overlayable::PUBLIC); - } - if (overlayable.policies & Overlayable::Policy::kProduct) { - pb_overlayable->add_policy(pb::Overlayable::PRODUCT); - } - if (overlayable.policies & Overlayable::Policy::kProductServices) { - pb_overlayable->add_policy(pb::Overlayable::PRODUCT_SERVICES); - } - if (overlayable.policies & Overlayable::Policy::kSystem) { - pb_overlayable->add_policy(pb::Overlayable::SYSTEM); - } - if (overlayable.policies & Overlayable::Policy::kVendor) { - pb_overlayable->add_policy(pb::Overlayable::VENDOR); - } - - SerializeSourceToPb(overlayable.source, &source_pool, - pb_overlayable->mutable_source()); - pb_overlayable->set_comment(overlayable.comment); + if (entry->overlayable_item) { + SerializeOverlayableItemToPb(entry->overlayable_item.value(), overlayables, &source_pool, + pb_entry, out_table); } for (const std::unique_ptr<ResourceConfigValue>& config_value : entry->values) { diff --git a/tools/aapt2/format/proto/ProtoSerialize_test.cpp b/tools/aapt2/format/proto/ProtoSerialize_test.cpp index fb913f409f52..4a3c1b86236e 100644 --- a/tools/aapt2/format/proto/ProtoSerialize_test.cpp +++ b/tools/aapt2/format/proto/ProtoSerialize_test.cpp @@ -93,8 +93,11 @@ TEST(ProtoSerializeTest, SerializeSinglePackage) { util::make_unique<Reference>(expected_ref), context->GetDiagnostics())); // Make an overlayable resource. + OverlayableItem overlayable_item(std::make_shared<Overlayable>( + "OverlayableName", "overlay://theme", Source("res/values/overlayable.xml", 40))); + overlayable_item.source = Source("res/values/overlayable.xml", 42); ASSERT_TRUE(table->SetOverlayable(test::ParseNameOrDie("com.app.a:integer/overlayable"), - Overlayable{}, test::GetDiagnostics())); + overlayable_item, test::GetDiagnostics())); pb::ResourceTable pb_table; SerializeTableToPb(*table, &pb_table, context->GetDiagnostics()); @@ -160,9 +163,15 @@ TEST(ProtoSerializeTest, SerializeSinglePackage) { new_table.FindResource(test::ParseNameOrDie("com.app.a:integer/overlayable")); ASSERT_TRUE(search_result); ASSERT_THAT(search_result.value().entry, NotNull()); - ASSERT_TRUE(search_result.value().entry->overlayable); - EXPECT_THAT(search_result.value().entry->overlayable.value().policies, - Eq(Overlayable::Policy::kNone)); + ASSERT_TRUE(search_result.value().entry->overlayable_item); + OverlayableItem& result_overlayable_item = search_result.value().entry->overlayable_item.value(); + EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("OverlayableName")); + EXPECT_THAT(result_overlayable_item.overlayable->actor, Eq("overlay://theme")); + EXPECT_THAT(result_overlayable_item.overlayable->source.path, Eq("res/values/overlayable.xml")); + EXPECT_THAT(result_overlayable_item.overlayable->source.line, Eq(40)); + EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kNone)); + EXPECT_THAT(result_overlayable_item.source.path, Eq("res/values/overlayable.xml")); + EXPECT_THAT(result_overlayable_item.source.line, Eq(42)); } TEST(ProtoSerializeTest, SerializeAndDeserializeXml) { @@ -503,26 +512,31 @@ TEST(ProtoSerializeTest, SerializeDeserializeConfiguration) { } TEST(ProtoSerializeTest, SerializeAndDeserializeOverlayable) { - Overlayable overlayable_foo{}; - overlayable_foo.policies |= Overlayable::Policy::kSystem; - overlayable_foo.policies |= Overlayable::Policy::kProduct; + OverlayableItem overlayable_item_foo(std::make_shared<Overlayable>( + "CustomizableResources", "overlay://customization")); + overlayable_item_foo.policies |= OverlayableItem::Policy::kSystem; + overlayable_item_foo.policies |= OverlayableItem::Policy::kProduct; - Overlayable overlayable_bar{}; - overlayable_bar.policies |= Overlayable::Policy::kProductServices; - overlayable_bar.policies |= Overlayable::Policy::kVendor; + OverlayableItem overlayable_item_bar(std::make_shared<Overlayable>( + "TaskBar", "overlay://theme")); + overlayable_item_bar.policies |= OverlayableItem::Policy::kProductServices; + overlayable_item_bar.policies |= OverlayableItem::Policy::kVendor; - Overlayable overlayable_baz{}; - overlayable_baz.policies |= Overlayable::Policy::kPublic; + OverlayableItem overlayable_item_baz(std::make_shared<Overlayable>( + "FontPack", "overlay://theme")); + overlayable_item_baz.policies |= OverlayableItem::Policy::kPublic; - Overlayable overlayable_biz{}; + OverlayableItem overlayable_item_biz(std::make_shared<Overlayable>( + "Other", "overlay://customization")); + overlayable_item_biz.comment ="comment"; std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .SetOverlayable("com.app.a:bool/foo", overlayable_foo) - .SetOverlayable("com.app.a:bool/bar", overlayable_bar) - .SetOverlayable("com.app.a:bool/baz", overlayable_baz) - .SetOverlayable("com.app.a:bool/biz", overlayable_biz) + .SetOverlayable("com.app.a:bool/foo", overlayable_item_foo) + .SetOverlayable("com.app.a:bool/bar", overlayable_item_bar) + .SetOverlayable("com.app.a:bool/baz", overlayable_item_baz) + .SetOverlayable("com.app.a:bool/biz", overlayable_item_biz) .AddValue("com.app.a:bool/fiz", ResourceUtils::TryParseBool("true")) .Build(); @@ -538,33 +552,41 @@ TEST(ProtoSerializeTest, SerializeAndDeserializeOverlayable) { Maybe<ResourceTable::SearchResult> search_result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/foo")); ASSERT_TRUE(search_result); - ASSERT_TRUE(search_result.value().entry->overlayable); - Overlayable result_overlayable = search_result.value().entry->overlayable.value(); - EXPECT_THAT(result_overlayable.policies, Eq(Overlayable::Policy::kSystem - | Overlayable::Policy::kProduct)); + ASSERT_TRUE(search_result.value().entry->overlayable_item); + OverlayableItem& overlayable_item = search_result.value().entry->overlayable_item.value(); + EXPECT_THAT(overlayable_item.overlayable->name, Eq("CustomizableResources")); + EXPECT_THAT(overlayable_item.overlayable->actor, Eq("overlay://customization")); + EXPECT_THAT(overlayable_item.policies, Eq(OverlayableItem::Policy::kSystem + | OverlayableItem::Policy::kProduct)); search_result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/bar")); ASSERT_TRUE(search_result); - ASSERT_TRUE(search_result.value().entry->overlayable); - result_overlayable = search_result.value().entry->overlayable.value(); - EXPECT_THAT(result_overlayable.policies, Eq(Overlayable::Policy::kProductServices - | Overlayable::Policy::kVendor)); + ASSERT_TRUE(search_result.value().entry->overlayable_item); + overlayable_item = search_result.value().entry->overlayable_item.value(); + EXPECT_THAT(overlayable_item.overlayable->name, Eq("TaskBar")); + EXPECT_THAT(overlayable_item.overlayable->actor, Eq("overlay://theme")); + EXPECT_THAT(overlayable_item.policies, Eq(OverlayableItem::Policy::kProductServices + | OverlayableItem::Policy::kVendor)); search_result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/baz")); ASSERT_TRUE(search_result); - ASSERT_TRUE(search_result.value().entry->overlayable); - result_overlayable = search_result.value().entry->overlayable.value(); - EXPECT_THAT(result_overlayable.policies, Overlayable::Policy::kPublic); + ASSERT_TRUE(search_result.value().entry->overlayable_item); + overlayable_item = search_result.value().entry->overlayable_item.value(); + EXPECT_THAT(overlayable_item.overlayable->name, Eq("FontPack")); + EXPECT_THAT(overlayable_item.overlayable->actor, Eq("overlay://theme")); + EXPECT_THAT(overlayable_item.policies, Eq(OverlayableItem::Policy::kPublic)); search_result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/biz")); ASSERT_TRUE(search_result); - ASSERT_TRUE(search_result.value().entry->overlayable); - result_overlayable = search_result.value().entry->overlayable.value(); - EXPECT_THAT(result_overlayable.policies, Overlayable::Policy::kNone); + ASSERT_TRUE(search_result.value().entry->overlayable_item); + overlayable_item = search_result.value().entry->overlayable_item.value(); + EXPECT_THAT(overlayable_item.overlayable->name, Eq("Other")); + EXPECT_THAT(overlayable_item.policies, Eq(OverlayableItem::Policy::kNone)); + EXPECT_THAT(overlayable_item.comment, Eq("comment")); search_result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/fiz")); ASSERT_TRUE(search_result); - ASSERT_FALSE(search_result.value().entry->overlayable); + ASSERT_FALSE(search_result.value().entry->overlayable_item); } } // namespace aapt diff --git a/tools/aapt2/link/ReferenceLinker.cpp b/tools/aapt2/link/ReferenceLinker.cpp index 8cbc03738677..c2340ba65e38 100644 --- a/tools/aapt2/link/ReferenceLinker.cpp +++ b/tools/aapt2/link/ReferenceLinker.cpp @@ -374,8 +374,8 @@ bool ReferenceLinker::Consume(IAaptContext* context, ResourceTable* table) { } // Ensure that definitions for values declared as overlayable exist - if (entry->overlayable && entry->values.empty()) { - context->GetDiagnostics()->Error(DiagMessage(entry->overlayable.value().source) + if (entry->overlayable_item && entry->values.empty()) { + context->GetDiagnostics()->Error(DiagMessage(entry->overlayable_item.value().source) << "no definition for overlayable symbol '" << name << "'"); error = true; diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp index 22e1723591a8..cc9fed554350 100644 --- a/tools/aapt2/link/TableMerger.cpp +++ b/tools/aapt2/link/TableMerger.cpp @@ -134,18 +134,18 @@ static bool MergeEntry(IAaptContext* context, const Source& src, dst_entry->allow_new = std::move(src_entry->allow_new); } - if (src_entry->overlayable) { - if (dst_entry->overlayable) { + if (src_entry->overlayable_item) { + if (dst_entry->overlayable_item) { // Do not allow a resource with an overlayable declaration to have that overlayable // declaration redefined - context->GetDiagnostics()->Error(DiagMessage(src_entry->overlayable.value().source) + context->GetDiagnostics()->Error(DiagMessage(src_entry->overlayable_item.value().source) << "duplicate overlayable declaration for resource '" << src_entry->name << "'"); - context->GetDiagnostics()->Error(DiagMessage(dst_entry->overlayable.value().source) + context->GetDiagnostics()->Error(DiagMessage(dst_entry->overlayable_item.value().source) << "previous declaration here"); return false; } else { - dst_entry->overlayable = std::move(src_entry->overlayable); + dst_entry->overlayable_item = std::move(src_entry->overlayable_item); } } diff --git a/tools/aapt2/link/TableMerger_test.cpp b/tools/aapt2/link/TableMerger_test.cpp index 17b2a83bad04..921d634e583e 100644 --- a/tools/aapt2/link/TableMerger_test.cpp +++ b/tools/aapt2/link/TableMerger_test.cpp @@ -437,14 +437,16 @@ TEST_F(TableMergerTest, OverlaidStyleablesAndStylesShouldBeMerged) { } TEST_F(TableMergerTest, SetOverlayable) { - Overlayable overlayable{}; - overlayable.policies |= Overlayable::Policy::kProduct; - overlayable.policies |= Overlayable::Policy::kVendor; + auto overlayable = std::make_shared<Overlayable>("CustomizableResources", + "overlay://customization"); + OverlayableItem overlayable_item(overlayable); + overlayable_item.policies |= OverlayableItem::Policy::kProduct; + overlayable_item.policies |= OverlayableItem::Policy::kVendor; std::unique_ptr<ResourceTable> table_a = test::ResourceTableBuilder() .SetPackageId("com.app.a", 0x7f) - .SetOverlayable("bool/foo", overlayable) + .SetOverlayable("bool/foo", overlayable_item) .Build(); std::unique_ptr<ResourceTable> table_b = @@ -463,26 +465,30 @@ TEST_F(TableMergerTest, SetOverlayable) { const ResourceName name = test::ParseNameOrDie("com.app.a:bool/foo"); Maybe<ResourceTable::SearchResult> search_result = final_table.FindResource(name); ASSERT_TRUE(search_result); - ASSERT_TRUE(search_result.value().entry->overlayable); - Overlayable& result_overlayable = search_result.value().entry->overlayable.value(); - EXPECT_THAT(result_overlayable.policies, Eq(Overlayable::Policy::kProduct - | Overlayable::Policy::kVendor)); + ASSERT_TRUE(search_result.value().entry->overlayable_item); + OverlayableItem& result_overlayable_item = search_result.value().entry->overlayable_item.value(); + EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("CustomizableResources")); + EXPECT_THAT(result_overlayable_item.overlayable->actor, Eq("overlay://customization")); + EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kProduct + | OverlayableItem::Policy::kVendor)); } TEST_F(TableMergerTest, SetOverlayableLater) { + auto overlayable = std::make_shared<Overlayable>("CustomizableResources", + "overlay://customization"); std::unique_ptr<ResourceTable> table_a = test::ResourceTableBuilder() .SetPackageId("com.app.a", 0x7f) .AddSimple("bool/foo") .Build(); - Overlayable overlayable{}; - overlayable.policies |= Overlayable::Policy::kPublic; - overlayable.policies |= Overlayable::Policy::kProductServices; + OverlayableItem overlayable_item(overlayable); + overlayable_item.policies |= OverlayableItem::Policy::kPublic; + overlayable_item.policies |= OverlayableItem::Policy::kProductServices; std::unique_ptr<ResourceTable> table_b = test::ResourceTableBuilder() .SetPackageId("com.app.a", 0x7f) - .SetOverlayable("bool/foo", overlayable) + .SetOverlayable("bool/foo", overlayable_item) .Build(); ResourceTable final_table; @@ -495,27 +501,33 @@ TEST_F(TableMergerTest, SetOverlayableLater) { const ResourceName name = test::ParseNameOrDie("com.app.a:bool/foo"); Maybe<ResourceTable::SearchResult> search_result = final_table.FindResource(name); ASSERT_TRUE(search_result); - ASSERT_TRUE(search_result.value().entry->overlayable); - Overlayable& result_overlayable = search_result.value().entry->overlayable.value(); - EXPECT_THAT(result_overlayable.policies, Eq(Overlayable::Policy::kPublic - | Overlayable::Policy::kProductServices)); + ASSERT_TRUE(search_result.value().entry->overlayable_item); + OverlayableItem& result_overlayable_item = search_result.value().entry->overlayable_item.value(); + EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("CustomizableResources")); + EXPECT_THAT(result_overlayable_item.overlayable->actor, Eq("overlay://customization")); + EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kPublic + | OverlayableItem::Policy::kProductServices)); } -TEST_F(TableMergerTest, SetOverlayableSamePolicesFail) { - Overlayable overlayable_first{}; - overlayable_first.policies |= Overlayable::Policy::kProduct; +TEST_F(TableMergerTest, SameResourceDifferentNameFail) { + auto overlayable_first = std::make_shared<Overlayable>("CustomizableResources", + "overlay://customization"); + OverlayableItem overlayable_item_first(overlayable_first); + overlayable_item_first.policies |= OverlayableItem::Policy::kProduct; std::unique_ptr<ResourceTable> table_a = test::ResourceTableBuilder() .SetPackageId("com.app.a", 0x7f) - .SetOverlayable("bool/foo", overlayable_first) + .SetOverlayable("bool/foo", overlayable_item_first) .Build(); - Overlayable overlayable_second{}; - overlayable_second.policies |= Overlayable::Policy::kProduct; + auto overlayable_second = std::make_shared<Overlayable>("ThemeResources", + "overlay://theme"); + OverlayableItem overlayable_item_second(overlayable_second); + overlayable_item_second.policies |= OverlayableItem::Policy::kProduct; std::unique_ptr<ResourceTable> table_b = test::ResourceTableBuilder() .SetPackageId("com.app.a", 0x7f) - .SetOverlayable("bool/foo", overlayable_second) + .SetOverlayable("bool/foo", overlayable_item_second) .Build(); ResourceTable final_table; @@ -526,21 +538,24 @@ TEST_F(TableMergerTest, SetOverlayableSamePolicesFail) { ASSERT_FALSE(merger.Merge({}, table_b.get(), false /*overlay*/)); } -TEST_F(TableMergerTest, SetOverlayableDifferentPolicesFail) { - Overlayable overlayable_first{}; - overlayable_first.policies |= Overlayable::Policy::kVendor; +TEST_F(TableMergerTest, SameResourceSameNameFail) { + auto overlayable = std::make_shared<Overlayable>("CustomizableResources", + "overlay://customization"); + + OverlayableItem overlayable_item_first(overlayable); + overlayable_item_first.policies |= OverlayableItem::Policy::kProduct; std::unique_ptr<ResourceTable> table_a = test::ResourceTableBuilder() .SetPackageId("com.app.a", 0x7f) - .SetOverlayable("bool/foo",overlayable_first) + .SetOverlayable("bool/foo", overlayable_item_first) .Build(); - Overlayable overlayable_second{}; - overlayable_second.policies |= Overlayable::Policy::kProduct; + OverlayableItem overlayable_item_second(overlayable); + overlayable_item_second.policies |= OverlayableItem::Policy::kSystem; std::unique_ptr<ResourceTable> table_b = test::ResourceTableBuilder() .SetPackageId("com.app.a", 0x7f) - .SetOverlayable("bool/foo", overlayable_second) + .SetOverlayable("bool/foo", overlayable_item_second) .Build(); ResourceTable final_table; diff --git a/tools/aapt2/split/TableSplitter.cpp b/tools/aapt2/split/TableSplitter.cpp index 9c5b5d36b798..24cd5ba302ea 100644 --- a/tools/aapt2/split/TableSplitter.cpp +++ b/tools/aapt2/split/TableSplitter.cpp @@ -248,7 +248,7 @@ void TableSplitter::SplitTable(ResourceTable* original_table) { if (!split_entry->id) { split_entry->id = entry->id; split_entry->visibility = entry->visibility; - split_entry->overlayable = entry->overlayable; + split_entry->overlayable_item = entry->overlayable_item; } // Copy the selected values into the new Split Entry. diff --git a/tools/aapt2/test/Builders.cpp b/tools/aapt2/test/Builders.cpp index 884ec38290f8..9a93f2a7476c 100644 --- a/tools/aapt2/test/Builders.cpp +++ b/tools/aapt2/test/Builders.cpp @@ -136,7 +136,7 @@ ResourceTableBuilder& ResourceTableBuilder::SetSymbolState(const StringPiece& na } ResourceTableBuilder& ResourceTableBuilder::SetOverlayable(const StringPiece& name, - const Overlayable& overlayable) { + const OverlayableItem& overlayable) { ResourceName res_name = ParseNameOrDie(name); CHECK(table_->SetOverlayable(res_name, overlayable, GetDiagnostics())); diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h index a12048436e38..c971a1b47fd5 100644 --- a/tools/aapt2/test/Builders.h +++ b/tools/aapt2/test/Builders.h @@ -74,7 +74,7 @@ class ResourceTableBuilder { ResourceTableBuilder& SetSymbolState(const android::StringPiece& name, const ResourceId& id, Visibility::Level level, bool allow_new = false); ResourceTableBuilder& SetOverlayable(const android::StringPiece& name, - const Overlayable& overlayable); + const OverlayableItem& overlayable); StringPool* string_pool(); std::unique_ptr<ResourceTable> Build(); |