diff options
280 files changed, 10044 insertions, 2748 deletions
diff --git a/Android.bp b/Android.bp index a7ce9172bc95..9426a9c503ed 100644 --- a/Android.bp +++ b/Android.bp @@ -803,10 +803,9 @@ filegroup { } filegroup { - name: "incremental_data_loader_aidl", + name: "dataloader_aidl", srcs: [ - "core/java/android/service/incremental/IIncrementalDataLoaderStatusListener.aidl", - "core/java/android/service/incremental/IIncrementalDataLoaderService.aidl", + "core/java/android/content/pm/IDataLoaderStatusListener.aidl", ], path: "core/java", } @@ -815,7 +814,27 @@ aidl_interface { name: "libincremental_aidl", srcs: [ ":incremental_aidl", - ":incremental_data_loader_aidl", + ], + imports: [ + "libdataloader_aidl", + ], + backend: { + java: { + sdk_version: "28", + }, + cpp: { + enabled: true, + }, + ndk: { + enabled: true, + }, + }, +} + +aidl_interface { + name: "libdataloader_aidl", + srcs: [ + ":dataloader_aidl", ], backend: { java: { @@ -1675,6 +1694,7 @@ filegroup { srcs: [ ":framework-annotations", "core/java/android/os/HandlerExecutor.java", + "core/java/android/util/BackupUtils.java", "core/java/android/util/KeyValueListParser.java", "core/java/android/util/LocalLog.java", "core/java/android/util/Rational.java", diff --git a/TEST_MAPPING b/TEST_MAPPING index 1e0b58ef973c..55fa5ed13d79 100644 --- a/TEST_MAPPING +++ b/TEST_MAPPING @@ -8,5 +8,10 @@ } ] } + ], + "postsubmit-managedprofile-stress": [ + { + "name": "ManagedProfileLifecycleStressTest" + } ] } diff --git a/apex/appsearch/service/java/com/android/server/appsearch/impl/FakeIcing.java b/apex/appsearch/service/java/com/android/server/appsearch/impl/FakeIcing.java index 5ac922c4a9b6..3dbb5cffe908 100644 --- a/apex/appsearch/service/java/com/android/server/appsearch/impl/FakeIcing.java +++ b/apex/appsearch/service/java/com/android/server/appsearch/impl/FakeIcing.java @@ -24,10 +24,8 @@ import android.util.SparseArray; import com.google.android.icing.proto.DocumentProto; import com.google.android.icing.proto.PropertyProto; +import com.google.android.icing.proto.SearchResultProto; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; @@ -94,23 +92,25 @@ public class FakeIcing { * Returns documents containing the given term. * * @param term A single exact term to look up in the index. - * @return The matching documents, or an empty {@code List} if no documents match. + * @return A {@link SearchResultProto} containing the matching documents, which may have no + * results if no documents match. */ @NonNull - public List<DocumentProto> query(@NonNull String term) { + public SearchResultProto query(@NonNull String term) { String normTerm = normalizeString(term); Set<Integer> docIds = mIndex.get(normTerm); if (docIds == null || docIds.isEmpty()) { - return Collections.emptyList(); + return SearchResultProto.getDefaultInstance(); } - List<DocumentProto> matches = new ArrayList<>(docIds.size()); + SearchResultProto.Builder results = SearchResultProto.newBuilder(); for (int docId : docIds) { DocumentProto document = mDocStore.get(docId); if (document != null) { - matches.add(document); + results.addResults( + SearchResultProto.ResultProto.newBuilder().setDocument(document)); } } - return matches; + return results.build(); } /** diff --git a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java index f11d4f8782d0..f10db6c08a0e 100644 --- a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java +++ b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java @@ -64,8 +64,7 @@ import android.net.INetworkStatsService; import android.net.Network; import android.net.NetworkRequest; import android.net.NetworkStats; -import android.net.wifi.IWifiManager; -import android.net.wifi.WifiActivityEnergyInfo; +import android.net.wifi.WifiManager; import android.os.BatteryStats; import android.os.BatteryStatsInternal; import android.os.Binder; @@ -98,6 +97,7 @@ import android.os.SystemProperties; import android.os.Temperature; import android.os.UserHandle; import android.os.UserManager; +import android.os.connectivity.WifiActivityEnergyInfo; import android.os.storage.DiskInfo; import android.os.storage.StorageManager; import android.os.storage.VolumeInfo; @@ -170,6 +170,7 @@ import java.util.Objects; import java.util.Set; import java.util.UUID; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -342,7 +343,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { private final KernelWakelockReader mKernelWakelockReader = new KernelWakelockReader(); private final KernelWakelockStats mTmpWakelockStats = new KernelWakelockStats(); - private IWifiManager mWifiManager = null; + private WifiManager mWifiManager = null; private TelephonyManager mTelephony = null; @GuardedBy("sStatsdLock") private final HashSet<Long> mDeathTimeMillis = new HashSet<>(); @@ -1148,35 +1149,48 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { private void pullWifiActivityInfo( int tagId, long elapsedNanos, long wallClockNanos, List<StatsLogEventWrapper> pulledData) { - long token = Binder.clearCallingIdentity(); + WifiManager wifiManager; synchronized (this) { if (mWifiManager == null) { - mWifiManager = - IWifiManager.Stub.asInterface( - ServiceManager.getService(Context.WIFI_SERVICE)); + mWifiManager = mContext.getSystemService(WifiManager.class); } + wifiManager = mWifiManager; } - if (mWifiManager != null) { - try { - SynchronousResultReceiver wifiReceiver = new SynchronousResultReceiver("wifi"); - mWifiManager.requestActivityInfo(wifiReceiver); - final WifiActivityEnergyInfo wifiInfo = awaitControllerInfo(wifiReceiver); - StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, - wallClockNanos); - e.writeLong(wifiInfo.getTimeSinceBootMillis()); - e.writeInt(wifiInfo.getStackState()); - e.writeLong(wifiInfo.getControllerTxDurationMillis()); - e.writeLong(wifiInfo.getControllerRxDurationMillis()); - e.writeLong(wifiInfo.getControllerIdleDurationMillis()); - e.writeLong(wifiInfo.getControllerEnergyUsedMicroJoules()); - pulledData.add(e); - } catch (RemoteException e) { - Slog.e(TAG, - "Pulling wifiManager for wifi controller activity energy info has error", - e); - } finally { - Binder.restoreCallingIdentity(token); + if (wifiManager == null) { + return; + } + long token = Binder.clearCallingIdentity(); + try { + SynchronousResultReceiver wifiReceiver = new SynchronousResultReceiver("wifi"); + wifiManager.getWifiActivityEnergyInfoAsync( + new Executor() { + @Override + public void execute(Runnable runnable) { + // run the listener on the binder thread, if it was run on the main + // thread it would deadlock since we would be waiting on ourselves + runnable.run(); + } + }, + info -> { + Bundle bundle = new Bundle(); + bundle.putParcelable(BatteryStats.RESULT_RECEIVER_CONTROLLER_KEY, info); + wifiReceiver.send(0, bundle); + } + ); + final WifiActivityEnergyInfo wifiInfo = awaitControllerInfo(wifiReceiver); + if (wifiInfo == null) { + return; } + StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos); + e.writeLong(wifiInfo.getTimeSinceBootMillis()); + e.writeInt(wifiInfo.getStackState()); + e.writeLong(wifiInfo.getControllerTxDurationMillis()); + e.writeLong(wifiInfo.getControllerRxDurationMillis()); + e.writeLong(wifiInfo.getControllerIdleDurationMillis()); + e.writeLong(wifiInfo.getControllerEnergyUsedMicroJoules()); + pulledData.add(e); + } finally { + Binder.restoreCallingIdentity(token); } } diff --git a/api/current.txt b/api/current.txt index 297835c70851..76b1a7d4115c 100644 --- a/api/current.txt +++ b/api/current.txt @@ -299,6 +299,7 @@ package android { field public static final int animateFirstView = 16843477; // 0x10102d5 field public static final int animateLayoutChanges = 16843506; // 0x10102f2 field public static final int animateOnClick = 16843356; // 0x101025c + field public static final int animatedImageDrawable = 16844298; // 0x101060a field public static final int animation = 16843213; // 0x10101cd field public static final int animationCache = 16842989; // 0x10100ed field public static final int animationDuration = 16843026; // 0x1010112 @@ -723,6 +724,7 @@ package android { field public static final int host = 16842792; // 0x1010028 field public static final int hotSpotX = 16844055; // 0x1010517 field public static final int hotSpotY = 16844056; // 0x1010518 + field public static final int htmlDescription = 16844299; // 0x101060b field public static final int hyphenationFrequency = 16843998; // 0x10104de field public static final int icon = 16842754; // 0x1010002 field @Deprecated public static final int iconPreview = 16843337; // 0x1010249 @@ -2926,6 +2928,7 @@ package android.accessibilityservice { method public int describeContents(); method public static String feedbackTypeToString(int); method public static String flagToString(int); + method public int getAnimatedImageRes(); method @Deprecated public boolean getCanRetrieveWindowContent(); method public int getCapabilities(); method @Deprecated public String getDescription(); @@ -2935,6 +2938,7 @@ package android.accessibilityservice { method public android.content.pm.ResolveInfo getResolveInfo(); method public String getSettingsActivityName(); method public String loadDescription(android.content.pm.PackageManager); + method @Nullable public String loadHtmlDescription(@NonNull android.content.pm.PackageManager); method public CharSequence loadSummary(android.content.pm.PackageManager); method public void setInteractiveUiTimeoutMillis(@IntRange(from=0) int); method public void setNonInteractiveUiTimeoutMillis(@IntRange(from=0) int); @@ -5177,12 +5181,12 @@ package android.app { method public android.content.Intent getResultData(); } - public abstract class IntentService extends android.app.Service { - ctor public IntentService(String); - method @Nullable public android.os.IBinder onBind(android.content.Intent); - method @WorkerThread protected abstract void onHandleIntent(@Nullable android.content.Intent); - method public void onStart(@Nullable android.content.Intent, int); - method public void setIntentRedelivery(boolean); + @Deprecated public abstract class IntentService extends android.app.Service { + ctor @Deprecated public IntentService(String); + method @Deprecated @Nullable public android.os.IBinder onBind(android.content.Intent); + method @Deprecated @WorkerThread protected abstract void onHandleIntent(@Nullable android.content.Intent); + method @Deprecated public void onStart(@Nullable android.content.Intent, int); + method @Deprecated public void setIntentRedelivery(boolean); } public class KeyguardManager { @@ -11361,6 +11365,15 @@ package android.content.pm { field public int version; } + public final class InstallSourceInfo implements android.os.Parcelable { + method public int describeContents(); + method @Nullable public String getInitiatingPackageName(); + method @Nullable public String getInstallingPackageName(); + method @Nullable public String getOriginatingPackageName(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.InstallSourceInfo> CREATOR; + } + public class InstrumentationInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable { ctor public InstrumentationInfo(); ctor public InstrumentationInfo(android.content.pm.InstrumentationInfo); @@ -11725,10 +11738,11 @@ package android.content.pm { method public abstract int getComponentEnabledSetting(@NonNull android.content.ComponentName); method @NonNull public abstract android.graphics.drawable.Drawable getDefaultActivityIcon(); method @Nullable public abstract android.graphics.drawable.Drawable getDrawable(@NonNull String, @DrawableRes int, @Nullable android.content.pm.ApplicationInfo); + method @NonNull public android.content.pm.InstallSourceInfo getInstallSourceInfo(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; method @NonNull public abstract java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int); method @NonNull public java.util.List<android.content.pm.ModuleInfo> getInstalledModules(int); method @NonNull public abstract java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int); - method @Nullable public abstract String getInstallerPackageName(@NonNull String); + method @Deprecated @Nullable public abstract String getInstallerPackageName(@NonNull String); method @NonNull public abstract byte[] getInstantAppCookie(); method public abstract int getInstantAppCookieMaxBytes(); method @NonNull public abstract android.content.pm.InstrumentationInfo getInstrumentationInfo(@NonNull android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException; @@ -25060,6 +25074,7 @@ package android.media { method @NonNull public java.util.List<byte[]> getSecureStopIds(); method @NonNull public java.util.List<byte[]> getSecureStops(); method @android.media.MediaDrm.SecurityLevel public int getSecurityLevel(@NonNull byte[]); + method @NonNull public static java.util.List<java.util.UUID> getSupportedCryptoSchemes(); method public static boolean isCryptoSchemeSupported(@NonNull java.util.UUID); method public static boolean isCryptoSchemeSupported(@NonNull java.util.UUID, @NonNull String); method public static boolean isCryptoSchemeSupported(@NonNull java.util.UUID, @NonNull String, @android.media.MediaDrm.SecurityLevel int); @@ -25600,6 +25615,50 @@ package android.media { field public static final int MUXER_OUTPUT_WEBM = 1; // 0x1 } + public final class MediaParser { + method public boolean advance(@NonNull android.media.MediaParser.SeekableInputReader) throws java.io.IOException, java.lang.InterruptedException; + method @NonNull public static android.media.MediaParser create(@NonNull android.media.MediaParser.OutputConsumer, @NonNull java.lang.String...); + method @NonNull public static android.media.MediaParser createByName(@NonNull String, @NonNull android.media.MediaParser.OutputConsumer); + method @Nullable public String getExtractorName(); + method @NonNull public static java.util.List<java.lang.String> getExtractorNames(@NonNull android.media.MediaFormat); + method public void release(); + method public void seek(@NonNull android.media.MediaParser.SeekPoint); + } + + public static interface MediaParser.InputReader { + method public long getLength(); + method public long getPosition(); + method public int read(@NonNull byte[], int, int) throws java.io.IOException, java.lang.InterruptedException; + } + + public static interface MediaParser.OutputConsumer { + method public void onFormat(int, @NonNull android.media.MediaFormat); + method public void onSampleCompleted(int, long, int, int, int, @Nullable android.media.MediaCodec.CryptoInfo); + method public void onSampleData(int, @NonNull android.media.MediaParser.InputReader) throws java.io.IOException, java.lang.InterruptedException; + method public void onSeekMap(@NonNull android.media.MediaParser.SeekMap); + method public void onTracksFound(int); + } + + public static interface MediaParser.SeekMap { + method public long getDurationUs(); + method @NonNull public android.util.Pair<android.media.MediaParser.SeekPoint,android.media.MediaParser.SeekPoint> getSeekPoints(long); + method public boolean isSeekable(); + field public static final int UNKNOWN_DURATION = -2147483648; // 0x80000000 + } + + public static final class MediaParser.SeekPoint { + field @NonNull public static final android.media.MediaParser.SeekPoint START; + field public final long position; + field public final long timeUs; + } + + public static interface MediaParser.SeekableInputReader extends android.media.MediaParser.InputReader { + method public void seekToPosition(long); + } + + public static final class MediaParser.UnrecognizedInputFormatException extends java.io.IOException { + } + public class MediaPlayer implements android.media.AudioRouting android.media.VolumeAutomation { ctor public MediaPlayer(); method public void addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler); @@ -36873,6 +36932,7 @@ package android.provider { method @WorkerThread public static boolean isBlocked(android.content.Context, String); method @WorkerThread public static int unblock(android.content.Context, String); field public static final String AUTHORITY = "com.android.blockednumber"; + field public static final android.net.Uri AUTHORITY_URI; } public static class BlockedNumberContract.BlockedNumbers { @@ -43732,6 +43792,27 @@ package android.telecom { method public final void setVideoProvider(android.telecom.Connection.VideoProvider); method public final void setVideoState(int); method public static String stateToString(int); + field public static final int AUDIO_CODEC_AMR = 1; // 0x1 + field public static final int AUDIO_CODEC_AMR_WB = 2; // 0x2 + field public static final int AUDIO_CODEC_EVRC = 4; // 0x4 + field public static final int AUDIO_CODEC_EVRC_B = 5; // 0x5 + field public static final int AUDIO_CODEC_EVRC_NW = 7; // 0x7 + field public static final int AUDIO_CODEC_EVRC_WB = 6; // 0x6 + field public static final int AUDIO_CODEC_EVS_FB = 20; // 0x14 + field public static final int AUDIO_CODEC_EVS_NB = 17; // 0x11 + field public static final int AUDIO_CODEC_EVS_SWB = 19; // 0x13 + field public static final int AUDIO_CODEC_EVS_WB = 18; // 0x12 + field public static final int AUDIO_CODEC_G711A = 13; // 0xd + field public static final int AUDIO_CODEC_G711AB = 15; // 0xf + field public static final int AUDIO_CODEC_G711U = 11; // 0xb + field public static final int AUDIO_CODEC_G722 = 14; // 0xe + field public static final int AUDIO_CODEC_G723 = 12; // 0xc + field public static final int AUDIO_CODEC_G729 = 16; // 0x10 + field public static final int AUDIO_CODEC_GSM_EFR = 8; // 0x8 + field public static final int AUDIO_CODEC_GSM_FR = 9; // 0x9 + field public static final int AUDIO_CODEC_GSM_HR = 10; // 0xa + field public static final int AUDIO_CODEC_NONE = 0; // 0x0 + field public static final int AUDIO_CODEC_QCELP13K = 3; // 0x3 field public static final int CAPABILITY_CANNOT_DOWNGRADE_VIDEO_TO_AUDIO = 8388608; // 0x800000 field public static final int CAPABILITY_CAN_PAUSE_VIDEO = 1048576; // 0x100000 field public static final int CAPABILITY_CAN_PULL_CALL = 16777216; // 0x1000000 @@ -43765,6 +43846,7 @@ package android.telecom { field public static final String EVENT_RTT_AUDIO_INDICATION_CHANGED = "android.telecom.event.RTT_AUDIO_INDICATION_CHANGED"; field public static final String EXTRA_ANSWERING_DROPS_FG_CALL = "android.telecom.extra.ANSWERING_DROPS_FG_CALL"; field public static final String EXTRA_ANSWERING_DROPS_FG_CALL_APP_NAME = "android.telecom.extra.ANSWERING_DROPS_FG_CALL_APP_NAME"; + field public static final String EXTRA_AUDIO_CODEC = "android.telecom.extra.AUDIO_CODEC"; field public static final String EXTRA_CALL_SUBJECT = "android.telecom.extra.CALL_SUBJECT"; field public static final String EXTRA_CHILD_ADDRESS = "android.telecom.extra.CHILD_ADDRESS"; field public static final String EXTRA_IS_RTT_AUDIO_PRESENT = "android.telecom.extra.IS_RTT_AUDIO_PRESENT"; @@ -44533,6 +44615,7 @@ package android.telephony { field public static final String KEY_DEFAULT_VM_NUMBER_STRING = "default_vm_number_string"; field public static final String KEY_DIAL_STRING_REPLACE_STRING_ARRAY = "dial_string_replace_string_array"; field public static final String KEY_DISABLE_CDMA_ACTIVATION_CODE_BOOL = "disable_cdma_activation_code_bool"; + field public static final String KEY_DISABLE_SUPPLEMENTARY_SERVICES_IN_AIRPLANE_MODE_BOOL = "disable_supplementary_services_in_airplane_mode_bool"; field public static final String KEY_DISCONNECT_CAUSE_PLAY_BUSYTONE_INT_ARRAY = "disconnect_cause_play_busytone_int_array"; field public static final String KEY_DISPLAY_HD_AUDIO_PROPERTY_BOOL = "display_hd_audio_property_bool"; field public static final String KEY_DROP_VIDEO_CALL_WHEN_ANSWERING_AUDIO_CALL_BOOL = "drop_video_call_when_answering_audio_call_bool"; diff --git a/api/system-current.txt b/api/system-current.txt index 8bb198396ac3..7fdf9edec911 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -1198,6 +1198,7 @@ package android.app.prediction { field public static final int ACTION_DISMISS = 2; // 0x2 field public static final int ACTION_LAUNCH = 1; // 0x1 field public static final int ACTION_PIN = 3; // 0x3 + field public static final int ACTION_UNPIN = 4; // 0x4 field @NonNull public static final android.os.Parcelable.Creator<android.app.prediction.AppTargetEvent> CREATOR; } @@ -4487,6 +4488,7 @@ package android.net { public class Network implements android.os.Parcelable { ctor public Network(@NonNull android.net.Network); method @NonNull public android.net.Network getPrivateDnsBypassingCopy(); + field public final int netId; } public final class NetworkCapabilities implements android.os.Parcelable { @@ -5340,32 +5342,6 @@ package android.net.wifi { field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.SoftApInfo> CREATOR; } - public final class WifiActivityEnergyInfo implements android.os.Parcelable { - ctor public WifiActivityEnergyInfo(long, int, long, long, long, long, long); - method public int describeContents(); - method public long getControllerEnergyUsedMicroJoules(); - method public long getControllerIdleDurationMillis(); - method public long getControllerRxDurationMillis(); - method public long getControllerScanDurationMillis(); - method public long getControllerTxDurationMillis(); - method public int getStackState(); - method public long getTimeSinceBootMillis(); - method public boolean isValid(); - method public void setControllerEnergyUsedMicroJoules(long); - method public void setControllerIdleDurationMillis(long); - method public void setControllerRxDurationMillis(long); - method public void setControllerScanDurationMillis(long); - method public void setControllerTxDurationMillis(long); - method public void setStackState(int); - method public void setTimeSinceBootMillis(long); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.WifiActivityEnergyInfo> CREATOR; - field public static final int STACK_STATE_INVALID = 0; // 0x0 - field public static final int STACK_STATE_STATE_ACTIVE = 1; // 0x1 - field public static final int STACK_STATE_STATE_IDLE = 3; // 0x3 - field public static final int STACK_STATE_STATE_SCANNING = 2; // 0x2 - } - public final class WifiClient implements android.os.Parcelable { method public int describeContents(); method @NonNull public android.net.MacAddress getMacAddress(); @@ -5502,6 +5478,7 @@ package android.net.wifi { method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.ACCESS_WIFI_STATE, android.Manifest.permission.READ_WIFI_CREDENTIAL}) public java.util.List<android.net.wifi.WifiConfiguration> getPrivilegedConfiguredNetworks(); method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public android.net.wifi.SoftApConfiguration getSoftApConfiguration(); method public int getVerboseLoggingLevel(); + method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public void getWifiActivityEnergyInfoAsync(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.OnWifiActivityEnergyInfoListener); method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public android.net.wifi.WifiConfiguration getWifiApConfiguration(); method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public int getWifiApState(); method public boolean isApMacRandomizationSupported(); @@ -5605,6 +5582,10 @@ package android.net.wifi { method public default void select(@NonNull android.net.wifi.WifiConfiguration); } + public static interface WifiManager.OnWifiActivityEnergyInfoListener { + method public void onWifiActivityEnergyInfo(@Nullable android.os.connectivity.WifiActivityEnergyInfo); + } + public static interface WifiManager.OnWifiUsabilityStatsListener { method public void onWifiUsabilityStats(int, boolean, @NonNull android.net.wifi.WifiUsabilityStatsEntry); } @@ -5672,10 +5653,13 @@ package android.net.wifi { field public static final int SCAN_TYPE_HIGH_ACCURACY = 2; // 0x2 field public static final int SCAN_TYPE_LOW_LATENCY = 0; // 0x0 field public static final int SCAN_TYPE_LOW_POWER = 1; // 0x1 + field public static final int WIFI_BAND_24_5_6_GHZ = 11; // 0xb + field public static final int WIFI_BAND_24_5_WITH_DFS_6_GHZ = 15; // 0xf field public static final int WIFI_BAND_24_GHZ = 1; // 0x1 field public static final int WIFI_BAND_5_GHZ = 2; // 0x2 field public static final int WIFI_BAND_5_GHZ_DFS_ONLY = 4; // 0x4 field public static final int WIFI_BAND_5_GHZ_WITH_DFS = 6; // 0x6 + field public static final int WIFI_BAND_6_GHZ = 8; // 0x8 field public static final int WIFI_BAND_BOTH = 3; // 0x3 field public static final int WIFI_BAND_BOTH_WITH_DFS = 7; // 0x7 field public static final int WIFI_BAND_UNSPECIFIED = 0; // 0x0 @@ -6604,6 +6588,32 @@ package android.os.connectivity { field @NonNull public static final android.os.Parcelable.Creator<android.os.connectivity.CellularBatteryStats> CREATOR; } + public final class WifiActivityEnergyInfo implements android.os.Parcelable { + ctor public WifiActivityEnergyInfo(long, int, long, long, long, long, long); + method public int describeContents(); + method public long getControllerEnergyUsedMicroJoules(); + method public long getControllerIdleDurationMillis(); + method public long getControllerRxDurationMillis(); + method public long getControllerScanDurationMillis(); + method public long getControllerTxDurationMillis(); + method public int getStackState(); + method public long getTimeSinceBootMillis(); + method public boolean isValid(); + method public void setControllerEnergyUsedMicroJoules(long); + method public void setControllerIdleDurationMillis(long); + method public void setControllerRxDurationMillis(long); + method public void setControllerScanDurationMillis(long); + method public void setControllerTxDurationMillis(long); + method public void setStackState(int); + method public void setTimeSinceBootMillis(long); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.os.connectivity.WifiActivityEnergyInfo> CREATOR; + field public static final int STACK_STATE_INVALID = 0; // 0x0 + field public static final int STACK_STATE_STATE_ACTIVE = 1; // 0x1 + field public static final int STACK_STATE_STATE_IDLE = 3; // 0x3 + field public static final int STACK_STATE_STATE_SCANNING = 2; // 0x2 + } + public final class WifiBatteryStats implements android.os.Parcelable { method public int describeContents(); method public long getEnergyConsumedMaMillis(); @@ -6821,7 +6831,6 @@ package android.printservice.recommendation { package android.provider { public class BlockedNumberContract { - field @NonNull public static final android.net.Uri AUTHORITY_URI; field public static final String METHOD_NOTIFY_EMERGENCY_CONTACT = "notify_emergency_contact"; field public static final String METHOD_SHOULD_SYSTEM_BLOCK_NUMBER = "should_system_block_number"; field public static final String RES_BLOCK_STATUS = "block_status"; @@ -8421,6 +8430,10 @@ package android.telephony { field public final double lng; } + public class CellBroadcastIntents { + method public static void sendOrderedBroadcastForBackgroundReceivers(@NonNull android.content.Context, @Nullable android.os.UserHandle, @NonNull android.content.Intent, @Nullable String, @Nullable String, @Nullable android.content.BroadcastReceiver, @Nullable android.os.Handler, int, @Nullable String, @Nullable android.os.Bundle); + } + public abstract class CellBroadcastService extends android.app.Service { ctor public CellBroadcastService(); method @CallSuper public android.os.IBinder onBind(@Nullable android.content.Intent); diff --git a/api/test-current.txt b/api/test-current.txt index 2d357364f042..3ddbbf82d9d7 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -578,6 +578,7 @@ package android.app.prediction { field public static final int ACTION_DISMISS = 2; // 0x2 field public static final int ACTION_LAUNCH = 1; // 0x1 field public static final int ACTION_PIN = 3; // 0x3 + field public static final int ACTION_UNPIN = 4; // 0x4 field @NonNull public static final android.os.Parcelable.Creator<android.app.prediction.AppTargetEvent> CREATOR; } diff --git a/api/test-lint-baseline.txt b/api/test-lint-baseline.txt index 3aa8187c0a79..a8c4db38c841 100644 --- a/api/test-lint-baseline.txt +++ b/api/test-lint-baseline.txt @@ -8,35 +8,35 @@ AcronymName: android.app.NotificationChannel#setImportanceLockedByOEM(boolean): ActionValue: android.location.Location#EXTRA_NO_GPS_LOCATION: ActionValue: android.telephony.ims.ImsCallProfile#EXTRA_ADDITIONAL_CALL_INFO: - Inconsistent extra value; expected `android.telephony.ims.extra.ADDITIONAL_CALL_INFO`, was `AdditionalCallInfo` + ActionValue: android.telephony.ims.ImsCallProfile#EXTRA_CALL_RAT_TYPE: - Inconsistent extra value; expected `android.telephony.ims.extra.CALL_RAT_TYPE`, was `CallRadioTech` + ActionValue: android.telephony.ims.ImsCallProfile#EXTRA_CHILD_NUMBER: - Inconsistent extra value; expected `android.telephony.ims.extra.CHILD_NUMBER`, was `ChildNum` + ActionValue: android.telephony.ims.ImsCallProfile#EXTRA_CNA: - Inconsistent extra value; expected `android.telephony.ims.extra.CNA`, was `cna` + ActionValue: android.telephony.ims.ImsCallProfile#EXTRA_CNAP: - Inconsistent extra value; expected `android.telephony.ims.extra.CNAP`, was `cnap` + ActionValue: android.telephony.ims.ImsCallProfile#EXTRA_CODEC: - Inconsistent extra value; expected `android.telephony.ims.extra.CODEC`, was `Codec` + ActionValue: android.telephony.ims.ImsCallProfile#EXTRA_DIALSTRING: - Inconsistent extra value; expected `android.telephony.ims.extra.DIALSTRING`, was `dialstring` + ActionValue: android.telephony.ims.ImsCallProfile#EXTRA_DISPLAY_TEXT: - Inconsistent extra value; expected `android.telephony.ims.extra.DISPLAY_TEXT`, was `DisplayText` + ActionValue: android.telephony.ims.ImsCallProfile#EXTRA_EMERGENCY_CALL: - Inconsistent extra value; expected `android.telephony.ims.extra.EMERGENCY_CALL`, was `e_call` + ActionValue: android.telephony.ims.ImsCallProfile#EXTRA_IS_CALL_PULL: - Inconsistent extra value; expected `android.telephony.ims.extra.IS_CALL_PULL`, was `CallPull` + ActionValue: android.telephony.ims.ImsCallProfile#EXTRA_OI: - Inconsistent extra value; expected `android.telephony.ims.extra.OI`, was `oi` + ActionValue: android.telephony.ims.ImsCallProfile#EXTRA_OIR: - Inconsistent extra value; expected `android.telephony.ims.extra.OIR`, was `oir` + ActionValue: android.telephony.ims.ImsCallProfile#EXTRA_REMOTE_URI: - Inconsistent extra value; expected `android.telephony.ims.extra.REMOTE_URI`, was `remote_uri` + ActionValue: android.telephony.ims.ImsCallProfile#EXTRA_USSD: - Inconsistent extra value; expected `android.telephony.ims.extra.USSD`, was `ussd` + ActionValue: android.telephony.ims.ImsReasonInfo#EXTRA_MSG_SERVICE_NOT_AUTHORIZED: - Inconsistent extra value; expected `android.telephony.ims.extra.MSG_SERVICE_NOT_AUTHORIZED`, was `Forbidden. Not Authorized for Service` + ActionValue: android.telephony.mbms.vendor.VendorUtils#ACTION_CLEANUP: ActionValue: android.telephony.mbms.vendor.VendorUtils#ACTION_DOWNLOAD_RESULT_INTERNAL: @@ -100,13 +100,13 @@ ArrayReturn: android.os.NativeHandle#getFileDescriptors(): ArrayReturn: android.security.keystore.AttestationUtils#attestDeviceIds(android.content.Context, int[], byte[]): ArrayReturn: android.telephony.ims.ImsUtListener#onUtConfigurationCallBarringQueried(int, android.telephony.ims.ImsSsInfo[]) parameter #1: - Method parameter should be Collection<ImsSsInfo> (or subclass) instead of raw array; was `android.telephony.ims.ImsSsInfo[]` + ArrayReturn: android.telephony.ims.ImsUtListener#onUtConfigurationCallForwardQueried(int, android.telephony.ims.ImsCallForwardInfo[]) parameter #1: - Method parameter should be Collection<ImsCallForwardInfo> (or subclass) instead of raw array; was `android.telephony.ims.ImsCallForwardInfo[]` + ArrayReturn: android.telephony.ims.ImsUtListener#onUtConfigurationCallWaitingQueried(int, android.telephony.ims.ImsSsInfo[]) parameter #1: - Method parameter should be Collection<ImsSsInfo> (or subclass) instead of raw array; was `android.telephony.ims.ImsSsInfo[]` + ArrayReturn: android.telephony.ims.stub.ImsRegistrationImplBase#onSubscriberAssociatedUriChanged(android.net.Uri[]) parameter #0: - Method parameter should be Collection<Uri> (or subclass) instead of raw array; was `android.net.Uri[]` + ArrayReturn: android.view.FocusFinder#sort(android.view.View[], int, int, android.view.ViewGroup, boolean) parameter #0: ArrayReturn: android.view.contentcapture.ViewNode#getAutofillOptions(): @@ -268,7 +268,7 @@ ConcreteCollection: android.service.autofill.InternalTransformation#batchApply(a ConcreteCollection: android.service.autofill.UserData#getFieldClassificationAlgorithms(): ConcreteCollection: android.telephony.ims.ImsConferenceState#mParticipants: - Field type is concrete collection (`java.util.HashMap`); must be higher-level interface + ContextFirst: android.os.VibrationEffect#get(android.net.Uri, android.content.Context) parameter #1: @@ -338,9 +338,9 @@ ExecutorRegistration: android.os.RemoteCallback#RemoteCallback(android.os.Remote ExecutorRegistration: android.permission.PermissionControllerManager#getAppPermissions(String, android.permission.PermissionControllerManager.OnGetAppPermissionResultCallback, android.os.Handler): ExecutorRegistration: android.telephony.ims.stub.ImsCallSessionImplBase#setListener(android.telephony.ims.ImsCallSessionListener): - Registration methods should have overload that accepts delivery Executor: `setListener` + ExecutorRegistration: android.telephony.ims.stub.ImsUtImplBase#setListener(android.telephony.ims.ImsUtListener): - Registration methods should have overload that accepts delivery Executor: `setListener` + ExecutorRegistration: android.telephony.mbms.vendor.MbmsDownloadServiceBase#addProgressListener(android.telephony.mbms.DownloadRequest, android.telephony.mbms.DownloadProgressListener): ExecutorRegistration: android.telephony.mbms.vendor.MbmsDownloadServiceBase#addStatusListener(android.telephony.mbms.DownloadRequest, android.telephony.mbms.DownloadStatusListener): @@ -460,11 +460,13 @@ InterfaceConstant: android.telecom.PhoneAccountSuggestionService#SERVICE_INTERFA InternalField: android.telephony.ims.ImsConferenceState#mParticipants: - Internal field mParticipants must not be exposed + KotlinOperator: android.os.WorkSource#get(int): +KotlinOperator: android.util.SparseArrayMap#get(int, String): + Method can be invoked with an indexing operator from Kotlin: `get` (this is usually desirable; just make sure it makes sense for this type of object) ListenerInterface: android.media.audiopolicy.AudioPolicy.AudioPolicyFocusListener: @@ -474,9 +476,9 @@ ListenerInterface: android.media.audiopolicy.AudioPolicy.AudioPolicyStatusListen ListenerInterface: android.os.IncidentManager.AuthListener: ListenerInterface: android.telephony.ims.ImsCallSessionListener: - Listeners should be an interface, or otherwise renamed Callback: ImsCallSessionListener + ListenerInterface: android.telephony.ims.ImsUtListener: - Listeners should be an interface, or otherwise renamed Callback: ImsUtListener + ListenerLast: android.hardware.camera2.CameraDevice#createCustomCaptureSession(android.hardware.camera2.params.InputConfiguration, java.util.List<android.hardware.camera2.params.OutputConfiguration>, int, android.hardware.camera2.CameraCaptureSession.StateCallback, android.os.Handler) parameter #4: @@ -496,17 +498,17 @@ ManagerConstructor: android.content.pm.ShortcutManager#ShortcutManager(android.c ManagerLookup: android.telephony.ims.ImsMmTelManager#createForSubscriptionId(int): - Managers must always be obtained from Context (`createForSubscriptionId`) + ManagerLookup: android.telephony.ims.ProvisioningManager#createForSubscriptionId(int): - Managers must always be obtained from Context (`createForSubscriptionId`) + MethodNameTense: android.telephony.ims.feature.CapabilityChangeRequest#getCapabilitiesToEnable(): - Unexpected tense; probably meant `enabled`, was `getCapabilitiesToEnable` + MethodNameUnits: android.telephony.ims.ImsCallForwardInfo#getTimeSeconds(): - Returned time values must be in milliseconds, was `getTimeSeconds` + MinMaxConstant: android.os.UserHandle#MIN_SECONDARY_USER_ID: @@ -1460,7 +1462,7 @@ MissingNullability: android.telecom.PhoneAccountSuggestionService#onBind(android MissingNullability: android.telecom.PhoneAccountSuggestionService#onBind(android.content.Intent) parameter #0: MissingNullability: android.telephony.CallQuality#writeToParcel(android.os.Parcel, int) parameter #0: - Missing nullability on parameter `dest` in method `writeToParcel` + MissingNullability: android.telephony.DataSpecificRegistrationInfo#writeToParcel(android.os.Parcel, int) parameter #0: MissingNullability: android.telephony.LteVopsSupportInfo#writeToParcel(android.os.Parcel, int) parameter #0: @@ -1478,9 +1480,9 @@ MissingNullability: android.telephony.SmsManager#checkSmsShortCodeDestination(St MissingNullability: android.telephony.TelephonyManager#checkCarrierPrivilegesForPackage(String) parameter #0: MissingNullability: android.telephony.TelephonyManager#getCarrierPackageNamesForIntent(android.content.Intent): - Missing nullability on method `getCarrierPackageNamesForIntent` return + MissingNullability: android.telephony.TelephonyManager#getCarrierPackageNamesForIntent(android.content.Intent) parameter #0: - Missing nullability on parameter `intent` in method `getCarrierPackageNamesForIntent` + MissingNullability: android.telephony.TelephonyManager#getLine1AlphaTag(): MissingNullability: android.telephony.TelephonyManager#getRadioHalVersion(): @@ -1518,315 +1520,315 @@ MissingNullability: android.telephony.TelephonyManager#setCarrierTestOverride(St MissingNullability: android.telephony.TelephonyManager#setCarrierTestOverride(String, String, String, String, String, String, String, String, String) parameter #8: MissingNullability: android.telephony.ims.ImsCallForwardInfo#getNumber(): - Missing nullability on method `getNumber` return + MissingNullability: android.telephony.ims.ImsCallForwardInfo#writeToParcel(android.os.Parcel, int) parameter #0: - Missing nullability on parameter `out` in method `writeToParcel` + MissingNullability: android.telephony.ims.ImsCallProfile#ImsCallProfile(int, int, android.os.Bundle, android.telephony.ims.ImsStreamMediaProfile) parameter #2: - Missing nullability on parameter `callExtras` in method `ImsCallProfile` + MissingNullability: android.telephony.ims.ImsCallProfile#ImsCallProfile(int, int, android.os.Bundle, android.telephony.ims.ImsStreamMediaProfile) parameter #3: - Missing nullability on parameter `mediaProfile` in method `ImsCallProfile` + MissingNullability: android.telephony.ims.ImsCallProfile#getCallExtra(String): - Missing nullability on method `getCallExtra` return + MissingNullability: android.telephony.ims.ImsCallProfile#getCallExtra(String) parameter #0: - Missing nullability on parameter `name` in method `getCallExtra` + MissingNullability: android.telephony.ims.ImsCallProfile#getCallExtra(String, String): - Missing nullability on method `getCallExtra` return + MissingNullability: android.telephony.ims.ImsCallProfile#getCallExtra(String, String) parameter #0: - Missing nullability on parameter `name` in method `getCallExtra` + MissingNullability: android.telephony.ims.ImsCallProfile#getCallExtra(String, String) parameter #1: - Missing nullability on parameter `defaultValue` in method `getCallExtra` + MissingNullability: android.telephony.ims.ImsCallProfile#getCallExtraBoolean(String) parameter #0: - Missing nullability on parameter `name` in method `getCallExtraBoolean` + MissingNullability: android.telephony.ims.ImsCallProfile#getCallExtraBoolean(String, boolean) parameter #0: - Missing nullability on parameter `name` in method `getCallExtraBoolean` + MissingNullability: android.telephony.ims.ImsCallProfile#getCallExtraInt(String) parameter #0: - Missing nullability on parameter `name` in method `getCallExtraInt` + MissingNullability: android.telephony.ims.ImsCallProfile#getCallExtraInt(String, int) parameter #0: - Missing nullability on parameter `name` in method `getCallExtraInt` + MissingNullability: android.telephony.ims.ImsCallProfile#getCallExtras(): - Missing nullability on method `getCallExtras` return + MissingNullability: android.telephony.ims.ImsCallProfile#getMediaProfile(): - Missing nullability on method `getMediaProfile` return + MissingNullability: android.telephony.ims.ImsCallProfile#getVideoStateFromImsCallProfile(android.telephony.ims.ImsCallProfile) parameter #0: - Missing nullability on parameter `callProfile` in method `getVideoStateFromImsCallProfile` + MissingNullability: android.telephony.ims.ImsCallProfile#setCallExtra(String, String) parameter #0: - Missing nullability on parameter `name` in method `setCallExtra` + MissingNullability: android.telephony.ims.ImsCallProfile#setCallExtra(String, String) parameter #1: - Missing nullability on parameter `value` in method `setCallExtra` + MissingNullability: android.telephony.ims.ImsCallProfile#setCallExtraBoolean(String, boolean) parameter #0: - Missing nullability on parameter `name` in method `setCallExtraBoolean` + MissingNullability: android.telephony.ims.ImsCallProfile#setCallExtraInt(String, int) parameter #0: - Missing nullability on parameter `name` in method `setCallExtraInt` + MissingNullability: android.telephony.ims.ImsCallProfile#updateCallExtras(android.telephony.ims.ImsCallProfile) parameter #0: - Missing nullability on parameter `profile` in method `updateCallExtras` + MissingNullability: android.telephony.ims.ImsCallProfile#updateCallType(android.telephony.ims.ImsCallProfile) parameter #0: - Missing nullability on parameter `profile` in method `updateCallType` + MissingNullability: android.telephony.ims.ImsCallProfile#updateMediaProfile(android.telephony.ims.ImsCallProfile) parameter #0: - Missing nullability on parameter `profile` in method `updateMediaProfile` + MissingNullability: android.telephony.ims.ImsCallProfile#writeToParcel(android.os.Parcel, int) parameter #0: - Missing nullability on parameter `out` in method `writeToParcel` + MissingNullability: android.telephony.ims.ImsCallSessionListener#callSessionConferenceExtendFailed(android.telephony.ims.ImsReasonInfo) parameter #0: - Missing nullability on parameter `reasonInfo` in method `callSessionConferenceExtendFailed` + MissingNullability: android.telephony.ims.ImsCallSessionListener#callSessionConferenceExtendReceived(android.telephony.ims.stub.ImsCallSessionImplBase, android.telephony.ims.ImsCallProfile) parameter #0: - Missing nullability on parameter `newSession` in method `callSessionConferenceExtendReceived` + MissingNullability: android.telephony.ims.ImsCallSessionListener#callSessionConferenceExtendReceived(android.telephony.ims.stub.ImsCallSessionImplBase, android.telephony.ims.ImsCallProfile) parameter #1: - Missing nullability on parameter `profile` in method `callSessionConferenceExtendReceived` + MissingNullability: android.telephony.ims.ImsCallSessionListener#callSessionConferenceExtended(android.telephony.ims.stub.ImsCallSessionImplBase, android.telephony.ims.ImsCallProfile) parameter #0: - Missing nullability on parameter `newSession` in method `callSessionConferenceExtended` + MissingNullability: android.telephony.ims.ImsCallSessionListener#callSessionConferenceExtended(android.telephony.ims.stub.ImsCallSessionImplBase, android.telephony.ims.ImsCallProfile) parameter #1: - Missing nullability on parameter `profile` in method `callSessionConferenceExtended` + MissingNullability: android.telephony.ims.ImsCallSessionListener#callSessionConferenceStateUpdated(android.telephony.ims.ImsConferenceState) parameter #0: - Missing nullability on parameter `state` in method `callSessionConferenceStateUpdated` + MissingNullability: android.telephony.ims.ImsCallSessionListener#callSessionHandover(int, int, android.telephony.ims.ImsReasonInfo) parameter #2: - Missing nullability on parameter `reasonInfo` in method `callSessionHandover` + MissingNullability: android.telephony.ims.ImsCallSessionListener#callSessionHandoverFailed(int, int, android.telephony.ims.ImsReasonInfo) parameter #2: - Missing nullability on parameter `reasonInfo` in method `callSessionHandoverFailed` + MissingNullability: android.telephony.ims.ImsCallSessionListener#callSessionHeld(android.telephony.ims.ImsCallProfile) parameter #0: - Missing nullability on parameter `profile` in method `callSessionHeld` + MissingNullability: android.telephony.ims.ImsCallSessionListener#callSessionHoldFailed(android.telephony.ims.ImsReasonInfo) parameter #0: - Missing nullability on parameter `reasonInfo` in method `callSessionHoldFailed` + MissingNullability: android.telephony.ims.ImsCallSessionListener#callSessionHoldReceived(android.telephony.ims.ImsCallProfile) parameter #0: - Missing nullability on parameter `profile` in method `callSessionHoldReceived` + MissingNullability: android.telephony.ims.ImsCallSessionListener#callSessionInitiated(android.telephony.ims.ImsCallProfile) parameter #0: - Missing nullability on parameter `profile` in method `callSessionInitiated` + MissingNullability: android.telephony.ims.ImsCallSessionListener#callSessionInitiatedFailed(android.telephony.ims.ImsReasonInfo) parameter #0: - Missing nullability on parameter `reasonInfo` in method `callSessionInitiatedFailed` + MissingNullability: android.telephony.ims.ImsCallSessionListener#callSessionInviteParticipantsRequestFailed(android.telephony.ims.ImsReasonInfo) parameter #0: - Missing nullability on parameter `reasonInfo` in method `callSessionInviteParticipantsRequestFailed` + MissingNullability: android.telephony.ims.ImsCallSessionListener#callSessionMergeComplete(android.telephony.ims.stub.ImsCallSessionImplBase) parameter #0: - Missing nullability on parameter `newSession` in method `callSessionMergeComplete` + MissingNullability: android.telephony.ims.ImsCallSessionListener#callSessionMergeFailed(android.telephony.ims.ImsReasonInfo) parameter #0: - Missing nullability on parameter `reasonInfo` in method `callSessionMergeFailed` + MissingNullability: android.telephony.ims.ImsCallSessionListener#callSessionMergeStarted(android.telephony.ims.stub.ImsCallSessionImplBase, android.telephony.ims.ImsCallProfile) parameter #0: - Missing nullability on parameter `newSession` in method `callSessionMergeStarted` + MissingNullability: android.telephony.ims.ImsCallSessionListener#callSessionMergeStarted(android.telephony.ims.stub.ImsCallSessionImplBase, android.telephony.ims.ImsCallProfile) parameter #1: - Missing nullability on parameter `profile` in method `callSessionMergeStarted` + MissingNullability: android.telephony.ims.ImsCallSessionListener#callSessionProgressing(android.telephony.ims.ImsStreamMediaProfile) parameter #0: - Missing nullability on parameter `profile` in method `callSessionProgressing` + MissingNullability: android.telephony.ims.ImsCallSessionListener#callSessionRemoveParticipantsRequestFailed(android.telephony.ims.ImsReasonInfo) parameter #0: - Missing nullability on parameter `reasonInfo` in method `callSessionRemoveParticipantsRequestFailed` + MissingNullability: android.telephony.ims.ImsCallSessionListener#callSessionResumeFailed(android.telephony.ims.ImsReasonInfo) parameter #0: - Missing nullability on parameter `reasonInfo` in method `callSessionResumeFailed` + MissingNullability: android.telephony.ims.ImsCallSessionListener#callSessionResumeReceived(android.telephony.ims.ImsCallProfile) parameter #0: - Missing nullability on parameter `profile` in method `callSessionResumeReceived` + MissingNullability: android.telephony.ims.ImsCallSessionListener#callSessionResumed(android.telephony.ims.ImsCallProfile) parameter #0: - Missing nullability on parameter `profile` in method `callSessionResumed` + MissingNullability: android.telephony.ims.ImsCallSessionListener#callSessionRttMessageReceived(String) parameter #0: - Missing nullability on parameter `rttMessage` in method `callSessionRttMessageReceived` + MissingNullability: android.telephony.ims.ImsCallSessionListener#callSessionRttModifyRequestReceived(android.telephony.ims.ImsCallProfile) parameter #0: - Missing nullability on parameter `callProfile` in method `callSessionRttModifyRequestReceived` + MissingNullability: android.telephony.ims.ImsCallSessionListener#callSessionSuppServiceReceived(android.telephony.ims.ImsSuppServiceNotification) parameter #0: - Missing nullability on parameter `suppSrvNotification` in method `callSessionSuppServiceReceived` + MissingNullability: android.telephony.ims.ImsCallSessionListener#callSessionTerminated(android.telephony.ims.ImsReasonInfo) parameter #0: - Missing nullability on parameter `reasonInfo` in method `callSessionTerminated` + MissingNullability: android.telephony.ims.ImsCallSessionListener#callSessionUpdateFailed(android.telephony.ims.ImsReasonInfo) parameter #0: - Missing nullability on parameter `reasonInfo` in method `callSessionUpdateFailed` + MissingNullability: android.telephony.ims.ImsCallSessionListener#callSessionUpdateReceived(android.telephony.ims.ImsCallProfile) parameter #0: - Missing nullability on parameter `profile` in method `callSessionUpdateReceived` + MissingNullability: android.telephony.ims.ImsCallSessionListener#callSessionUpdated(android.telephony.ims.ImsCallProfile) parameter #0: - Missing nullability on parameter `profile` in method `callSessionUpdated` + MissingNullability: android.telephony.ims.ImsCallSessionListener#callSessionUssdMessageReceived(int, String) parameter #1: - Missing nullability on parameter `ussdMessage` in method `callSessionUssdMessageReceived` + MissingNullability: android.telephony.ims.ImsConferenceState#getConnectionStateForStatus(String) parameter #0: - Missing nullability on parameter `status` in method `getConnectionStateForStatus` + MissingNullability: android.telephony.ims.ImsConferenceState#mParticipants: - Missing nullability on field `mParticipants` in class `class android.telephony.ims.ImsConferenceState` + MissingNullability: android.telephony.ims.ImsConferenceState#writeToParcel(android.os.Parcel, int) parameter #0: - Missing nullability on parameter `out` in method `writeToParcel` + MissingNullability: android.telephony.ims.ImsExternalCallState#writeToParcel(android.os.Parcel, int) parameter #0: - Missing nullability on parameter `out` in method `writeToParcel` + MissingNullability: android.telephony.ims.ImsReasonInfo#ImsReasonInfo(int, int, String) parameter #2: - Missing nullability on parameter `extraMessage` in method `ImsReasonInfo` + MissingNullability: android.telephony.ims.ImsReasonInfo#getExtraMessage(): - Missing nullability on method `getExtraMessage` return + MissingNullability: android.telephony.ims.ImsReasonInfo#writeToParcel(android.os.Parcel, int) parameter #0: - Missing nullability on parameter `out` in method `writeToParcel` + MissingNullability: android.telephony.ims.ImsService#createMmTelFeature(int): - Missing nullability on method `createMmTelFeature` return + MissingNullability: android.telephony.ims.ImsService#createRcsFeature(int): - Missing nullability on method `createRcsFeature` return + MissingNullability: android.telephony.ims.ImsService#getConfig(int): - Missing nullability on method `getConfig` return + MissingNullability: android.telephony.ims.ImsService#getRegistration(int): - Missing nullability on method `getRegistration` return + MissingNullability: android.telephony.ims.ImsService#onUpdateSupportedImsFeatures(android.telephony.ims.stub.ImsFeatureConfiguration) parameter #0: - Missing nullability on parameter `c` in method `onUpdateSupportedImsFeatures` + MissingNullability: android.telephony.ims.ImsService#querySupportedImsFeatures(): - Missing nullability on method `querySupportedImsFeatures` return + MissingNullability: android.telephony.ims.ImsSsData#writeToParcel(android.os.Parcel, int) parameter #0: - Missing nullability on parameter `out` in method `writeToParcel` + MissingNullability: android.telephony.ims.ImsSsInfo#writeToParcel(android.os.Parcel, int) parameter #0: - Missing nullability on parameter `out` in method `writeToParcel` + MissingNullability: android.telephony.ims.ImsStreamMediaProfile#copyFrom(android.telephony.ims.ImsStreamMediaProfile) parameter #0: - Missing nullability on parameter `profile` in method `copyFrom` + MissingNullability: android.telephony.ims.ImsStreamMediaProfile#writeToParcel(android.os.Parcel, int) parameter #0: - Missing nullability on parameter `out` in method `writeToParcel` + MissingNullability: android.telephony.ims.ImsSuppServiceNotification#ImsSuppServiceNotification(int, int, int, int, String, String[]) parameter #4: - Missing nullability on parameter `number` in method `ImsSuppServiceNotification` + MissingNullability: android.telephony.ims.ImsSuppServiceNotification#ImsSuppServiceNotification(int, int, int, int, String, String[]) parameter #5: - Missing nullability on parameter `history` in method `ImsSuppServiceNotification` + MissingNullability: android.telephony.ims.ImsSuppServiceNotification#history: - Missing nullability on field `history` in class `class android.telephony.ims.ImsSuppServiceNotification` + MissingNullability: android.telephony.ims.ImsSuppServiceNotification#number: - Missing nullability on field `number` in class `class android.telephony.ims.ImsSuppServiceNotification` + MissingNullability: android.telephony.ims.ImsSuppServiceNotification#writeToParcel(android.os.Parcel, int) parameter #0: - Missing nullability on parameter `out` in method `writeToParcel` + MissingNullability: android.telephony.ims.ImsUtListener#onSupplementaryServiceIndication(android.telephony.ims.ImsSsData) parameter #0: - Missing nullability on parameter `ssData` in method `onSupplementaryServiceIndication` + MissingNullability: android.telephony.ims.ImsUtListener#onUtConfigurationCallBarringQueried(int, android.telephony.ims.ImsSsInfo[]) parameter #1: - Missing nullability on parameter `cbInfo` in method `onUtConfigurationCallBarringQueried` + MissingNullability: android.telephony.ims.ImsUtListener#onUtConfigurationCallForwardQueried(int, android.telephony.ims.ImsCallForwardInfo[]) parameter #1: - Missing nullability on parameter `cfInfo` in method `onUtConfigurationCallForwardQueried` + MissingNullability: android.telephony.ims.ImsUtListener#onUtConfigurationCallWaitingQueried(int, android.telephony.ims.ImsSsInfo[]) parameter #1: - Missing nullability on parameter `cwInfo` in method `onUtConfigurationCallWaitingQueried` + MissingNullability: android.telephony.ims.ImsUtListener#onUtConfigurationQueried(int, android.os.Bundle) parameter #1: - Missing nullability on parameter `configuration` in method `onUtConfigurationQueried` + MissingNullability: android.telephony.ims.ImsUtListener#onUtConfigurationQueryFailed(int, android.telephony.ims.ImsReasonInfo) parameter #1: - Missing nullability on parameter `error` in method `onUtConfigurationQueryFailed` + MissingNullability: android.telephony.ims.ImsUtListener#onUtConfigurationUpdateFailed(int, android.telephony.ims.ImsReasonInfo) parameter #1: - Missing nullability on parameter `error` in method `onUtConfigurationUpdateFailed` + MissingNullability: android.telephony.ims.ImsVideoCallProvider#changeCameraCapabilities(android.telecom.VideoProfile.CameraCapabilities) parameter #0: - Missing nullability on parameter `CameraCapabilities` in method `changeCameraCapabilities` + MissingNullability: android.telephony.ims.ImsVideoCallProvider#onSendSessionModifyRequest(android.telecom.VideoProfile, android.telecom.VideoProfile) parameter #0: - Missing nullability on parameter `fromProfile` in method `onSendSessionModifyRequest` + MissingNullability: android.telephony.ims.ImsVideoCallProvider#onSendSessionModifyRequest(android.telecom.VideoProfile, android.telecom.VideoProfile) parameter #1: - Missing nullability on parameter `toProfile` in method `onSendSessionModifyRequest` + MissingNullability: android.telephony.ims.ImsVideoCallProvider#onSendSessionModifyResponse(android.telecom.VideoProfile) parameter #0: - Missing nullability on parameter `responseProfile` in method `onSendSessionModifyResponse` + MissingNullability: android.telephony.ims.ImsVideoCallProvider#onSetCamera(String) parameter #0: - Missing nullability on parameter `cameraId` in method `onSetCamera` + MissingNullability: android.telephony.ims.ImsVideoCallProvider#onSetCamera(String, int) parameter #0: - Missing nullability on parameter `cameraId` in method `onSetCamera` + MissingNullability: android.telephony.ims.ImsVideoCallProvider#onSetDisplaySurface(android.view.Surface) parameter #0: - Missing nullability on parameter `surface` in method `onSetDisplaySurface` + MissingNullability: android.telephony.ims.ImsVideoCallProvider#onSetPauseImage(android.net.Uri) parameter #0: - Missing nullability on parameter `uri` in method `onSetPauseImage` + MissingNullability: android.telephony.ims.ImsVideoCallProvider#onSetPreviewSurface(android.view.Surface) parameter #0: - Missing nullability on parameter `surface` in method `onSetPreviewSurface` + MissingNullability: android.telephony.ims.ImsVideoCallProvider#receiveSessionModifyRequest(android.telecom.VideoProfile) parameter #0: - Missing nullability on parameter `VideoProfile` in method `receiveSessionModifyRequest` + MissingNullability: android.telephony.ims.ImsVideoCallProvider#receiveSessionModifyResponse(int, android.telecom.VideoProfile, android.telecom.VideoProfile) parameter #1: - Missing nullability on parameter `requestedProfile` in method `receiveSessionModifyResponse` + MissingNullability: android.telephony.ims.ImsVideoCallProvider#receiveSessionModifyResponse(int, android.telecom.VideoProfile, android.telecom.VideoProfile) parameter #2: - Missing nullability on parameter `responseProfile` in method `receiveSessionModifyResponse` + MissingNullability: android.telephony.ims.feature.CapabilityChangeRequest#getCapabilitiesToDisable(): - Missing nullability on method `getCapabilitiesToDisable` return + MissingNullability: android.telephony.ims.feature.CapabilityChangeRequest#getCapabilitiesToEnable(): - Missing nullability on method `getCapabilitiesToEnable` return + MissingNullability: android.telephony.ims.feature.CapabilityChangeRequest#writeToParcel(android.os.Parcel, int) parameter #0: - Missing nullability on parameter `dest` in method `writeToParcel` + MissingNullability: android.telephony.ims.feature.ImsFeature#changeEnabledCapabilities(android.telephony.ims.feature.CapabilityChangeRequest, android.telephony.ims.feature.ImsFeature.CapabilityCallbackProxy) parameter #0: - Missing nullability on parameter `request` in method `changeEnabledCapabilities` + MissingNullability: android.telephony.ims.feature.ImsFeature#changeEnabledCapabilities(android.telephony.ims.feature.CapabilityChangeRequest, android.telephony.ims.feature.ImsFeature.CapabilityCallbackProxy) parameter #1: - Missing nullability on parameter `c` in method `changeEnabledCapabilities` + MissingNullability: android.telephony.ims.feature.MmTelFeature#queryCapabilityStatus(): - Missing nullability on method `queryCapabilityStatus` return + MissingNullability: android.telephony.ims.feature.MmTelFeature.MmTelCapabilities#MmTelCapabilities(android.telephony.ims.feature.ImsFeature.Capabilities) parameter #0: - Missing nullability on parameter `c` in method `MmTelCapabilities` + MissingNullability: android.telephony.ims.stub.ImsCallSessionImplBase#accept(int, android.telephony.ims.ImsStreamMediaProfile) parameter #1: - Missing nullability on parameter `profile` in method `accept` + MissingNullability: android.telephony.ims.stub.ImsCallSessionImplBase#deflect(String) parameter #0: - Missing nullability on parameter `deflectNumber` in method `deflect` + MissingNullability: android.telephony.ims.stub.ImsCallSessionImplBase#extendToConference(String[]) parameter #0: - Missing nullability on parameter `participants` in method `extendToConference` + MissingNullability: android.telephony.ims.stub.ImsCallSessionImplBase#getCallId(): - Missing nullability on method `getCallId` return + MissingNullability: android.telephony.ims.stub.ImsCallSessionImplBase#getCallProfile(): - Missing nullability on method `getCallProfile` return + MissingNullability: android.telephony.ims.stub.ImsCallSessionImplBase#getImsVideoCallProvider(): - Missing nullability on method `getImsVideoCallProvider` return + MissingNullability: android.telephony.ims.stub.ImsCallSessionImplBase#getLocalCallProfile(): - Missing nullability on method `getLocalCallProfile` return + MissingNullability: android.telephony.ims.stub.ImsCallSessionImplBase#getProperty(String): - Missing nullability on method `getProperty` return + MissingNullability: android.telephony.ims.stub.ImsCallSessionImplBase#getProperty(String) parameter #0: - Missing nullability on parameter `name` in method `getProperty` + MissingNullability: android.telephony.ims.stub.ImsCallSessionImplBase#getRemoteCallProfile(): - Missing nullability on method `getRemoteCallProfile` return + MissingNullability: android.telephony.ims.stub.ImsCallSessionImplBase#hold(android.telephony.ims.ImsStreamMediaProfile) parameter #0: - Missing nullability on parameter `profile` in method `hold` + MissingNullability: android.telephony.ims.stub.ImsCallSessionImplBase#inviteParticipants(String[]) parameter #0: - Missing nullability on parameter `participants` in method `inviteParticipants` + MissingNullability: android.telephony.ims.stub.ImsCallSessionImplBase#removeParticipants(String[]) parameter #0: - Missing nullability on parameter `participants` in method `removeParticipants` + MissingNullability: android.telephony.ims.stub.ImsCallSessionImplBase#resume(android.telephony.ims.ImsStreamMediaProfile) parameter #0: - Missing nullability on parameter `profile` in method `resume` + MissingNullability: android.telephony.ims.stub.ImsCallSessionImplBase#sendDtmf(char, android.os.Message) parameter #1: - Missing nullability on parameter `result` in method `sendDtmf` + MissingNullability: android.telephony.ims.stub.ImsCallSessionImplBase#sendRttMessage(String) parameter #0: - Missing nullability on parameter `rttMessage` in method `sendRttMessage` + MissingNullability: android.telephony.ims.stub.ImsCallSessionImplBase#sendRttModifyRequest(android.telephony.ims.ImsCallProfile) parameter #0: - Missing nullability on parameter `toProfile` in method `sendRttModifyRequest` + MissingNullability: android.telephony.ims.stub.ImsCallSessionImplBase#sendUssd(String) parameter #0: - Missing nullability on parameter `ussdMessage` in method `sendUssd` + MissingNullability: android.telephony.ims.stub.ImsCallSessionImplBase#setListener(android.telephony.ims.ImsCallSessionListener) parameter #0: - Missing nullability on parameter `listener` in method `setListener` + MissingNullability: android.telephony.ims.stub.ImsCallSessionImplBase#start(String, android.telephony.ims.ImsCallProfile) parameter #0: - Missing nullability on parameter `callee` in method `start` + MissingNullability: android.telephony.ims.stub.ImsCallSessionImplBase#start(String, android.telephony.ims.ImsCallProfile) parameter #1: - Missing nullability on parameter `profile` in method `start` + MissingNullability: android.telephony.ims.stub.ImsCallSessionImplBase#startConference(String[], android.telephony.ims.ImsCallProfile) parameter #0: - Missing nullability on parameter `participants` in method `startConference` + MissingNullability: android.telephony.ims.stub.ImsCallSessionImplBase#startConference(String[], android.telephony.ims.ImsCallProfile) parameter #1: - Missing nullability on parameter `profile` in method `startConference` + MissingNullability: android.telephony.ims.stub.ImsCallSessionImplBase#update(int, android.telephony.ims.ImsStreamMediaProfile) parameter #1: - Missing nullability on parameter `profile` in method `update` + MissingNullability: android.telephony.ims.stub.ImsCallSessionImplBase.State#toString(int): - Missing nullability on method `toString` return + MissingNullability: android.telephony.ims.stub.ImsConfigImplBase#getConfigString(int): - Missing nullability on method `getConfigString` return + MissingNullability: android.telephony.ims.stub.ImsConfigImplBase#notifyProvisionedValueChanged(int, String) parameter #1: - Missing nullability on parameter `value` in method `notifyProvisionedValueChanged` + MissingNullability: android.telephony.ims.stub.ImsConfigImplBase#setConfig(int, String) parameter #1: - Missing nullability on parameter `value` in method `setConfig` + MissingNullability: android.telephony.ims.stub.ImsFeatureConfiguration#getServiceFeatures(): - Missing nullability on method `getServiceFeatures` return + MissingNullability: android.telephony.ims.stub.ImsFeatureConfiguration#writeToParcel(android.os.Parcel, int) parameter #0: - Missing nullability on parameter `dest` in method `writeToParcel` + MissingNullability: android.telephony.ims.stub.ImsFeatureConfiguration.Builder#addFeature(int, int): - Missing nullability on method `addFeature` return + MissingNullability: android.telephony.ims.stub.ImsFeatureConfiguration.Builder#build(): - Missing nullability on method `build` return + MissingNullability: android.telephony.ims.stub.ImsMultiEndpointImplBase#onImsExternalCallStateUpdate(java.util.List<android.telephony.ims.ImsExternalCallState>) parameter #0: - Missing nullability on parameter `externalCallDialogs` in method `onImsExternalCallStateUpdate` + MissingNullability: android.telephony.ims.stub.ImsRegistrationImplBase#onDeregistered(android.telephony.ims.ImsReasonInfo) parameter #0: - Missing nullability on parameter `info` in method `onDeregistered` + MissingNullability: android.telephony.ims.stub.ImsRegistrationImplBase#onSubscriberAssociatedUriChanged(android.net.Uri[]) parameter #0: - Missing nullability on parameter `uris` in method `onSubscriberAssociatedUriChanged` + MissingNullability: android.telephony.ims.stub.ImsRegistrationImplBase#onTechnologyChangeFailed(int, android.telephony.ims.ImsReasonInfo) parameter #1: - Missing nullability on parameter `info` in method `onTechnologyChangeFailed` + MissingNullability: android.telephony.ims.stub.ImsSmsImplBase#getSmsFormat(): - Missing nullability on method `getSmsFormat` return + MissingNullability: android.telephony.ims.stub.ImsSmsImplBase#onSmsReceived(int, String, byte[]) parameter #1: - Missing nullability on parameter `format` in method `onSmsReceived` + MissingNullability: android.telephony.ims.stub.ImsSmsImplBase#onSmsReceived(int, String, byte[]) parameter #2: - Missing nullability on parameter `pdu` in method `onSmsReceived` + MissingNullability: android.telephony.ims.stub.ImsSmsImplBase#onSmsStatusReportReceived(int, String, byte[]) parameter #1: - Missing nullability on parameter `format` in method `onSmsStatusReportReceived` + MissingNullability: android.telephony.ims.stub.ImsSmsImplBase#onSmsStatusReportReceived(int, String, byte[]) parameter #2: - Missing nullability on parameter `pdu` in method `onSmsStatusReportReceived` + MissingNullability: android.telephony.ims.stub.ImsSmsImplBase#onSmsStatusReportReceived(int, int, String, byte[]) parameter #2: - Missing nullability on parameter `format` in method `onSmsStatusReportReceived` + MissingNullability: android.telephony.ims.stub.ImsSmsImplBase#onSmsStatusReportReceived(int, int, String, byte[]) parameter #3: - Missing nullability on parameter `pdu` in method `onSmsStatusReportReceived` + MissingNullability: android.telephony.ims.stub.ImsSmsImplBase#sendSms(int, int, String, String, boolean, byte[]) parameter #2: - Missing nullability on parameter `format` in method `sendSms` + MissingNullability: android.telephony.ims.stub.ImsSmsImplBase#sendSms(int, int, String, String, boolean, byte[]) parameter #3: - Missing nullability on parameter `smsc` in method `sendSms` + MissingNullability: android.telephony.ims.stub.ImsSmsImplBase#sendSms(int, int, String, String, boolean, byte[]) parameter #5: - Missing nullability on parameter `pdu` in method `sendSms` + MissingNullability: android.telephony.ims.stub.ImsUtImplBase#queryCallForward(int, String) parameter #1: - Missing nullability on parameter `number` in method `queryCallForward` + MissingNullability: android.telephony.ims.stub.ImsUtImplBase#setListener(android.telephony.ims.ImsUtListener) parameter #0: - Missing nullability on parameter `listener` in method `setListener` + MissingNullability: android.telephony.ims.stub.ImsUtImplBase#transact(android.os.Bundle) parameter #0: - Missing nullability on parameter `ssInfo` in method `transact` + MissingNullability: android.telephony.ims.stub.ImsUtImplBase#updateCallBarring(int, int, String[]) parameter #2: - Missing nullability on parameter `barrList` in method `updateCallBarring` + MissingNullability: android.telephony.ims.stub.ImsUtImplBase#updateCallBarringForServiceClass(int, int, String[], int) parameter #2: - Missing nullability on parameter `barrList` in method `updateCallBarringForServiceClass` + MissingNullability: android.telephony.ims.stub.ImsUtImplBase#updateCallForward(int, int, String, int, int) parameter #2: - Missing nullability on parameter `number` in method `updateCallForward` + MissingNullability: android.telephony.mbms.DownloadRequest.Builder#setServiceId(String): MissingNullability: android.telephony.mbms.DownloadRequest.Builder#setServiceId(String) parameter #0: @@ -2278,7 +2280,7 @@ NotCloseable: android.app.prediction.AppPredictor: NotCloseable: android.os.HwParcel: NotCloseable: android.telephony.ims.stub.ImsUtImplBase: - Classes that release resources (close()) should implement AutoClosable and CloseGuard: class android.telephony.ims.stub.ImsUtImplBase + OnNameExpected: android.service.autofill.augmented.AugmentedAutofillService#dump(java.io.PrintWriter, String[]): @@ -2292,21 +2294,21 @@ OnNameExpected: android.service.notification.NotificationAssistantService#attach OnNameExpected: android.service.quicksettings.TileService#isQuickSettingsSupported(): OnNameExpected: android.telephony.ims.ImsService#createMmTelFeature(int): - If implemented by developer, should follow the on<Something> style; otherwise consider marking final + OnNameExpected: android.telephony.ims.ImsService#createRcsFeature(int): - If implemented by developer, should follow the on<Something> style; otherwise consider marking final + OnNameExpected: android.telephony.ims.ImsService#disableIms(int): - If implemented by developer, should follow the on<Something> style; otherwise consider marking final + OnNameExpected: android.telephony.ims.ImsService#enableIms(int): - If implemented by developer, should follow the on<Something> style; otherwise consider marking final + OnNameExpected: android.telephony.ims.ImsService#getConfig(int): - If implemented by developer, should follow the on<Something> style; otherwise consider marking final + OnNameExpected: android.telephony.ims.ImsService#getRegistration(int): - If implemented by developer, should follow the on<Something> style; otherwise consider marking final + OnNameExpected: android.telephony.ims.ImsService#querySupportedImsFeatures(): - If implemented by developer, should follow the on<Something> style; otherwise consider marking final + OnNameExpected: android.telephony.ims.ImsService#readyForFeatureCreation(): - If implemented by developer, should follow the on<Something> style; otherwise consider marking final + OnNameExpected: android.telephony.mbms.vendor.MbmsGroupCallServiceBase#dispose(int): OnNameExpected: android.telephony.mbms.vendor.MbmsGroupCallServiceBase#initialize(android.telephony.mbms.MbmsGroupCallSessionCallback, int): @@ -2446,7 +2448,7 @@ RethrowRemoteException: android.os.HwBinder#transact(int, android.os.HwParcel, a RethrowRemoteException: android.os.IHwBinder#transact(int, android.os.HwParcel, android.os.HwParcel, int): RethrowRemoteException: android.telephony.ims.ImsService#onUpdateSupportedImsFeatures(android.telephony.ims.stub.ImsFeatureConfiguration): - Methods calling system APIs should rethrow `RemoteException` as `RuntimeException` (but do not list it in the throws clause) + RethrowRemoteException: android.telephony.mbms.vendor.MbmsDownloadServiceBase#addProgressListener(android.telephony.mbms.DownloadRequest, android.telephony.mbms.DownloadProgressListener): RethrowRemoteException: android.telephony.mbms.vendor.MbmsDownloadServiceBase#addStatusListener(android.telephony.mbms.DownloadRequest, android.telephony.mbms.DownloadStatusListener): @@ -2526,7 +2528,7 @@ SamShouldBeLast: android.service.autofill.ImageTransformation#apply(android.serv SamShouldBeLast: android.service.autofill.InternalTransformation#batchApply(android.service.autofill.ValueFinder, android.widget.RemoteViews, java.util.ArrayList<android.util.Pair<java.lang.Integer,android.service.autofill.InternalTransformation>>): SamShouldBeLast: android.telephony.ims.ImsMmTelManager#getFeatureState(java.util.function.Consumer<java.lang.Integer>, java.util.concurrent.Executor): - SAM-compatible parameters (such as parameter 1, "callback", in android.telephony.ims.ImsMmTelManager.getFeatureState) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions + SamShouldBeLast: android.view.Choreographer#postCallback(int, Runnable, Object): SamShouldBeLast: android.view.Choreographer#postCallbackDelayed(int, Runnable, Object, long): @@ -2599,6 +2601,8 @@ UserHandle: android.app.role.RoleManager#removeOnRoleHoldersChangedListenerAsUse UserHandle: android.app.role.RoleManager#removeRoleHolderAsUser(String, String, int, android.os.UserHandle, java.util.concurrent.Executor, java.util.function.Consumer<java.lang.Boolean>): +UserHandle: android.companion.CompanionDeviceManager#isDeviceAssociated(String, android.net.MacAddress, android.os.UserHandle): + When a method overload is needed to target a specific UserHandle, callers should be directed to use Context.createPackageContextAsUser() and re-obtain the relevant Manager, and no new API should be added UserHandle: android.content.pm.PackageManager#getInstallReason(String, android.os.UserHandle): UserHandle: android.content.pm.PackageManager#getPermissionFlags(String, String, android.os.UserHandle): diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java index 22e1d0187f01..bdb83804d903 100644 --- a/cmds/am/src/com/android/commands/am/Am.java +++ b/cmds/am/src/com/android/commands/am/Am.java @@ -175,7 +175,11 @@ public class Am extends BaseCommand { } else if (opt.equals("--no-hidden-api-checks")) { instrument.disableHiddenApiChecks = true; } else if (opt.equals("--no-test-api-checks")) { - instrument.disableTestApiChecks = true; + // TODO(satayev): remove this option, only kept for backwards compatibility with + // cached tradefed instance + instrument.disableTestApiChecks = false; + } else if (opt.equals("--no-test-api-access")) { + instrument.disableTestApiChecks = false; } else if (opt.equals("--no-isolated-storage")) { instrument.disableIsolatedStorage = true; } else if (opt.equals("--user")) { diff --git a/cmds/am/src/com/android/commands/am/Instrument.java b/cmds/am/src/com/android/commands/am/Instrument.java index 6afd7c40c1c1..2adbc1f6e1ae 100644 --- a/cmds/am/src/com/android/commands/am/Instrument.java +++ b/cmds/am/src/com/android/commands/am/Instrument.java @@ -86,7 +86,7 @@ public class Instrument { String logPath = null; public boolean noWindowAnimation = false; public boolean disableHiddenApiChecks = false; - public boolean disableTestApiChecks = false; + public boolean disableTestApiChecks = true; public boolean disableIsolatedStorage = false; public String abi = null; public int userId = UserHandle.USER_CURRENT; diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index b9051daab6ae..155f26b295d1 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -5798,10 +5798,10 @@ message PermissionGrantRequestResultReported { optional int64 request_id = 1; // UID of package requesting the permission grant - optional int32 requesting_uid = 2 [(is_uid) = true]; + optional int32 uid = 2 [(is_uid) = true]; // Name of package requesting the permission grant - optional string requesting_package_name = 3; + optional string package_name = 3; // The permission to be granted optional string permission_name = 4; diff --git a/cmds/telecom/src/com/android/commands/telecom/Telecom.java b/cmds/telecom/src/com/android/commands/telecom/Telecom.java index a4b30587469f..03b08f785f74 100644 --- a/cmds/telecom/src/com/android/commands/telecom/Telecom.java +++ b/cmds/telecom/src/com/android/commands/telecom/Telecom.java @@ -58,7 +58,6 @@ public final class Telecom extends BaseCommand { private static final String COMMAND_SET_TEST_CALL_SCREENING_APP = "set-test-call-screening-app"; 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"; @@ -99,7 +98,6 @@ public final class Telecom extends BaseCommand { + "<USER_SN>\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>" @@ -191,9 +189,6 @@ public final class Telecom extends BaseCommand { case COMMAND_ADD_OR_REMOVE_CALL_COMPANION_APP: runAddOrRemoveCallCompanionApp(); break; - case COMMAND_SET_TEST_AUTO_MODE_APP: - runSetTestAutoModeApp(); - break; case COMMAND_SET_PHONE_ACCOUNT_SUGGESTION_COMPONENT: runSetTestPhoneAcctSuggestionComponent(); break; @@ -305,11 +300,6 @@ public final class Telecom extends BaseCommand { mTelecomService.addOrRemoveTestCallCompanionApp(packageName, isAddedBool); } - private void runSetTestAutoModeApp() throws RemoteException { - final String packageName = nextArg(); - mTelecomService.setTestAutoModeApp(packageName); - } - private void runSetTestPhoneAcctSuggestionComponent() throws RemoteException { final String componentName = nextArg(); mTelecomService.setTestPhoneAcctSuggestionComponent(componentName); diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java index 3db0b2612c78..7fd01dbf95fb 100644 --- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java +++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java @@ -20,6 +20,8 @@ import static android.content.pm.PackageManager.FEATURE_FINGERPRINT; import android.annotation.IntDef; import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.UnsupportedAppUsage; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledAfter; @@ -512,6 +514,21 @@ public class AccessibilityServiceInfo implements Parcelable { private static final long REQUEST_ACCESSIBILITY_BUTTON_CHANGE = 136293963L; /** + * Resource id of the animated image of the accessibility service. + */ + private int mAnimatedImageRes; + + /** + * Resource id of the html description of the accessibility service. + */ + private int mHtmlDescriptionRes; + + /** + * Non localized html description of the accessibility service. + */ + private String mNonLocalizedHtmlDescription; + + /** * Creates a new instance. */ public AccessibilityServiceInfo() { @@ -626,6 +643,20 @@ public class AccessibilityServiceInfo implements Parcelable { mNonLocalizedSummary = nonLocalizedSummary.toString().trim(); } } + peekedValue = asAttributes.peekValue( + com.android.internal.R.styleable.AccessibilityService_animatedImageDrawable); + if (peekedValue != null) { + mAnimatedImageRes = peekedValue.resourceId; + } + peekedValue = asAttributes.peekValue( + com.android.internal.R.styleable.AccessibilityService_htmlDescription); + if (peekedValue != null) { + mHtmlDescriptionRes = peekedValue.resourceId; + final CharSequence nonLocalizedHtmlDescription = peekedValue.coerceToString(); + if (nonLocalizedHtmlDescription != null) { + mNonLocalizedHtmlDescription = nonLocalizedHtmlDescription.toString().trim(); + } + } asAttributes.recycle(); } catch (NameNotFoundException e) { throw new XmlPullParserException( "Unable to create context for: " @@ -727,6 +758,18 @@ public class AccessibilityServiceInfo implements Parcelable { } /** + * The animated image resource id. + * <p> + * <strong>Statically set from + * {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong> + * </p> + * @return The animated image resource id. + */ + public int getAnimatedImageRes() { + return mAnimatedImageRes; + } + + /** * Whether this service can retrieve the current window's content. * <p> * <strong>Statically set from @@ -833,6 +876,29 @@ public class AccessibilityServiceInfo implements Parcelable { } /** + * The localized html description of the accessibility service. + * <p> + * <strong>Statically set from + * {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong> + * </p> + * @return The localized html description. + */ + @Nullable + public String loadHtmlDescription(@NonNull PackageManager packageManager) { + if (mHtmlDescriptionRes == 0) { + return mNonLocalizedHtmlDescription; + } + + final ServiceInfo serviceInfo = mResolveInfo.serviceInfo; + final CharSequence htmlDescription = packageManager.getText(serviceInfo.packageName, + mHtmlDescriptionRes, serviceInfo.applicationInfo); + if (htmlDescription != null) { + return htmlDescription.toString().trim(); + } + return null; + } + + /** * Set the recommended time that non-interactive controls need to remain on the screen to * support the user. * <p> @@ -915,7 +981,10 @@ public class AccessibilityServiceInfo implements Parcelable { parcel.writeInt(mSummaryResId); parcel.writeString(mNonLocalizedSummary); parcel.writeInt(mDescriptionResId); + parcel.writeInt(mAnimatedImageRes); + parcel.writeInt(mHtmlDescriptionRes); parcel.writeString(mNonLocalizedDescription); + parcel.writeString(mNonLocalizedHtmlDescription); } private void initFromParcel(Parcel parcel) { @@ -934,7 +1003,10 @@ public class AccessibilityServiceInfo implements Parcelable { mSummaryResId = parcel.readInt(); mNonLocalizedSummary = parcel.readString(); mDescriptionResId = parcel.readInt(); + mAnimatedImageRes = parcel.readInt(); + mHtmlDescriptionRes = parcel.readInt(); mNonLocalizedDescription = parcel.readString(); + mNonLocalizedHtmlDescription = parcel.readString(); } @Override diff --git a/core/java/android/annotation/UnsupportedAppUsage.java b/core/java/android/annotation/UnsupportedAppUsage.java index 204d71d93cd8..1af48cb63079 100644 --- a/core/java/android/annotation/UnsupportedAppUsage.java +++ b/core/java/android/annotation/UnsupportedAppUsage.java @@ -148,6 +148,18 @@ public @interface UnsupportedAppUsage { String publicAlternatives() default ""; /** + * Override the default source position when generating an index of the annotations. + * + * <p>This is intended for use by tools that generate java source code, to point to the + * original source position of the annotation, rather than the position within the generated + * code. It should never be set manually. + * + * <p>The format of the value is "path/to/file:startline:startcol:endline:endcol" indicating + * the position of the annotation itself. + */ + String overrideSourcePosition() default ""; + + /** * Container for {@link UnsupportedAppUsage} that allows it to be applied repeatedly to types. */ @Retention(CLASS) diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 0113f6912183..034826a8d5fa 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -39,6 +39,7 @@ import android.content.pm.IPackageDeleteObserver; import android.content.pm.IPackageManager; import android.content.pm.IPackageMoveObserver; import android.content.pm.IPackageStatsObserver; +import android.content.pm.InstallSourceInfo; import android.content.pm.InstantAppInfo; import android.content.pm.InstrumentationInfo; import android.content.pm.IntentFilterVerificationInfo; @@ -2085,6 +2086,21 @@ public class ApplicationPackageManager extends PackageManager { } @Override + @NonNull + public InstallSourceInfo getInstallSourceInfo(String packageName) throws NameNotFoundException { + final InstallSourceInfo installSourceInfo; + try { + installSourceInfo = mPM.getInstallSourceInfo(packageName); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + if (installSourceInfo == null) { + throw new NameNotFoundException(packageName); + } + return installSourceInfo; + } + + @Override public int getMoveStatus(int moveId) { try { return mPM.getMoveStatus(moveId); @@ -2782,7 +2798,7 @@ public class ApplicationPackageManager extends PackageManager { public Drawable loadUnbadgedItemIcon(@NonNull PackageItemInfo itemInfo, @Nullable ApplicationInfo appInfo) { if (itemInfo.showUserIcon != UserHandle.USER_NULL) { - // Indicates itemInfo is for a different user (e.g. a profile's parent), so use a + // Indicates itemInfo is for a different user (e.g. a profile's parent), so use a // generic user icon (users generally lack permission to view each other's actual icons) int targetUserId = itemInfo.showUserIcon; return UserIcons.getDefaultUserIcon( diff --git a/core/java/android/app/IntentService.java b/core/java/android/app/IntentService.java index 11c747f5db17..74fb99a0909f 100644 --- a/core/java/android/app/IntentService.java +++ b/core/java/android/app/IntentService.java @@ -44,13 +44,6 @@ import android.os.Message; * long as necessary (and will not block the application's main loop), but * only one request will be processed at a time. * - * <p class="note"><b>Note:</b> IntentService is subject to all the - * <a href="/preview/features/background.html">background execution limits</a> - * imposed with Android 8.0 (API level 26). In most cases, you are better off - * using {@link android.support.v4.app.JobIntentService}, which uses jobs - * instead of services when running on Android 8.0 or higher. - * </p> - * * <div class="special reference"> * <h3>Developer Guides</h3> * <p>For a detailed discussion about how to create services, read the @@ -59,8 +52,14 @@ import android.os.Message; * </div> * * @see android.support.v4.app.JobIntentService - * @see android.os.AsyncTask + * + * @deprecated IntentService is subject to all the + * <a href="/preview/features/background.html">background execution limits</a> + * imposed with Android 8.0 (API level 26). Consider using {@link androidx.work.WorkManager} + * or {@link androidx.core.app.JobIntentService}, which uses jobs + * instead of services when running on Android 8.0 or higher. */ +@Deprecated public abstract class IntentService extends Service { private volatile Looper mServiceLooper; @UnsupportedAppUsage diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java index 1f91b3f431a1..9b62e3b3af98 100644 --- a/core/java/android/app/Service.java +++ b/core/java/android/app/Service.java @@ -57,7 +57,7 @@ import java.lang.annotation.RetentionPolicy; * networking) operations, it should spawn its own thread in which to do that * work. More information on this can be found in * <a href="{@docRoot}guide/topics/fundamentals/processes-and-threads.html">Processes and - * Threads</a>. The {@link IntentService} class is available + * Threads</a>. The {@link androidx.core.app.JobIntentService} class is available * as a standard implementation of Service that has its own thread where it * schedules its work to be done.</p> * diff --git a/core/java/android/app/admin/DeviceAdminInfo.java b/core/java/android/app/admin/DeviceAdminInfo.java index 00903c43b291..63bc40b86aa7 100644 --- a/core/java/android/app/admin/DeviceAdminInfo.java +++ b/core/java/android/app/admin/DeviceAdminInfo.java @@ -55,6 +55,14 @@ public final class DeviceAdminInfo implements Parcelable { static final String TAG = "DeviceAdminInfo"; /** + * A type of policy that this device admin can use: profile owner on an organization-owned + * device. + * + * @hide + */ + public static final int USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER = -3; + + /** * A type of policy that this device admin can use: device owner meta-policy * for an admin that is designated as owner of the device. * diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 39dc51e307f1..34ceb08f39bf 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -9135,7 +9135,14 @@ public class DevicePolicyManager { } /** - * Called by device owner to get the MAC address of the Wi-Fi device. + * Called by device owner, or profile owner on organization-owned device, to get the MAC + * address of the Wi-Fi device. + * + * NOTE: The MAC address returned here should only be used for inventory management and is + * not likely to be the MAC address used by the device to connect to Wi-Fi networks: MAC + * addresses used for scanning and connecting to Wi-Fi networks are randomized by default. + * To get the randomized MAC address used, call + * {@link android.net.wifi.WifiConfiguration#getRandomizedMacAddress}. * * @param admin Which device owner this request is associated with. * @return the MAC address of the Wi-Fi device, or null when the information is not available. diff --git a/core/java/android/app/prediction/AppTargetEvent.java b/core/java/android/app/prediction/AppTargetEvent.java index 26ff0c17da2e..f519145c4aa8 100644 --- a/core/java/android/app/prediction/AppTargetEvent.java +++ b/core/java/android/app/prediction/AppTargetEvent.java @@ -38,7 +38,7 @@ public final class AppTargetEvent implements Parcelable { /** * @hide */ - @IntDef({ACTION_LAUNCH, ACTION_DISMISS, ACTION_PIN}) + @IntDef({ACTION_LAUNCH, ACTION_DISMISS, ACTION_PIN, ACTION_UNPIN}) @Retention(RetentionPolicy.SOURCE) public @interface ActionType {} @@ -57,6 +57,11 @@ public final class AppTargetEvent implements Parcelable { */ public static final int ACTION_PIN = 3; + /** + * Event type constant indicating an app target has been un-pinned. + */ + public static final int ACTION_UNPIN = 4; + private final AppTarget mTarget; private final String mLocation; private final int mAction; diff --git a/core/java/android/app/timezonedetector/TimeZoneDetector.java b/core/java/android/app/timezonedetector/TimeZoneDetector.java index 909cbc2ccdf7..387a36bba608 100644 --- a/core/java/android/app/timezonedetector/TimeZoneDetector.java +++ b/core/java/android/app/timezonedetector/TimeZoneDetector.java @@ -17,6 +17,7 @@ package android.app.timezonedetector; import android.annotation.NonNull; +import android.annotation.RequiresPermission; import android.annotation.SystemService; import android.content.Context; import android.os.RemoteException; @@ -26,10 +27,11 @@ import android.util.Log; /** * The interface through which system components can send signals to the TimeZoneDetectorService. + * * @hide */ @SystemService(Context.TIME_ZONE_DETECTOR_SERVICE) -public final class TimeZoneDetector { +public class TimeZoneDetector { private static final String TAG = "timezonedetector.TimeZoneDetector"; private static final boolean DEBUG = false; @@ -41,10 +43,11 @@ public final class TimeZoneDetector { } /** - * Suggests the current time zone to the detector. The detector may ignore the signal if better - * signals are available such as those that come from more reliable sources or were - * determined more recently. + * Suggests the current time zone, determined using telephony signals, to the detector. The + * detector may ignore the signal based on system settings, whether better information is + * available, and so on. */ + @RequiresPermission(android.Manifest.permission.SET_TIME_ZONE) public void suggestPhoneTimeZone(@NonNull PhoneTimeZoneSuggestion timeZoneSuggestion) { if (DEBUG) { Log.d(TAG, "suggestPhoneTimeZone called: " + timeZoneSuggestion); @@ -56,4 +59,28 @@ public final class TimeZoneDetector { } } + /** + * Suggests the current time zone, determined for the user's manually information, to the + * detector. The detector may ignore the signal based on system settings. + */ + @RequiresPermission(android.Manifest.permission.SET_TIME_ZONE) + public void suggestManualTimeZone(@NonNull ManualTimeZoneSuggestion timeZoneSuggestion) { + if (DEBUG) { + Log.d(TAG, "suggestManualTimeZone called: " + timeZoneSuggestion); + } + try { + mITimeZoneDetectorService.suggestManualTimeZone(timeZoneSuggestion); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * A shared utility method to create a {@link ManualTimeZoneSuggestion}. + */ + public static ManualTimeZoneSuggestion createManualTimeZoneSuggestion(String tzId, String why) { + ManualTimeZoneSuggestion suggestion = new ManualTimeZoneSuggestion(tzId); + suggestion.addDebugInfo(why); + return suggestion; + } } diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 341b5206ba90..d370a380bc3b 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -3427,7 +3427,6 @@ public abstract class Context { //@hide: TIME_DETECTOR_SERVICE, //@hide: TIME_ZONE_DETECTOR_SERVICE, PERMISSION_SERVICE, - INCREMENTAL_SERVICE, }) @Retention(RetentionPolicy.SOURCE) public @interface ServiceName {} @@ -4971,6 +4970,13 @@ public abstract class Context { /** * Use with {@link #getSystemService(String)} to retrieve an + * {@link android.content.pm.DataLoaderManager}. + * @hide + */ + public static final String DATA_LOADER_MANAGER_SERVICE = "dataloadermanager"; + + /** + * Use with {@link #getSystemService(String)} to retrieve an * {@link android.os.incremental.IncrementalManager}. * @hide */ diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 40aca0ef2033..af7b98683219 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -4053,6 +4053,13 @@ public class Intent implements Parcelable, Cloneable { public static final String ACTION_SERVICE_STATE = "android.intent.action.SERVICE_STATE"; /** + * Used for looking up a Data Loader Service providers. + * @hide + */ + @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) + public static final String ACTION_LOAD_DATA = "android.intent.action.LOAD_DATA"; + + /** * An int extra used with {@link #ACTION_SERVICE_STATE} which indicates voice registration * state. * @see android.telephony.ServiceState#STATE_EMERGENCY_ONLY diff --git a/core/java/android/content/pm/DataLoaderManager.java b/core/java/android/content/pm/DataLoaderManager.java new file mode 100644 index 000000000000..26880384e502 --- /dev/null +++ b/core/java/android/content/pm/DataLoaderManager.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Bundle; +import android.os.RemoteException; + +/** + * Data loader manager takes care of data loaders of different packages. It provides methods to + * initialize a data loader binder service (binding and creating it), to return a binder of the data + * loader binder service and to destroy a data loader binder service. + * @see com.android.server.pm.DataLoaderManagerService + * @hide + */ +public class DataLoaderManager { + private static final String TAG = "DataLoaderManager"; + private final IDataLoaderManager mService; + + public DataLoaderManager(IDataLoaderManager service) { + mService = service; + } + + /** + * Finds a data loader binder service and binds to it. This requires PackageManager. + * + * @param dataLoaderId ID for the new data loader binder service. + * @param params Bundle that contains parameters to configure the data loader service. + * Must contain: + * key: "packageName", value: String, package name of data loader service + * package; + * key: "extras", value: Bundle, client-specific data structures + * + * @param listener Callback for the data loader service to report status back to the + * caller. + * @return false if 1) target ID collides with a data loader that is already bound to data + * loader manager; 2) package name is not specified; 3) fails to find data loader package; + * or 4) fails to bind to the specified data loader service, otherwise return true. + */ + public boolean initializeDataLoader(int dataLoaderId, @NonNull Bundle params, + @NonNull IDataLoaderStatusListener listener) { + try { + return mService.initializeDataLoader(dataLoaderId, params, listener); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns a binder interface of the data loader binder service, given its ID. + */ + @Nullable + public IDataLoader getDataLoader(int dataLoaderId) { + try { + return mService.getDataLoader(dataLoaderId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Destroys the data loader binder service and removes it from data loader manager service. + */ + @Nullable + public void destroyDataLoader(int dataLoaderId) { + try { + mService.destroyDataLoader(dataLoaderId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } +} diff --git a/core/java/android/content/pm/IDataLoader.aidl b/core/java/android/content/pm/IDataLoader.aidl new file mode 100644 index 000000000000..60cc9ba9e141 --- /dev/null +++ b/core/java/android/content/pm/IDataLoader.aidl @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm; + +import android.os.Bundle; +import android.content.pm.IDataLoaderStatusListener; +import android.content.pm.InstallationFile; +import java.util.List; + +/** + * TODO: update with new APIs + * @hide + */ +oneway interface IDataLoader { + void create(int id, in Bundle params, IDataLoaderStatusListener listener); + void start(in List<InstallationFile> fileInfos); + void stop(); + void destroy(); + void onFileCreated(long inode, in byte[] metadata); +} diff --git a/core/java/android/content/pm/IDataLoaderManager.aidl b/core/java/android/content/pm/IDataLoaderManager.aidl new file mode 100644 index 000000000000..f453c9b6c45f --- /dev/null +++ b/core/java/android/content/pm/IDataLoaderManager.aidl @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm; + +import android.os.Bundle; +import android.content.pm.IDataLoader; +import android.content.pm.IDataLoaderStatusListener; +import java.util.List; + +/** @hide */ +interface IDataLoaderManager { + boolean initializeDataLoader(int id, in Bundle params, IDataLoaderStatusListener listener); + IDataLoader getDataLoader(int dataLoaderId); + void destroyDataLoader(int dataLoaderId); +}
\ No newline at end of file diff --git a/core/java/android/service/incremental/IIncrementalDataLoaderStatusListener.aidl b/core/java/android/content/pm/IDataLoaderStatusListener.aidl index f04242dc6c02..a60d6ee2d28a 100644 --- a/core/java/android/service/incremental/IIncrementalDataLoaderStatusListener.aidl +++ b/core/java/android/content/pm/IDataLoaderStatusListener.aidl @@ -14,13 +14,13 @@ * limitations under the License. */ -package android.service.incremental; +package android.content.pm; /** - * Callbacks from DataLoaderService to IncrementalService to report data loader status. + * Callbacks from a data loader binder service to report data loader status. * @hide */ -oneway interface IIncrementalDataLoaderStatusListener { +oneway interface IDataLoaderStatusListener { /** Data loader status */ const int DATA_LOADER_READY = 0; const int DATA_LOADER_NOT_READY = 1; @@ -31,6 +31,6 @@ oneway interface IIncrementalDataLoaderStatusListener { const int DATA_LOADER_CONNECTION_OK = 6; /** Data loader status callback */ - void onStatusChanged(in int storageId, in int status); + void onStatusChanged(in int dataLoaderId, in int status); } diff --git a/core/java/android/content/pm/IPackageInstallerSession.aidl b/core/java/android/content/pm/IPackageInstallerSession.aidl index b74061793f9b..0b3c7654f4fb 100644 --- a/core/java/android/content/pm/IPackageInstallerSession.aidl +++ b/core/java/android/content/pm/IPackageInstallerSession.aidl @@ -46,4 +46,5 @@ interface IPackageInstallerSession { int getParentSessionId(); boolean isStaged(); + void addFile(in String name, long size, in byte[] metadata); } diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index a71367d562f7..b3d8eb51e324 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -26,6 +26,7 @@ import android.content.pm.ChangedPackages; import android.content.pm.InstantAppInfo; import android.content.pm.FeatureInfo; import android.content.pm.IDexModuleRegisterCallback; +import android.content.pm.InstallSourceInfo; import android.content.pm.IPackageInstaller; import android.content.pm.IPackageDeleteObserver; import android.content.pm.IPackageDeleteObserver2; @@ -237,6 +238,8 @@ interface IPackageManager { @UnsupportedAppUsage String getInstallerPackageName(in String packageName); + InstallSourceInfo getInstallSourceInfo(in String packageName); + void resetApplicationPreferences(int userId); @UnsupportedAppUsage diff --git a/core/java/android/content/pm/InstallSourceInfo.aidl b/core/java/android/content/pm/InstallSourceInfo.aidl new file mode 100644 index 000000000000..21ee4c35b57c --- /dev/null +++ b/core/java/android/content/pm/InstallSourceInfo.aidl @@ -0,0 +1,19 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm; + +parcelable InstallSourceInfo; diff --git a/core/java/android/content/pm/InstallSourceInfo.java b/core/java/android/content/pm/InstallSourceInfo.java new file mode 100644 index 000000000000..4d235f1af2f7 --- /dev/null +++ b/core/java/android/content/pm/InstallSourceInfo.java @@ -0,0 +1,110 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Information about how an app was installed. + * @see PackageManager#getInstallSourceInfo(String) + */ +public final class InstallSourceInfo implements Parcelable { + + @Nullable private final String mInitiatingPackageName; + + @Nullable private final String mOriginatingPackageName; + + @Nullable private final String mInstallingPackageName; + + /** @hide */ + public InstallSourceInfo(@Nullable String initiatingPackageName, + @Nullable String originatingPackageName, @Nullable String installingPackageName) { + this.mInitiatingPackageName = initiatingPackageName; + this.mOriginatingPackageName = originatingPackageName; + this.mInstallingPackageName = installingPackageName; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString(mInitiatingPackageName); + dest.writeString(mOriginatingPackageName); + dest.writeString(mInstallingPackageName); + } + + private InstallSourceInfo(Parcel source) { + mInitiatingPackageName = source.readString(); + mOriginatingPackageName = source.readString(); + mInstallingPackageName = source.readString(); + } + + /** The name of the package that requested the installation, or null if not available. */ + @Nullable + public String getInitiatingPackageName() { + return mInitiatingPackageName; + } + + /** + * The name of the package on behalf of which the initiating package requested the installation, + * or null if not available. + * <p> + * For example if a downloaded APK is installed via the Package Installer this could be the + * app that performed the download. This value is provided by the initiating package and not + * verified by the framework. + * <p> + * Note that the {@code InstallSourceInfo} returned by + * {@link PackageManager#getInstallSourceInfo(String)} will not have this information + * available unless the calling application holds the INSTALL_PACKAGES permission. + */ + @Nullable + public String getOriginatingPackageName() { + return mOriginatingPackageName; + } + + /** + * The name of the package responsible for the installation (the installer of record), or null + * if not available. + * Note that this may differ from the initiating package name and can be modified. + * + * @see PackageManager#setInstallerPackageName(String, String) + */ + @Nullable + public String getInstallingPackageName() { + return mInstallingPackageName; + } + + @NonNull + public static final Parcelable.Creator<InstallSourceInfo> CREATOR = + new Creator<InstallSourceInfo>() { + @Override + public InstallSourceInfo createFromParcel(Parcel source) { + return new InstallSourceInfo(source); + } + + @Override + public InstallSourceInfo[] newArray(int size) { + return new InstallSourceInfo[size]; + } + }; +} diff --git a/core/java/android/content/pm/InstallationFile.aidl b/core/java/android/content/pm/InstallationFile.aidl new file mode 100644 index 000000000000..1edff9d6c7aa --- /dev/null +++ b/core/java/android/content/pm/InstallationFile.aidl @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm; + +/** + * Describes a file which is part of a package installation. + */ +parcelable InstallationFile; + diff --git a/core/java/android/content/pm/InstallationFile.java b/core/java/android/content/pm/InstallationFile.java new file mode 100644 index 000000000000..ac5fd1e41075 --- /dev/null +++ b/core/java/android/content/pm/InstallationFile.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Defines the properties of a file in an installation session. + * TODO(b/136132412): update with new APIs. + * + * @hide + */ +public final class InstallationFile implements Parcelable { + public static final int FILE_TYPE_UNKNOWN = -1; + public static final int FILE_TYPE_APK = 0; + public static final int FILE_TYPE_LIB = 1; + public static final int FILE_TYPE_OBB = 2; + + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"FILE_TYPE_"}, value = { + FILE_TYPE_APK, + FILE_TYPE_LIB, + FILE_TYPE_OBB, + }) + public @interface FileType { + } + + private String mFileName; + private @FileType int mFileType; + private long mFileSize; + private byte[] mMetadata; + + public InstallationFile(@NonNull String fileName, long fileSize, + @Nullable byte[] metadata) { + mFileName = fileName; + mFileSize = fileSize; + mMetadata = metadata; + if (fileName.toLowerCase().endsWith(".apk")) { + mFileType = FILE_TYPE_APK; + } else if (fileName.toLowerCase().endsWith(".obb")) { + mFileType = FILE_TYPE_OBB; + } else if (fileName.toLowerCase().endsWith(".so") && fileName.toLowerCase().startsWith( + "lib/")) { + mFileType = FILE_TYPE_LIB; + } else { + mFileType = FILE_TYPE_UNKNOWN; + } + } + + public @FileType int getFileType() { + return mFileType; + } + + public @NonNull String getName() { + return mFileName; + } + + public long getSize() { + return mFileSize; + } + + public @Nullable byte[] getMetadata() { + return mMetadata; + } + + private InstallationFile(Parcel source) { + mFileName = source.readString(); + mFileType = source.readInt(); + mFileSize = source.readLong(); + mMetadata = source.createByteArray(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString(mFileName); + dest.writeInt(mFileType); + dest.writeLong(mFileSize); + dest.writeByteArray(mMetadata); + } + + public static final @NonNull Creator<InstallationFile> CREATOR = + new Creator<InstallationFile>() { + public InstallationFile createFromParcel(Parcel source) { + return new InstallationFile(source); + } + + public InstallationFile[] newArray(int size) { + return new InstallationFile[size]; + } + }; + +} diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index 4ab6f3cd92b4..e9fc8f6acfc6 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -50,6 +50,8 @@ import android.os.ParcelableException; import android.os.RemoteException; import android.os.SystemProperties; import android.os.UserHandle; +import android.os.incremental.IncrementalDataLoaderParams; +import android.os.incremental.IncrementalDataLoaderParamsParcel; import android.system.ErrnoException; import android.system.Os; import android.util.ArraySet; @@ -1212,6 +1214,27 @@ public class PackageInstaller { } /** + * Configure files for an installation session. + * + * Currently only for Incremental installation session. Once this method is called, + * the files and their paths, as specified in the parameters, will be created and properly + * configured in the Incremental File System. + * + * TODO(b/136132412): update this and InstallationFile class with latest API design. + * + * @throws IllegalStateException if {@link SessionParams#incrementalParams} is null. + * + * @hide + */ + public void addFile(@NonNull String name, long size, @NonNull byte[] metadata) { + try { + mSession.addFile(name, size, metadata); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Release this session object. You can open the session again if it * hasn't been finalized. */ @@ -1398,6 +1421,8 @@ public class PackageInstaller { public boolean isStaged; /** {@hide} */ public long requiredInstalledVersionCode = PackageManager.VERSION_CODE_HIGHEST; + /** {@hide} */ + public IncrementalDataLoaderParams incrementalParams; /** * Construct parameters for a new package install session. @@ -1431,6 +1456,12 @@ public class PackageInstaller { isMultiPackage = source.readBoolean(); isStaged = source.readBoolean(); requiredInstalledVersionCode = source.readLong(); + IncrementalDataLoaderParamsParcel dataLoaderParamsParcel = source.readParcelable( + IncrementalDataLoaderParamsParcel.class.getClassLoader()); + if (dataLoaderParamsParcel != null) { + incrementalParams = new IncrementalDataLoaderParams( + dataLoaderParamsParcel); + } } /** {@hide} */ @@ -1454,6 +1485,7 @@ public class PackageInstaller { ret.isMultiPackage = isMultiPackage; ret.isStaged = isStaged; ret.requiredInstalledVersionCode = requiredInstalledVersionCode; + ret.incrementalParams = incrementalParams; return ret; } @@ -1782,6 +1814,16 @@ public class PackageInstaller { return (installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0; } + /** + * Set Incremental data loader params. + * + * {@hide} + */ + @RequiresPermission(Manifest.permission.INSTALL_PACKAGES) + public void setIncrementalParams(@NonNull IncrementalDataLoaderParams incrementalParams) { + this.incrementalParams = incrementalParams; + } + /** {@hide} */ public void dump(IndentingPrintWriter pw) { pw.printPair("mode", mode); @@ -1831,6 +1873,11 @@ public class PackageInstaller { dest.writeBoolean(isMultiPackage); dest.writeBoolean(isStaged); dest.writeLong(requiredInstalledVersionCode); + if (incrementalParams != null) { + dest.writeParcelable(incrementalParams.getData(), flags); + } else { + dest.writeParcelable(null, flags); + } } public static final Parcelable.Creator<SessionParams> diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index bbfdf910a9da..6e890ba0d827 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -5989,11 +5989,30 @@ public abstract class PackageManager { * * @param packageName The name of the package to query * @throws IllegalArgumentException if the given package name is not installed + * + * @deprecated use {@link #getInstallSourceInfo(String)} instead */ + @Deprecated @Nullable public abstract String getInstallerPackageName(@NonNull String packageName); /** + * Retrieves information about how a package was installed or updated. + * <p> + * If the calling application does not hold the INSTALL_PACKAGES permission then + * the result will always return {@code null} from + * {@link InstallSourceInfo#getOriginatingPackageName()}. + * + * @param packageName The name of the package to query + * @throws NameNotFoundException if the given package name is not installed + */ + @NonNull + public InstallSourceInfo getInstallSourceInfo(@NonNull String packageName) + throws NameNotFoundException { + throw new UnsupportedOperationException("getInstallSourceInfo not implemented"); + } + + /** * Attempts to clear the user data directory of an application. * Since this may take a little while, the result will * be posted back to the given observer. A deletion will fail if the diff --git a/core/java/android/net/MacAddress.aidl b/core/java/android/net/MacAddress.aidl new file mode 100644 index 000000000000..48a18a7ac821 --- /dev/null +++ b/core/java/android/net/MacAddress.aidl @@ -0,0 +1,20 @@ +/** + * + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +@JavaOnlyStableParcelable parcelable MacAddress; diff --git a/core/java/android/net/Network.java b/core/java/android/net/Network.java index 3f56def6d7d5..f12ba1367d98 100644 --- a/core/java/android/net/Network.java +++ b/core/java/android/net/Network.java @@ -63,7 +63,7 @@ public class Network implements Parcelable { /** * @hide */ - @UnsupportedAppUsage + @SystemApi public final int netId; // Objects used to perform per-network operations such as getSocketFactory diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl index 40048d9154de..e81a505a1715 100644 --- a/core/java/android/os/IUserManager.aidl +++ b/core/java/android/os/IUserManager.aidl @@ -87,6 +87,7 @@ interface IUserManager { void setDefaultGuestRestrictions(in Bundle restrictions); Bundle getDefaultGuestRestrictions(); boolean markGuestForDeletion(int userId); + UserInfo findCurrentGuestUser(); boolean isQuietModeEnabled(int userId); void setSeedAccountData(int userId, in String accountName, in String accountType, in PersistableBundle accountOptions, boolean persist); diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index dbe8dc3e38e2..9e9cd9218a0f 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -2375,6 +2375,20 @@ public class UserManager { } /** + * Gets the existing guest user if it exists. This does not include guest users that are dying. + * @return The existing guest user if it exists. Null otherwise. + * @hide + */ + @RequiresPermission(android.Manifest.permission.MANAGE_USERS) + public UserInfo findCurrentGuestUser() { + try { + return mService.findCurrentGuestUser(); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** * Creates a user with the specified name and options as a profile of another user. * Requires {@link android.Manifest.permission#MANAGE_USERS} permission. * The type of profile must be specified using the given flags. diff --git a/wifi/java/android/net/wifi/WifiActivityEnergyInfo.aidl b/core/java/android/os/connectivity/WifiActivityEnergyInfo.aidl index 007ec94378fd..ce75c129ec13 100644 --- a/wifi/java/android/net/wifi/WifiActivityEnergyInfo.aidl +++ b/core/java/android/os/connectivity/WifiActivityEnergyInfo.aidl @@ -14,6 +14,6 @@ * limitations under the License. */ -package android.net.wifi; +package android.os.connectivity; parcelable WifiActivityEnergyInfo; diff --git a/wifi/java/android/net/wifi/WifiActivityEnergyInfo.java b/core/java/android/os/connectivity/WifiActivityEnergyInfo.java index f0f31fae0aeb..7db003d9853c 100644 --- a/wifi/java/android/net/wifi/WifiActivityEnergyInfo.java +++ b/core/java/android/os/connectivity/WifiActivityEnergyInfo.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.net.wifi; +package android.os.connectivity; import android.annotation.IntDef; import android.annotation.NonNull; diff --git a/core/java/android/os/incremental/IIncrementalServiceProxy.aidl b/core/java/android/os/incremental/IIncrementalServiceProxy.aidl index 12740eaf3425..ffff52e5aac9 100644 --- a/core/java/android/os/incremental/IIncrementalServiceProxy.aidl +++ b/core/java/android/os/incremental/IIncrementalServiceProxy.aidl @@ -18,7 +18,7 @@ package android.os.incremental; import android.os.incremental.IncrementalFileSystemControlParcel; import android.os.incremental.IncrementalDataLoaderParamsParcel; -import android.service.incremental.IIncrementalDataLoaderStatusListener; +import android.content.pm.IDataLoaderStatusListener; /** * Binder service to receive calls from native Incremental Service and handle Java tasks such as @@ -29,7 +29,7 @@ interface IIncrementalServiceProxy { boolean prepareDataLoader(int mountId, in IncrementalFileSystemControlParcel control, in IncrementalDataLoaderParamsParcel params, - in IIncrementalDataLoaderStatusListener listener); + in IDataLoaderStatusListener listener); boolean startDataLoader(int mountId); void showHealthBlockedUI(int mountId); void destroyDataLoader(int mountId); diff --git a/core/java/android/os/incremental/IncrementalFileStorages.java b/core/java/android/os/incremental/IncrementalFileStorages.java new file mode 100644 index 000000000000..5bd0748b8d97 --- /dev/null +++ b/core/java/android/os/incremental/IncrementalFileStorages.java @@ -0,0 +1,280 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os.incremental; + +/** + * Set up files and directories used in an installation session. + * Currently only used by Incremental Installation. + * For Incremental installation, the expected outcome of this function is: + * 0) All the files are in defaultStorage + * 1) All APK files are in the same directory, bound to mApkStorage, and bound to the + * InstallerSession's stage dir. The files are linked from mApkStorage to defaultStorage. + * 2) All lib files are in the sub directories as their names suggest, and in the same parent + * directory as the APK files. The files are linked from mApkStorage to defaultStorage. + * 3) OBB files are in another directory that is different from APK files and lib files, bound + * to mObbStorage. The files are linked from mObbStorage to defaultStorage. + * + * @throws IllegalStateException the session is not an Incremental installation session. + */ + +import static dalvik.system.VMRuntime.getInstructionSet; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.pm.InstallationFile; +import android.os.IVold; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.ArraySet; +import android.util.Slog; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Random; + +/** + * This class manages storage instances used during a package installation session. + * @hide + */ +public final class IncrementalFileStorages { + private static final String TAG = "IncrementalFileStorages"; + private @Nullable IncrementalStorage mDefaultStorage; + private @Nullable IncrementalStorage mApkStorage; + private @Nullable IncrementalStorage mObbStorage; + private @Nullable String mDefaultDir; + private @Nullable String mObbDir; + private @NonNull IncrementalManager mIncrementalManager; + private @Nullable ArraySet<String> mLibDirs; + private @NonNull String mPackageName; + private @NonNull File mStageDir; + + /** + * Set up files and directories used in an installation session. + * Currently only used by Incremental Installation. + * For Incremental installation, the expected outcome of this function is: + * 0) All the files are in defaultStorage + * 1) All APK files are in the same directory, bound to mApkStorage, and bound to the + * InstallerSession's stage dir. The files are linked from mApkStorage to defaultStorage. + * 2) All lib files are in the sub directories as their names suggest, and in the same parent + * directory as the APK files. The files are linked from mApkStorage to defaultStorage. + * 3) OBB files are in another directory that is different from APK files and lib files, bound + * to mObbStorage. The files are linked from mObbStorage to defaultStorage. + * + * @throws IllegalStateException the session is not an Incremental installation session. + */ + public IncrementalFileStorages(@NonNull String packageName, + @NonNull File stageDir, + @NonNull IncrementalManager incrementalManager, + @NonNull IncrementalDataLoaderParams incrementalDataLoaderParams) { + mPackageName = packageName; + mStageDir = stageDir; + mIncrementalManager = incrementalManager; + if (incrementalDataLoaderParams.getPackageName().equals("local")) { + final String incrementalPath = incrementalDataLoaderParams.getStaticArgs(); + mDefaultStorage = mIncrementalManager.openStorage(incrementalPath); + mDefaultDir = incrementalPath; + return; + } + mDefaultDir = getTempDir(); + if (mDefaultDir == null) { + return; + } + mDefaultStorage = mIncrementalManager.createStorage(mDefaultDir, + incrementalDataLoaderParams, + IncrementalManager.CREATE_MODE_CREATE + | IncrementalManager.CREATE_MODE_TEMPORARY_BIND, false); + } + + /** + * Adds a file into the installation session. Makes sure it will be placed inside + * a proper storage instance, based on its file type. + */ + public void addFile(@NonNull InstallationFile file) throws IOException { + if (mDefaultStorage == null) { + throw new IOException("Cannot add file because default storage does not exist"); + } + if (file.getFileType() == InstallationFile.FILE_TYPE_APK) { + addApkFile(file); + } else if (file.getFileType() == InstallationFile.FILE_TYPE_OBB) { + addObbFile(file); + } else if (file.getFileType() == InstallationFile.FILE_TYPE_LIB) { + addLibFile(file); + } else { + throw new IOException("Unknown file type: " + file.getFileType()); + } + } + + private void addApkFile(@NonNull InstallationFile apk) throws IOException { + // Create a storage for APK files and lib files + final String stageDirPath = mStageDir.getAbsolutePath(); + if (mApkStorage == null) { + mApkStorage = mIncrementalManager.createStorage(stageDirPath, mDefaultStorage, + IncrementalManager.CREATE_MODE_CREATE + | IncrementalManager.CREATE_MODE_TEMPORARY_BIND); + mApkStorage.bind(stageDirPath); + } + + if (!new File(mDefaultDir, apk.getName()).exists()) { + mDefaultStorage.makeFile(apk.getName(), apk.getSize(), + apk.getMetadata()); + } + // Assuming APK files are already named properly, e.g., "base.apk" + mDefaultStorage.makeLink(apk.getName(), mApkStorage, apk.getName()); + } + + private void addLibFile(@NonNull InstallationFile lib) throws IOException { + // TODO(b/136132412): remove this after we have incfs support for lib file mapping + if (mApkStorage == null) { + throw new IOException("Cannot add lib file without adding an apk file first"); + } + if (mLibDirs == null) { + mLibDirs = new ArraySet<>(); + } + String current = ""; + final Path libDirPath = Paths.get(lib.getName()).getParent(); + final int numDirComponents = libDirPath.getNameCount(); + for (int i = 0; i < numDirComponents; i++) { + String dirName = libDirPath.getName(i).toString(); + try { + dirName = getInstructionSet(dirName); + } catch (IllegalArgumentException ignored) { + } + current += dirName; + if (!mLibDirs.contains(current)) { + mDefaultStorage.makeDirectory(current); + mApkStorage.makeDirectory(current); + mLibDirs.add(current); + } + current += '/'; + } + String libFilePath = current + Paths.get(lib.getName()).getFileName(); + mDefaultStorage.makeFile(libFilePath, lib.getSize(), lib.getMetadata()); + mDefaultStorage.makeLink(libFilePath, mApkStorage, libFilePath); + } + + private void addObbFile(@NonNull InstallationFile obb) throws IOException { + if (mObbStorage == null) { + // Create a storage for OBB files + mObbDir = getTempDir(); + if (mObbDir == null) { + throw new IOException("Failed to create obb storage directory."); + } + mObbStorage = mIncrementalManager.createStorage( + mObbDir, mDefaultStorage, + IncrementalManager.CREATE_MODE_CREATE + | IncrementalManager.CREATE_MODE_TEMPORARY_BIND); + } + mDefaultStorage.makeFile(obb.getName(), obb.getSize(), obb.getMetadata()); + mDefaultStorage.makeLink(obb.getName(), mObbStorage, obb.getName()); + } + + private boolean hasObb() { + return (mObbStorage != null && mObbDir != null); + } + + /** + * Starts loading data for default storage. + * TODO(b/136132412): update the implementation with latest API design. + */ + public boolean startLoading() { + if (mDefaultStorage == null) { + return false; + } + return mDefaultStorage.startLoading(); + } + + /** + * Sets up obb storage directory and create bindings. + */ + public void finishSetUp() { + if (!hasObb()) { + return; + } + final String mainObbDir = String.format("/storage/emulated/0/Android/obb/%s", mPackageName); + final String packageObbDirRoot = + String.format("/mnt/runtime/%s/emulated/0/Android/obb/", mPackageName); + final String[] obbDirs = { + packageObbDirRoot + "read", + packageObbDirRoot + "write", + packageObbDirRoot + "full", + packageObbDirRoot + "default", + String.format("/data/media/0/Android/obb/%s", mPackageName), + mainObbDir, + }; + try { + Slog.i(TAG, "Creating obb directory '" + mainObbDir + "'"); + final IVold vold = IVold.Stub.asInterface(ServiceManager.getServiceOrThrow("vold")); + vold.mkdirs(mainObbDir); + for (String d : obbDirs) { + mObbStorage.bindPermanent(d); + } + } catch (ServiceManager.ServiceNotFoundException ex) { + Slog.e(TAG, "vold service is not found."); + cleanUp(); + } catch (IOException | RemoteException ex) { + Slog.e(TAG, "Failed to create obb dir at: " + mainObbDir, ex); + cleanUp(); + } + } + + /** + * Resets the states and unbinds storage instances for an installation session. + * TODO(b/136132412): make sure unnecessary binds are removed but useful storages are kept + */ + public void cleanUp() { + if (mDefaultStorage != null && mDefaultDir != null) { + try { + mDefaultStorage.unBind(mDefaultDir); + } catch (IOException ignored) { + } + mDefaultDir = null; + mDefaultStorage = null; + } + if (mApkStorage != null && mStageDir != null) { + try { + mApkStorage.unBind(mStageDir.getAbsolutePath()); + } catch (IOException ignored) { + } + mApkStorage = null; + } + if (mObbStorage != null && mObbDir != null) { + try { + mObbStorage.unBind(mObbDir); + } catch (IOException ignored) { + } + mObbDir = null; + mObbStorage = null; + } + } + + private String getTempDir() { + final String tmpDirRoot = "/data/tmp"; + final Random random = new Random(); + final Path tmpDir = + Paths.get(tmpDirRoot, String.valueOf(random.nextInt(Integer.MAX_VALUE - 1))); + try { + Files.createDirectories(tmpDir); + } catch (Exception ex) { + Slog.e(TAG, "Failed to create dir", ex); + return null; + } + return tmpDir.toAbsolutePath().toString(); + } +} diff --git a/core/java/android/provider/BlockedNumberContract.java b/core/java/android/provider/BlockedNumberContract.java index 7995f2285482..1eb76648f7b2 100644 --- a/core/java/android/provider/BlockedNumberContract.java +++ b/core/java/android/provider/BlockedNumberContract.java @@ -16,7 +16,6 @@ package android.provider; import android.annotation.IntDef; -import android.annotation.NonNull; import android.annotation.SystemApi; import android.annotation.WorkerThread; import android.content.Context; @@ -155,12 +154,7 @@ public class BlockedNumberContract { /** The authority for the blocked number provider */ public static final String AUTHORITY = "com.android.blockednumber"; - /** - * A content:// style uri to the authority for the blocked number provider - * @hide - */ - @NonNull - @SystemApi + /** A content:// style uri to the authority for the blocked number provider */ public static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY); private static final String LOG_TAG = BlockedNumberContract.class.getSimpleName(); diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java index 9e454e63c849..0e5200983bc0 100644 --- a/core/java/android/provider/Telephony.java +++ b/core/java/android/provider/Telephony.java @@ -4505,7 +4505,7 @@ public final class Telephony { /** * The current registered voice network operator name in long alphanumeric format. * <p> - * This is the same as {@link ServiceState#getVoiceOperatorAlphaLong()}. + * This is the same as {@link ServiceState#getOperatorAlphaLong()}. * @hide */ public static final String VOICE_OPERATOR_ALPHA_LONG = "voice_operator_alpha_long"; @@ -4516,12 +4516,11 @@ public final class Telephony { * In GSM/UMTS, short format can be up to 8 characters long. The current registered voice * network operator name in long alphanumeric format. * <p> - * This is the same as {@link ServiceState#getVoiceOperatorAlphaShort()}. + * This is the same as {@link ServiceState#getOperatorAlphaShort()}. * @hide */ public static final String VOICE_OPERATOR_ALPHA_SHORT = "voice_operator_alpha_short"; - /** * The current registered operator numeric id. * <p> @@ -4535,7 +4534,7 @@ public final class Telephony { /** * The current registered data network operator name in long alphanumeric format. * <p> - * This is the same as {@link ServiceState#getDataOperatorAlphaLong()}. + * This is the same as {@link ServiceState#getOperatorAlphaLong()}. * @hide */ public static final String DATA_OPERATOR_ALPHA_LONG = "data_operator_alpha_long"; @@ -4543,7 +4542,7 @@ public final class Telephony { /** * The current registered data network operator name in short alphanumeric format. * <p> - * This is the same as {@link ServiceState#getDataOperatorAlphaShort()}. + * This is the same as {@link ServiceState#getOperatorAlphaShort()}. * @hide */ public static final String DATA_OPERATOR_ALPHA_SHORT = "data_operator_alpha_short"; @@ -4551,7 +4550,7 @@ public final class Telephony { /** * The current registered data network operator numeric id. * <p> - * This is the same as {@link ServiceState#getDataOperatorNumeric()}. + * This is the same as {@link ServiceState#getOperatorNumeric()}. * @hide */ public static final String DATA_OPERATOR_NUMERIC = "data_operator_numeric"; diff --git a/core/java/android/service/controls/BooleanAction.aidl b/core/java/android/service/controls/BooleanAction.aidl new file mode 100644 index 000000000000..730ad36749f7 --- /dev/null +++ b/core/java/android/service/controls/BooleanAction.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.controls; + +parcelable BooleanAction;
\ No newline at end of file diff --git a/core/java/android/service/controls/BooleanAction.java b/core/java/android/service/controls/BooleanAction.java new file mode 100644 index 000000000000..8508c635142f --- /dev/null +++ b/core/java/android/service/controls/BooleanAction.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.controls; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; + +/** + * Action sent by a {@link ToggleTemplate} + * @hide + */ +public final class BooleanAction extends ControlAction { + + private final boolean mNewState; + + /** + * @param templateId the identifier of the {@link ToggleTemplate} that produced this action. + * @param newState new value for the state displayed by the {@link ToggleTemplate}. + */ + public BooleanAction(@NonNull String templateId, boolean newState) { + this(templateId, newState, null); + } + + /** + * @param templateId the identifier of the {@link ToggleTemplate} that originated this action. + * @param newValue new value for the state displayed by the {@link ToggleTemplate}. + * @param challengeValue a value sent by the user along with the action to authenticate. {@code} + * null is sent when no authentication is needed or has not been + * requested. + */ + public BooleanAction(@NonNull String templateId, boolean newValue, + @Nullable String challengeValue) { + super(templateId, challengeValue); + mNewState = newValue; + } + + BooleanAction(Parcel in) { + super(in); + mNewState = in.readByte() == 1; + } + + /** + * The new state set for the button in the corresponding {@link ToggleTemplate}. + * + * @return {@code true} if the button was toggled from an {@code off} state to an {@code on} + * state. + */ + public boolean getNewState() { + return mNewState; + } + + /** + * @return {@link ControlAction#TYPE_BOOLEAN} + */ + @Override + public int getActionType() { + return ControlAction.TYPE_BOOLEAN; + } + + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeByte(mNewState ? (byte) 1 : (byte) 0); + } + + public static final @NonNull Creator<BooleanAction> CREATOR = new Creator<BooleanAction>() { + @Override + public BooleanAction createFromParcel(Parcel source) { + return new BooleanAction(source); + } + + @Override + public BooleanAction[] newArray(int size) { + return new BooleanAction[size]; + } + }; +} diff --git a/core/java/android/service/controls/Control.aidl b/core/java/android/service/controls/Control.aidl new file mode 100644 index 000000000000..f4964f2e15d7 --- /dev/null +++ b/core/java/android/service/controls/Control.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2019, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.controls; + +parcelable Control;
\ No newline at end of file diff --git a/core/java/android/service/controls/Control.java b/core/java/android/service/controls/Control.java new file mode 100644 index 000000000000..a69408c43df3 --- /dev/null +++ b/core/java/android/service/controls/Control.java @@ -0,0 +1,284 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.controls; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Intent; +import android.content.res.ColorStateList; +import android.graphics.drawable.Icon; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.Preconditions; + +/** + * Represents a physical object that can be represented by a {@link ControlTemplate} and whose + * properties may be modified through a {@link ControlAction}. + * + * The information is provided by a {@link ControlProviderService} and represents static + * information (not current status) about the device. + * <p> + * Each control needs a unique (per provider) identifier that is persistent across reboots of the + * system. + * <p> + * Each {@link Control} will have a name and an icon. The name is usually set up by the user in the + * {@link ControlProvider} while the icon is usually decided by the {@link ControlProvider} based + * on the type of device. + * <p> + * The {@link ControlTemplate.TemplateType} provided will be used as a hint when displaying this in + * non-interactive situations (for example when there's no state to display). This template is not + * the one that will be shown with the current state and provide interactions. That template is set + * using {@link ControlState}. + * <p> + * An {@link Intent} linking to the provider Activity that expands this {@link Control} should be + * provided. + * @hide + */ +public class Control implements Parcelable { + + private final @NonNull String mControlId; + private final @NonNull Icon mIcon; + private final @NonNull CharSequence mTitle; + private final @Nullable ColorStateList mTintColor; + private final @NonNull Intent mAppIntent; + private final @ControlTemplate.TemplateType int mPrimaryType; + + /** + * @param controlId the unique persistent identifier for this object. + * @param icon an icon to display identifying the control. + * @param title the user facing name of this control (e.g. "Bedroom thermostat"). + * @param tintColor the color to tint parts of the element UI. If {@code null} is passed, the + * system accent color will be used. + * @param appIntent an intent linking to a page to interact with the corresponding device. + * @param primaryType the primary template for this type. + */ + public Control(@NonNull String controlId, + @NonNull Icon icon, + @NonNull CharSequence title, + @Nullable ColorStateList tintColor, + @NonNull Intent appIntent, + int primaryType) { + Preconditions.checkNotNull(controlId); + Preconditions.checkNotNull(icon); + Preconditions.checkNotNull(title); + Preconditions.checkNotNull(appIntent); + mControlId = controlId; + mIcon = icon; + mTitle = title; + mTintColor = tintColor; + mAppIntent = appIntent; + mPrimaryType = primaryType; + } + + public Control(Parcel in) { + mControlId = in.readString(); + mIcon = Icon.CREATOR.createFromParcel(in); + mTitle = in.readCharSequence(); + if (in.readByte() == 1) { + mTintColor = ColorStateList.CREATOR.createFromParcel(in); + } else { + mTintColor = null; + } + mAppIntent = Intent.CREATOR.createFromParcel(in); + mPrimaryType = in.readInt(); + } + + @NonNull + public String getControlId() { + return mControlId; + } + + @NonNull + public Icon getIcon() { + return mIcon; + } + + @NonNull + public CharSequence getTitle() { + return mTitle; + } + + @Nullable + public ColorStateList getTint() { + return mTintColor; + } + + @NonNull + public Intent getAppIntent() { + return mAppIntent; + } + + @ControlTemplate.TemplateType + public int getPrimaryType() { + return mPrimaryType; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mControlId); + mIcon.writeToParcel(dest, flags); + dest.writeCharSequence(mTitle); + if (mTintColor != null) { + dest.writeByte((byte) 1); + mTintColor.writeToParcel(dest, flags); + } else { + dest.writeByte((byte) 0); + } + mAppIntent.writeToParcel(dest, flags); + dest.writeInt(mPrimaryType); + } + + public static final Creator<Control> CREATOR = new Creator<Control>() { + @Override + public Control createFromParcel(Parcel source) { + return new Control(source); + } + + @Override + public Control[] newArray(int size) { + return new Control[size]; + } + }; + + /** + * Builder class for {@link Control}. + * + * This class facilitates the creation of {@link Control}. It provides the following + * defaults for non-optional parameters: + * <ul> + * <li> Title: {@code ""} + * <li> Primary template: {@link ControlTemplate#TYPE_NONE} + * </ul> + */ + public static class Builder { + private String mControlId; + private Icon mIcon; + private CharSequence mTitle = ""; + private ColorStateList mTintColor; + private @Nullable Intent mAppIntent; + private @ControlTemplate.TemplateType int mPrimaryType = ControlTemplate.TYPE_NONE; + + /** + * @param controlId the identifier for the {@link Control}. + * @param icon the icon for the {@link Control}. + * @param appIntent the intent linking to the device Activity. + */ + public Builder(@NonNull String controlId, + @NonNull Icon icon, + @NonNull Intent appIntent) { + Preconditions.checkNotNull(controlId); + Preconditions.checkNotNull(icon); + Preconditions.checkNotNull(appIntent); + mControlId = controlId; + mIcon = icon; + mAppIntent = appIntent; + } + + /** + * Creates a {@link Builder} using an existing {@link Control} as a base. + * @param control base for the builder. + */ + public Builder(@NonNull Control control) { + Preconditions.checkNotNull(control); + mControlId = control.mControlId; + mIcon = control.mIcon; + mTitle = control.mTitle; + mTintColor = control.mTintColor; + mAppIntent = control.mAppIntent; + mPrimaryType = control.mPrimaryType; + } + + /** + * @param controlId the identifier for the {@link Control}. + * @return {@code this} + */ + public Builder setControlId(@NonNull String controlId) { + Preconditions.checkNotNull(controlId); + mControlId = controlId; + return this; + } + + /** + * @param icon the icon for the {@link Control} + * @return {@code this} + */ + @NonNull + public Builder setIcon(@NonNull Icon icon) { + Preconditions.checkNotNull(icon); + mIcon = icon; + return this; + } + + /** + * @param title the user facing name of the {@link Control} + * @return {@code this} + */ + @NonNull + public Builder setTitle(@NonNull CharSequence title) { + Preconditions.checkNotNull(title); + mTitle = title; + return this; + } + + /** + * @param tint colors for tinting parts of the {@link Control} UI. Passing {@code null} will + * default to using the current color accent. + * @return {@code this} + */ + @NonNull + public Builder setTint(@Nullable ColorStateList tint) { + mTintColor = tint; + return this; + } + + /** + * @param appIntent an {@link Intent} linking to an Activity for the {@link Control} + * @return {@code this} + */ + @NonNull + public Builder setAppIntent(@NonNull Intent appIntent) { + Preconditions.checkNotNull(appIntent); + mAppIntent = appIntent; + return this; + } + + /** + * @param type type to use as default in the {@link Control} + * @return {@code this} + */ + @NonNull + public Builder setPrimaryType(@ControlTemplate.TemplateType int type) { + mPrimaryType = type; + return this; + } + + /** + * Build a {@link Control} + * @return a valid {@link Control} + */ + @NonNull + public Control build() { + return new Control(mControlId, mIcon, mTitle, mTintColor, mAppIntent, mPrimaryType); + } + } +} diff --git a/core/java/android/service/controls/ControlAction.aidl b/core/java/android/service/controls/ControlAction.aidl new file mode 100644 index 000000000000..e1a5276b70d6 --- /dev/null +++ b/core/java/android/service/controls/ControlAction.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.controls; + +parcelable ControlAction;
\ No newline at end of file diff --git a/core/java/android/service/controls/ControlAction.java b/core/java/android/service/controls/ControlAction.java new file mode 100644 index 000000000000..8b759556b597 --- /dev/null +++ b/core/java/android/service/controls/ControlAction.java @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.controls; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.Preconditions; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * An abstract action that is executed from a {@link ControlTemplate}. + * + * The action may have a value to authenticate the input, when the provider has requested it to + * complete the action. + * @hide + */ +public abstract class ControlAction implements Parcelable { + + /** + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + TYPE_BOOLEAN, + TYPE_FLOAT + }) + public @interface ActionType {}; + + /** + * The identifier of {@link BooleanAction}. + */ + public static final @ActionType int TYPE_BOOLEAN = 0; + + /** + * The identifier of {@link FloatAction}. + */ + public static final @ActionType int TYPE_FLOAT = 1; + + /** + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + RESPONSE_OK, + RESPONSE_FAIL, + RESPONSE_CHALLENGE_ACK, + RESPONSE_CHALLENGE_PIN, + RESPONSE_CHALLENGE_PASSPHRASE + }) + public @interface ResponseResult {}; + + /** + * Response code for {@link IControlsProviderCallback#onControlActionResponse} indicating that + * the action has been performed. The action may still fail later and the state may not change. + */ + public static final @ResponseResult int RESPONSE_OK = 0; + /** + * Response code for {@link IControlsProviderCallback#onControlActionResponse} indicating that + * the action has failed. + */ + public static final @ResponseResult int RESPONSE_FAIL = 1; + /** + * Response code for {@link IControlsProviderCallback#onControlActionResponse} indicating that + * in order for the action to be performed, acknowledgment from the user is required. + */ + public static final @ResponseResult int RESPONSE_CHALLENGE_ACK = 2; + /** + * Response code for {@link IControlsProviderCallback#onControlActionResponse} indicating that + * in order for the action to be performed, a PIN is required. + */ + public static final @ResponseResult int RESPONSE_CHALLENGE_PIN = 3; + /** + * Response code for {@link IControlsProviderCallback#onControlActionResponse} indicating that + * in order for the action to be performed, an alphanumeric passphrase is required. + */ + public static final @ResponseResult int RESPONSE_CHALLENGE_PASSPHRASE = 4; + + /** + * The {@link ActionType} associated with this class. + */ + public abstract @ActionType int getActionType(); + + private final @NonNull String mTemplateId; + private final @Nullable String mChallengeValue; + + private ControlAction() { + mTemplateId = ""; + mChallengeValue = null; + } + + /** + * @hide + */ + ControlAction(@NonNull String templateId, @Nullable String challengeValue) { + Preconditions.checkNotNull(templateId); + mTemplateId = templateId; + mChallengeValue = challengeValue; + } + + /** + * @hide + */ + ControlAction(Parcel in) { + mTemplateId = in.readString(); + if (in.readByte() == 1) { + mChallengeValue = in.readString(); + } else { + mChallengeValue = null; + } + } + + /** + * The identifier of the {@link ControlTemplate} that originated this action + */ + @NonNull + public String getTemplateId() { + return mTemplateId; + } + + /** + * The challenge value used to authenticate certain actions, if available. + */ + @Nullable + public String getChallengeValue() { + return mChallengeValue; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(getActionType()); + dest.writeString(mTemplateId); + if (mChallengeValue != null) { + dest.writeByte((byte) 1); + dest.writeString(mChallengeValue); + } else { + dest.writeByte((byte) 0); + } + } + + public static final @NonNull Creator<ControlAction> CREATOR = new Creator<ControlAction>() { + @Override + public ControlAction createFromParcel(Parcel source) { + int type = source.readInt(); + return createActionFromType(type, source); + } + + @Override + public ControlAction[] newArray(int size) { + return new ControlAction[size]; + } + }; + + private static ControlAction createActionFromType(@ActionType int type, Parcel source) { + switch(type) { + case TYPE_BOOLEAN: + return BooleanAction.CREATOR.createFromParcel(source); + case TYPE_FLOAT: + return FloatAction.CREATOR.createFromParcel(source); + default: + return null; + } + } + +} diff --git a/core/java/android/service/controls/ControlButton.aidl b/core/java/android/service/controls/ControlButton.aidl new file mode 100644 index 000000000000..6a7262d9542e --- /dev/null +++ b/core/java/android/service/controls/ControlButton.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2019, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.controls; + +parcelable ControlButton;
\ No newline at end of file diff --git a/core/java/android/service/controls/ControlButton.java b/core/java/android/service/controls/ControlButton.java new file mode 100644 index 000000000000..fed31158be35 --- /dev/null +++ b/core/java/android/service/controls/ControlButton.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.controls; + +import android.annotation.NonNull; +import android.graphics.drawable.Icon; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.Preconditions; + +/** + * Button element for {@link ControlTemplate}. + * @hide + */ +public class ControlButton implements Parcelable { + + private final boolean mActive; + private final @NonNull Icon mIcon; + private final @NonNull CharSequence mContentDescription; + + /** + * @param active true if the button should be rendered as active. + * @param icon icon to display in the button. + * @param contentDescription content description for the button. + */ + public ControlButton(boolean active, @NonNull Icon icon, + @NonNull CharSequence contentDescription) { + Preconditions.checkNotNull(icon); + Preconditions.checkNotNull(contentDescription); + mActive = active; + mIcon = icon; + mContentDescription = contentDescription; + } + + /** + * Whether the button should be rendered in its active state. + */ + public boolean isActive() { + return mActive; + } + + /** + * The icon for this button. + */ + @NonNull + public Icon getIcon() { + return mIcon; + } + + /** + * The content description for this button. + */ + @NonNull + public CharSequence getContentDescription() { + return mContentDescription; + } + + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeByte(mActive ? (byte) 1 : (byte) 0); + mIcon.writeToParcel(dest, flags); + dest.writeCharSequence(mContentDescription); + } + + ControlButton(Parcel in) { + mActive = in.readByte() != 0; + mIcon = Icon.CREATOR.createFromParcel(in); + mContentDescription = in.readCharSequence(); + } + + public static final Creator<ControlButton> CREATOR = new Creator<ControlButton>() { + @Override + public ControlButton createFromParcel(Parcel source) { + return new ControlButton(source); + } + + @Override + public ControlButton[] newArray(int size) { + return new ControlButton[size]; + } + }; +} diff --git a/core/java/android/service/controls/ControlState.aidl b/core/java/android/service/controls/ControlState.aidl new file mode 100644 index 000000000000..520d85b47d3e --- /dev/null +++ b/core/java/android/service/controls/ControlState.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2019, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.controls; + +parcelable ControlState;
\ No newline at end of file diff --git a/core/java/android/service/controls/ControlState.java b/core/java/android/service/controls/ControlState.java new file mode 100644 index 000000000000..804aef798eb5 --- /dev/null +++ b/core/java/android/service/controls/ControlState.java @@ -0,0 +1,318 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.controls; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.res.ColorStateList; +import android.graphics.drawable.Icon; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.Preconditions; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Current state for a {@link Control}. + * + * Collects information to render the current state of a {@link Control} as well as possible action + * that can be performed on it. Some of the information may temporarily override the defaults + * provided by the corresponding {@link Control}, while this state is being displayed. + * + * Additionally, this can be used to modify information related to the corresponding + * {@link Control}. + * @hide + */ +public final class ControlState implements Parcelable { + + /** + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + STATUS_OK, + STATUS_NOT_FOUND, + STATUS_ERROR, + STATUS_DISABLED, + }) + public @interface Status {}; + + /** + * The device corresponding to the {@link Control} is responding correctly. + */ + public static final int STATUS_OK = 0; + + /** + * The device corresponding to the {@link Control} cannot be found or was removed. + */ + public static final int STATUS_NOT_FOUND = 1; + + /** + * The device corresponding to the {@link Control} is in an error state. + */ + public static final int STATUS_ERROR = 2; + + /** + * The {@link Control} is currently disabled. + */ + public static final int STATUS_DISABLED = 3; + + private final @NonNull Control mControl; + private final @Status int mStatus; + private final @NonNull ControlTemplate mControlTemplate; + private final @NonNull CharSequence mStatusText; + private final @Nullable Icon mOverrideIcon; + private final @Nullable ColorStateList mOverrideTint; + + /** + * @param control the {@link Control} this state should be applied to. Can be used to + * update information about the {@link Control} + * @param status the current status of the {@link Control}. + * @param controlTemplate the template to be used to render the {@link Control}. + * @param statusText the text describing the current status. + * @param overrideIcon the icon to temporarily override the one provided in + * {@link Control#getIcon()}. Pass {@code null} to use the icon in + * {@link Control#getIcon()}. + * @param overrideTint the colors to temporarily override those provided in + * {@link Control#getTint()}. Pass {@code null} to use the colors in + * {@link Control#getTint()}. + */ + public ControlState(@NonNull Control control, + int status, + @NonNull ControlTemplate controlTemplate, + @NonNull CharSequence statusText, + @Nullable Icon overrideIcon, + @Nullable ColorStateList overrideTint) { + Preconditions.checkNotNull(control); + Preconditions.checkNotNull(controlTemplate); + Preconditions.checkNotNull(statusText); + + mControl = control; + mStatus = status; + mControlTemplate = controlTemplate; + mOverrideIcon = overrideIcon; + mStatusText = statusText; + mOverrideTint = overrideTint; + } + + ControlState(Parcel in) { + mControl = Control.CREATOR.createFromParcel(in); + mStatus = in.readInt(); + mControlTemplate = ControlTemplate.CREATOR.createFromParcel(in); + mStatusText = in.readCharSequence(); + if (in.readByte() == 1) { + mOverrideIcon = Icon.CREATOR.createFromParcel(in); + } else { + mOverrideIcon = null; + } + if (in.readByte() == 1) { + mOverrideTint = ColorStateList.CREATOR.createFromParcel(in); + } else { + mOverrideTint = null; + } + } + + @Override + public int describeContents() { + return 0; + } + + @Status + public int getStatus() { + return mStatus; + } + + @NonNull + public ControlTemplate getControlTemplate() { + return mControlTemplate; + } + + @Nullable + public Icon getOverrideIcon() { + return mOverrideIcon; + } + + @NonNull + public CharSequence getStatusText() { + return mStatusText; + } + + @Nullable + public ColorStateList getOverrideTint() { + return mOverrideTint; + } + + @NonNull + public Control getControl() { + return mControl; + } + + @NonNull + public String getControlId() { + return mControl.getControlId(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + mControl.writeToParcel(dest, flags); + dest.writeInt(mStatus); + mControlTemplate.writeToParcel(dest, flags); + dest.writeCharSequence(mStatusText); + if (mOverrideIcon != null) { + dest.writeByte((byte) 1); + mOverrideIcon.writeToParcel(dest, flags); + } else { + dest.writeByte((byte) 0); + } + if (mOverrideTint != null) { + dest.writeByte((byte) 1); + mOverrideTint.writeToParcel(dest, flags); + } else { + dest.writeByte((byte) 0); + } + } + + public static final Creator<ControlState> CREATOR = new Creator<ControlState>() { + @Override + public ControlState createFromParcel(Parcel source) { + return new ControlState(source); + } + + @Override + public ControlState[] newArray(int size) { + return new ControlState[size]; + } + }; + + /** + * Builder class for {@link ControlState}. + * + * This class facilitates the creation of {@link ControlState}. It provides the following + * defaults for non-optional parameters: + * <ul> + * <li> Status: {@link ControlState#STATUS_OK} + * <li> Control template: {@link ControlTemplate#NO_TEMPLATE} + * <li> Status text: {@code ""} + * </ul> + */ + public static class Builder { + private @NonNull Control mControl; + private @Status int mStatus = STATUS_OK; + private @NonNull ControlTemplate mControlTemplate = ControlTemplate.NO_TEMPLATE; + private @NonNull CharSequence mStatusText = ""; + private @Nullable Icon mOverrideIcon; + private @Nullable ColorStateList mOverrideTint; + + /** + * @param control the {@link Control} that the resulting {@link ControlState} refers to. + */ + public Builder(@NonNull Control control) { + Preconditions.checkNotNull(control); + mControl = control; + } + + /** + * Creates a {@link Builder} using an existing {@link ControlState} as a base. + * @param controlState base for the builder. + */ + public Builder(@NonNull ControlState controlState) { + Preconditions.checkNotNull(controlState); + mControl = controlState.mControl; + mControlTemplate = controlState.mControlTemplate; + mOverrideIcon = controlState.mOverrideIcon; + mStatusText = controlState.mStatusText; + mOverrideTint = controlState.mOverrideTint; + } + + + /** + * @param control the updated {@link Control} information. + * @return {@code this} + */ + @NonNull + public Builder setControl(@NonNull Control control) { + mControl = control; + return this; + } + + /** + * @param status the current status of the {@link Control} + * @return {@code this} + */ + @NonNull + public Builder setStatus(@Status int status) { + mStatus = status; + return this; + } + + /** + * @param controlTemplate the template to use when rendering the {@code Control}. + * @return {@code this} + */ + @NonNull + public Builder setControlTemplate(@NonNull ControlTemplate controlTemplate) { + Preconditions.checkNotNull(controlTemplate); + mControlTemplate = controlTemplate; + return this; + } + + /** + * @param statusText the user-visible description of the status. + * @return {@code this} + */ + @NonNull + public Builder setStatusText(@NonNull CharSequence statusText) { + Preconditions.checkNotNull(statusText); + mStatusText = statusText; + return this; + } + + /** + * @param overrideIcon the icon to override the one defined in the corresponding + * {@code Control}. Pass {@code null} to remove the override. + * @return {@code this} + */ + @NonNull + public Builder setOverrideIcon(@Nullable Icon overrideIcon) { + mOverrideIcon = overrideIcon; + return this; + } + + /** + * @param overrideTint the colors to override the ones defined in the corresponding + * {@code Control}. Pass {@code null} to remove the override. + * @return {@code this} + */ + @NonNull + public Builder setOverrideTint(@Nullable ColorStateList overrideTint) { + mOverrideTint = overrideTint; + return this; + } + + /** + * @return a new {@link ControlState} + */ + public ControlState build() { + return new ControlState(mControl, mStatus, mControlTemplate, mStatusText, + mOverrideIcon, mOverrideTint); + } + } +} + diff --git a/core/java/android/service/controls/ControlTemplate.aidl b/core/java/android/service/controls/ControlTemplate.aidl new file mode 100644 index 000000000000..ecb948c8a306 --- /dev/null +++ b/core/java/android/service/controls/ControlTemplate.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2019, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.controls; + +parcelable ControlTemplate;
\ No newline at end of file diff --git a/core/java/android/service/controls/ControlTemplate.java b/core/java/android/service/controls/ControlTemplate.java new file mode 100644 index 000000000000..e559862e86d6 --- /dev/null +++ b/core/java/android/service/controls/ControlTemplate.java @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.controls; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.Preconditions; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * An abstract input template for a {@link Control}. + * + * Specifies what layout is presented to the user when a {@link ControlState} is assigned to a + * particular {@link Control}. + * <p> + * Some instances of {@link Control} can originate actions (via user interaction) to modify its + * associated state. The actions available to a given {@link Control} in a particular + * {@link ControlState} are determined by its {@link ControlTemplate}. + * @see ControlAction + * @hide + */ +public abstract class ControlTemplate implements Parcelable { + + /** + * Singleton representing a {@link Control} with no input. + */ + public static final ControlTemplate NO_TEMPLATE = new ControlTemplate("") { + @Override + public int getTemplateType() { + return TYPE_NONE; + } + }; + + /** + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + TYPE_NONE, + TYPE_TOGGLE, + TYPE_RANGE, + TYPE_THUMBNAIL, + TYPE_DISCRETE_TOGGLE, + TYPE_COORD_RANGE + }) + public @interface TemplateType {} + + /** + * Type identifier of {@link ControlTemplate#NO_TEMPLATE}. + */ + public static final int TYPE_NONE = 0; + + /** + * Type identifier of {@link ToggleTemplate}. + */ + public static final int TYPE_TOGGLE = 1; + + /** + * Type identifier of {@link RangeTemplate}. + */ + public static final int TYPE_RANGE = 2; + + /** + * Type identifier of {@link ThumbnailTemplate}. + */ + public static final int TYPE_THUMBNAIL = 3; + + /** + * Type identifier of {@link DiscreteToggleTemplate}. + */ + public static final int TYPE_DISCRETE_TOGGLE = 4; + + /** + * @hide + */ + public static final int TYPE_COORD_RANGE = 5; + + private @NonNull final String mTemplateId; + + /** + * @return the identifier for this object. + */ + public String getTemplateId() { + return mTemplateId; + } + + /** + * The {@link TemplateType} associated with this class. + */ + public abstract @TemplateType int getTemplateType(); + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(getTemplateType()); + dest.writeString(mTemplateId); + } + + private ControlTemplate() { + mTemplateId = ""; + } + + ControlTemplate(Parcel in) { + mTemplateId = in.readString(); + } + + /** + * @hide + */ + ControlTemplate(@NonNull String templateId) { + Preconditions.checkNotNull(templateId); + mTemplateId = templateId; + } + + public static final Creator<ControlTemplate> CREATOR = new Creator<ControlTemplate>() { + @Override + public ControlTemplate createFromParcel(Parcel source) { + int type = source.readInt(); + return createTemplateFromType(type, source); + } + + @Override + public ControlTemplate[] newArray(int size) { + return new ControlTemplate[size]; + } + }; + + private static ControlTemplate createTemplateFromType(@TemplateType int type, Parcel source) { + switch(type) { + case TYPE_TOGGLE: + return ToggleTemplate.CREATOR.createFromParcel(source); + case TYPE_RANGE: + return RangeTemplate.CREATOR.createFromParcel(source); + case TYPE_THUMBNAIL: + return ThumbnailTemplate.CREATOR.createFromParcel(source); + case TYPE_DISCRETE_TOGGLE: + return DiscreteToggleTemplate.CREATOR.createFromParcel(source); + case TYPE_NONE: + return NO_TEMPLATE; + default: + return null; + } + } +} diff --git a/core/java/android/service/controls/DiscreteToggleTemplate.java b/core/java/android/service/controls/DiscreteToggleTemplate.java new file mode 100644 index 000000000000..5167af41c2f0 --- /dev/null +++ b/core/java/android/service/controls/DiscreteToggleTemplate.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.controls; + +import android.annotation.NonNull; +import android.os.Parcel; + +import com.android.internal.util.Preconditions; + +/** + * A template for a {@link Control} with two discrete inputs. + * + * The two inputs represent a <i>Negative</i> input and a <i>Positive</i> input. + * <p> + * When one of the buttons is actioned, a {@link BooleanAction} will be sent. + * {@link BooleanAction#getNewState} will be {@code false} if the button was + * {@link DiscreteToggleTemplate#getNegativeButton} and {@code true} if the button was + * {@link DiscreteToggleTemplate#getPositiveButton}. + * @hide + */ +public class DiscreteToggleTemplate extends ControlTemplate { + + private final @NonNull ControlButton mNegativeButton; + private final @NonNull ControlButton mPositiveButton; + + /** + * @param templateId the identifier for this template object + * @param negativeButton a {@ControlButton} for the <i>Negative</i> input + * @param positiveButton a {@ControlButton} for the <i>Positive</i> input + */ + public DiscreteToggleTemplate(@NonNull String templateId, + @NonNull ControlButton negativeButton, + @NonNull ControlButton positiveButton) { + super(templateId); + Preconditions.checkNotNull(negativeButton); + Preconditions.checkNotNull(positiveButton); + mNegativeButton = negativeButton; + mPositiveButton = positiveButton; + } + + DiscreteToggleTemplate(Parcel in) { + super(in); + this.mNegativeButton = ControlButton.CREATOR.createFromParcel(in); + this.mPositiveButton = ControlButton.CREATOR.createFromParcel(in); + } + + /** + * The {@link ControlButton} associated with the <i>Negative</i> action. + */ + @NonNull + public ControlButton getNegativeButton() { + return mNegativeButton; + } + + /** + * The {@link ControlButton} associated with the <i>Positive</i> action. + */ + @NonNull + public ControlButton getPositiveButton() { + return mPositiveButton; + } + + /** + * @return {@link ControlTemplate#TYPE_DISCRETE_TOGGLE} + */ + @Override + public int getTemplateType() { + return TYPE_DISCRETE_TOGGLE; + } + + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + mNegativeButton.writeToParcel(dest, flags); + mPositiveButton.writeToParcel(dest, flags); + } + + public static final Creator<DiscreteToggleTemplate> CREATOR = + new Creator<DiscreteToggleTemplate>() { + @Override + public DiscreteToggleTemplate createFromParcel(Parcel source) { + return new DiscreteToggleTemplate(source); + } + + @Override + public DiscreteToggleTemplate[] newArray(int size) { + return new DiscreteToggleTemplate[size]; + } + }; +} diff --git a/core/java/android/service/controls/FloatAction.aidl b/core/java/android/service/controls/FloatAction.aidl new file mode 100644 index 000000000000..dbc0f726880c --- /dev/null +++ b/core/java/android/service/controls/FloatAction.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.controls; + +parcelable FloatAction;
\ No newline at end of file diff --git a/core/java/android/service/controls/FloatAction.java b/core/java/android/service/controls/FloatAction.java new file mode 100644 index 000000000000..fe6db10a98cd --- /dev/null +++ b/core/java/android/service/controls/FloatAction.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.controls; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; + +/** + * Action sent by a {@link RangeTemplate}. + * @hide + */ +public final class FloatAction extends ControlAction { + + private final float mNewValue; + + /** + * @param templateId the identifier of the {@link RangeTemplate} that produced this action. + * @param newValue new value for the state displayed by the {@link RangeTemplate}. + */ + public FloatAction(@NonNull String templateId, float newValue) { + this(templateId, newValue, null); + } + + /** + * @param templateId the identifier of the {@link RangeTemplate} that originated this action. + * @param newValue new value for the state of the {@link RangeTemplate}. + * @param challengeValue a value sent by the user along with the action to authenticate. {@code} + * null is sent when no authentication is needed or has not been + * requested. + */ + + public FloatAction(@NonNull String templateId, float newValue, + @Nullable String challengeValue) { + super(templateId, challengeValue); + mNewValue = newValue; + } + + public FloatAction(Parcel in) { + super(in); + mNewValue = in.readFloat(); + } + + /** + * The new value set for the range in the corresponding {@link RangeTemplate}. + */ + public float getNewValue() { + return mNewValue; + } + + /** + * @return {@link ControlAction#TYPE_FLOAT} + */ + @Override + public int getActionType() { + return TYPE_FLOAT; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeFloat(mNewValue); + } + + public static final @NonNull Creator<FloatAction> CREATOR = new Creator<FloatAction>() { + @Override + public FloatAction createFromParcel(Parcel source) { + return new FloatAction(source); + } + + @Override + public FloatAction[] newArray(int size) { + return new FloatAction[size]; + } + }; +} diff --git a/core/java/android/service/controls/IControlsProvider.aidl b/core/java/android/service/controls/IControlsProvider.aidl new file mode 100644 index 000000000000..f778653eb3d3 --- /dev/null +++ b/core/java/android/service/controls/IControlsProvider.aidl @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2019, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.controls; + +import android.service.controls.ControlAction; + +/** @hide */ +oneway interface IControlsProvider { + void load(); + + void subscribe(in List<String> controlIds); + + void unsubscribe(); + + void onAction(in String controlId, in ControlAction action); +}
\ No newline at end of file diff --git a/core/java/android/service/controls/IControlsProviderCallback.aidl b/core/java/android/service/controls/IControlsProviderCallback.aidl new file mode 100644 index 000000000000..3dbb68c1c7f0 --- /dev/null +++ b/core/java/android/service/controls/IControlsProviderCallback.aidl @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2019, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.controls; + +import android.service.controls.Control; +import android.service.controls.ControlState; + +/** @hide */ +oneway interface IControlsProviderCallback { + void onLoad(in List<Control> controls); + + void onRefreshState(in List<ControlState> controlStates); + + void onControlActionResponse(in String controlId, int response); +}
\ No newline at end of file diff --git a/core/java/android/service/controls/RangeTemplate.aidl b/core/java/android/service/controls/RangeTemplate.aidl new file mode 100644 index 000000000000..a3d1ca074276 --- /dev/null +++ b/core/java/android/service/controls/RangeTemplate.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2019, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.controls; + +parcelable RangeTemplate;
\ No newline at end of file diff --git a/core/java/android/service/controls/RangeTemplate.java b/core/java/android/service/controls/RangeTemplate.java new file mode 100644 index 000000000000..70bf2dd4aad4 --- /dev/null +++ b/core/java/android/service/controls/RangeTemplate.java @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.controls; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; + +import com.android.internal.util.Preconditions; + +import java.security.InvalidParameterException; + +/** + * A template for a {@link Control} with inputs in a "continuous" range of values. + * + * @see FloatAction + * @hide + */ +public final class RangeTemplate extends ControlTemplate { + + private final float mMinValue; + private final float mMaxValue; + private final float mCurrentValue; + private final float mStepValue; + private final @NonNull CharSequence mFormatString; + + /** + * Construct a new {@link RangeTemplate}. + * + * The range must be valid, meaning: + * <ul> + * <li> {@code minValue} < {@code maxValue} + * <li> {@code minValue} < {@code currentValue} + * <li> {@code currentValue} < {@code maxValue} + * <li> 0 < {@code stepValue} + * </ul> + * <p> + * The current value of the Control will be formatted accordingly. + * + * @param templateId the identifier for this template object + * @param minValue minimum value for the input + * @param maxValue maximum value for the input + * @param currentValue the current value of the {@link ControlState} containing this object. + * @param stepValue minimum value of increments/decrements when interacting with this control. + * @param formatString a formatting string as per {@link String#format} used to display the + * {@code currentValue}. If {@code null} is passed, the "%.1f" is used. + * @throws InvalidParameterException if the parameters passed do not make a valid range. + */ + public RangeTemplate(@NonNull String templateId, + float minValue, + float maxValue, + float currentValue, + float stepValue, + @Nullable CharSequence formatString) { + super(templateId); + Preconditions.checkNotNull(formatString); + mMinValue = minValue; + mMaxValue = maxValue; + mCurrentValue = currentValue; + mStepValue = stepValue; + if (formatString != null) { + mFormatString = formatString; + } else { + mFormatString = "%.1f"; + } + validate(); + } + + /** + * Construct a new {@link RangeTemplate} from a {@link Parcel}. + * + * @throws InvalidParameterException if the parameters passed do not make a valid range + * @see RangeTemplate#RangeTemplate(String, float, float, float, float, CharSequence) + * @hide + */ + RangeTemplate(Parcel in) { + super(in); + mMinValue = in.readFloat(); + mMaxValue = in.readFloat(); + mCurrentValue = in.readFloat(); + mStepValue = in.readFloat(); + mFormatString = in.readCharSequence(); + validate(); + } + + /** + * The minimum value for this range. + */ + public float getMinValue() { + return mMinValue; + } + + /** + * The maximum value for this range. + */ + public float getMaxValue() { + return mMaxValue; + } + + /** + * The current value for this range. + */ + public float getCurrentValue() { + return mCurrentValue; + } + + /** + * The value of the smallest increment or decrement that can be performed on this range. + */ + public float getStepValue() { + return mStepValue; + } + + /** + * Formatter for generating a user visible {@link String} representing the value + * returned by {@link RangeTemplate#getCurrentValue}. + * @return a formatting string as specified in {@link String#format} + */ + @NonNull + public CharSequence getFormatString() { + return mFormatString; + } + + /** + * @return {@link ControlTemplate#TYPE_RANGE} + */ + @Override + public int getTemplateType() { + return TYPE_RANGE; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeFloat(mMinValue); + dest.writeFloat(mMaxValue); + dest.writeFloat(mCurrentValue); + dest.writeFloat(mStepValue); + dest.writeCharSequence(mFormatString); + } + + /** + * Validate constructor parameters + * + * @throws InvalidParameterException if the parameters passed do not make a valid range + */ + private void validate() { + if (Float.compare(mMinValue, mMaxValue) > 0) { + throw new InvalidParameterException( + String.format("minValue=%f > maxValue=%f", mMinValue, mMaxValue)); + } + if (Float.compare(mMinValue, mCurrentValue) > 0) { + throw new InvalidParameterException( + String.format("minValue=%f > currentValue=%f", mMinValue, mCurrentValue)); + } + if (Float.compare(mCurrentValue, mMaxValue) > 0) { + throw new InvalidParameterException( + String.format("currentValue=%f > maxValue=%f", mCurrentValue, mMaxValue)); + } + if (mStepValue <= 0) { + throw new InvalidParameterException(String.format("stepValue=%f <= 0", mStepValue)); + } + } + + public static final Creator<RangeTemplate> CREATOR = new Creator<RangeTemplate>() { + @Override + public RangeTemplate createFromParcel(Parcel source) { + return new RangeTemplate(source); + } + + @Override + public RangeTemplate[] newArray(int size) { + return new RangeTemplate[size]; + } + }; +} diff --git a/core/java/android/service/controls/ThumbnailTemplate.aidl b/core/java/android/service/controls/ThumbnailTemplate.aidl new file mode 100644 index 000000000000..fe8c7fed7c89 --- /dev/null +++ b/core/java/android/service/controls/ThumbnailTemplate.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2019, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.controls; + +parcelable ThumbnailTemplate;
\ No newline at end of file diff --git a/core/java/android/service/controls/ThumbnailTemplate.java b/core/java/android/service/controls/ThumbnailTemplate.java new file mode 100644 index 000000000000..796d2de89576 --- /dev/null +++ b/core/java/android/service/controls/ThumbnailTemplate.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.controls; + +import android.annotation.NonNull; +import android.graphics.drawable.Icon; +import android.os.Parcel; + +import com.android.internal.util.Preconditions; + +/** + * A template for a {@link Control} that displays an image. + * @hide + */ +public final class ThumbnailTemplate extends ControlTemplate { + + private final @NonNull Icon mThumbnail; + private final @NonNull CharSequence mContentDescription; + + /** + * @param templateId the identifier for this template object + * @param thumbnail an image to display on the {@link Control} + * @param contentDescription a description of the image for accessibility. + */ + public ThumbnailTemplate(@NonNull String templateId, @NonNull Icon thumbnail, + @NonNull CharSequence contentDescription) { + super(templateId); + Preconditions.checkNotNull(thumbnail); + Preconditions.checkNotNull(contentDescription); + mThumbnail = thumbnail; + mContentDescription = contentDescription; + } + + ThumbnailTemplate(Parcel in) { + super(in); + mThumbnail = Icon.CREATOR.createFromParcel(in); + mContentDescription = in.readCharSequence(); + } + + /** + * The {@link Icon} (image) displayed by this template. + */ + @NonNull + public Icon getThumbnail() { + return mThumbnail; + } + + /** + * The description of the image returned by {@link ThumbnailTemplate#getThumbnail()} + */ + @NonNull + public CharSequence getContentDescription() { + return mContentDescription; + } + + /** + * @return {@link ControlTemplate#TYPE_THUMBNAIL} + */ + @Override + public int getTemplateType() { + return TYPE_THUMBNAIL; + } + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + mThumbnail.writeToParcel(dest, flags); + dest.writeCharSequence(mContentDescription); + } + + public static final Creator<ThumbnailTemplate> CREATOR = new Creator<ThumbnailTemplate>() { + @Override + public ThumbnailTemplate createFromParcel(Parcel source) { + return new ThumbnailTemplate(source); + } + + @Override + public ThumbnailTemplate[] newArray(int size) { + return new ThumbnailTemplate[size]; + } + }; +} diff --git a/core/java/android/service/controls/ToggleTemplate.aidl b/core/java/android/service/controls/ToggleTemplate.aidl new file mode 100644 index 000000000000..1c823d9aee6d --- /dev/null +++ b/core/java/android/service/controls/ToggleTemplate.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2019, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.controls; + +parcelable ToggleTemplate;
\ No newline at end of file diff --git a/core/java/android/service/controls/ToggleTemplate.java b/core/java/android/service/controls/ToggleTemplate.java new file mode 100644 index 000000000000..3766bd168477 --- /dev/null +++ b/core/java/android/service/controls/ToggleTemplate.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.controls; + +import android.annotation.NonNull; +import android.os.Parcel; + +import com.android.internal.util.Preconditions; + +/** + * A template for a {@link Control} with a single button that can be toggled between two states. + * + * The states for the toggle correspond to the states in {@link ControlButton#isActive()}. + * An action on this template will originate a {@link BooleanAction} to change that state. + * + * @see BooleanAction + * @hide + */ +public final class ToggleTemplate extends ControlTemplate { + + private final @NonNull ControlButton mButton; + + /** + * @param templateId the identifier for this template object + * @param button a {@ControlButton} that can show the current state and toggle it + */ + public ToggleTemplate(@NonNull String templateId, @NonNull ControlButton button) { + super(templateId); + Preconditions.checkNotNull(button); + mButton = button; + } + + ToggleTemplate(Parcel in) { + super(in); + mButton = ControlButton.CREATOR.createFromParcel(in); + } + + /** + * The button provided to this object in {@link ToggleTemplate#ToggleTemplate} + */ + @NonNull + public ControlButton getButton() { + return mButton; + } + + /** + * @return {@link ControlTemplate#TYPE_TOGGLE} + */ + @Override + public int getTemplateType() { + return TYPE_TOGGLE; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + mButton.writeToParcel(dest, flags); + } + + public static final Creator<ToggleTemplate> CREATOR = new Creator<ToggleTemplate>() { + @Override + public ToggleTemplate createFromParcel(Parcel source) { + return new ToggleTemplate(source); + } + + @Override + public ToggleTemplate[] newArray(int size) { + return new ToggleTemplate[size]; + } + }; + +} diff --git a/core/java/android/service/incremental/IIncrementalDataLoaderService.aidl b/core/java/android/service/incremental/IIncrementalDataLoaderService.aidl deleted file mode 100644 index 723fc594bd72..000000000000 --- a/core/java/android/service/incremental/IIncrementalDataLoaderService.aidl +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.service.incremental; - -import android.os.incremental.IncrementalDataLoaderParamsParcel; -import android.os.incremental.IncrementalFileSystemControlParcel; -import android.service.incremental.IIncrementalDataLoaderStatusListener; - -/** @hide */ -oneway interface IIncrementalDataLoaderService { - void createDataLoader(in int storageId, - in IncrementalFileSystemControlParcel control, - in IncrementalDataLoaderParamsParcel params, - in IIncrementalDataLoaderStatusListener listener, - in boolean start); - void startDataLoader(in int storageId); - void stopDataLoader(in int storageId); - void destroyDataLoader(in int storageId); - void onFileCreated(in int storageId, in long inode, in byte[] metadata); -} diff --git a/core/java/android/service/notification/IConditionProvider.aidl b/core/java/android/service/notification/IConditionProvider.aidl index 3f3c6b80286d..dd3904fc28ce 100644 --- a/core/java/android/service/notification/IConditionProvider.aidl +++ b/core/java/android/service/notification/IConditionProvider.aidl @@ -1,5 +1,5 @@ /** - * Copyright (c) 2014, The Android Open Source Project + * Copyright (c) 2019, The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core/java/android/telephony/CellBroadcastIntents.java b/core/java/android/telephony/CellBroadcastIntents.java new file mode 100644 index 000000000000..4474f3ecc620 --- /dev/null +++ b/core/java/android/telephony/CellBroadcastIntents.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.os.Handler; +import android.os.UserHandle; +import android.util.Log; + +/** + * A static helper class used to send Intents with prepopulated flags. + * <p> + * This is intended to be used by the CellBroadcastService and will throw a security exception if + * used from a UID besides the network stack UID. + * + * @hide + */ +@SystemApi +public class CellBroadcastIntents { + private static final String LOG_TAG = "CellBroadcastIntents"; + + /** + * @hide + */ + private CellBroadcastIntents() { + } + + /** + * Returns an intent which can be received by background BroadcastReceivers. This is only + * intended to be used by the CellBroadcastService and will throw a security exception if called + * from another UID. + * + * @param context The context from which to send the broadcast + * @param user The user from which to send the broadcast + * @param intent The Intent to broadcast; all receivers matching this Intent will + * receive the broadcast. + * @param receiverPermission String naming a permissions that a receiver must hold in order to + * receive your broadcast. If null, no permission is required. + * @param receiverAppOp The app op associated with the broadcast. If null, no appOp is + * required. If both receiverAppOp and receiverPermission are + * non-null, a receiver must have both of them to receive the + * broadcast + * @param resultReceiver Your own BroadcastReceiver to treat as the final receiver of the + * broadcast. + * @param scheduler A custom Handler with which to schedule the resultReceiver + * callback; if null it will be scheduled in the Context's main + * thread. + * @param initialCode An initial value for the result code. Often Activity.RESULT_OK. + * @param initialData An initial value for the result data. Often null. + * @param initialExtras An initial value for the result extras. Often null. + */ + public static void sendOrderedBroadcastForBackgroundReceivers(@NonNull Context context, + @Nullable UserHandle user, @NonNull Intent intent, @Nullable String receiverPermission, + @Nullable String receiverAppOp, @Nullable BroadcastReceiver resultReceiver, + @Nullable Handler scheduler, int initialCode, @Nullable String initialData, + @Nullable Bundle initialExtras) { + Log.d(LOG_TAG, "sendOrderedBroadcastForBackgroundReceivers intent=" + intent.getAction()); + int status = context.checkCallingOrSelfPermission( + "android.permission.GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS"); + if (status == PackageManager.PERMISSION_DENIED) { + throw new SecurityException( + "Caller does not have permission to send broadcast for background receivers"); + } + intent.setFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); + if (user != null) { + context.createContextAsUser(user, 0).sendOrderedBroadcast(intent, receiverPermission, + receiverAppOp, resultReceiver, scheduler, initialCode, initialData, + initialExtras); + } else { + context.sendOrderedBroadcast(intent, receiverPermission, + receiverAppOp, resultReceiver, scheduler, initialCode, initialData, + initialExtras); + } + } +} diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl index 15b1d75e88d0..2f1a15f39ca5 100644 --- a/core/java/com/android/internal/app/IBatteryStats.aidl +++ b/core/java/com/android/internal/app/IBatteryStats.aidl @@ -19,10 +19,10 @@ package com.android.internal.app; import com.android.internal.os.BatteryStatsImpl; import android.bluetooth.BluetoothActivityEnergyInfo; -import android.net.wifi.WifiActivityEnergyInfo; import android.os.ParcelFileDescriptor; import android.os.WorkSource; import android.os.connectivity.CellularBatteryStats; +import android.os.connectivity.WifiActivityEnergyInfo; import android.os.connectivity.WifiBatteryStats; import android.os.connectivity.GpsBatteryStats; import android.os.health.HealthStatsParceler; diff --git a/core/java/com/android/internal/compat/CompatibilityChangeInfo.java b/core/java/com/android/internal/compat/CompatibilityChangeInfo.java index e48e2df4043c..16628d71712c 100644 --- a/core/java/com/android/internal/compat/CompatibilityChangeInfo.java +++ b/core/java/com/android/internal/compat/CompatibilityChangeInfo.java @@ -30,6 +30,7 @@ public class CompatibilityChangeInfo implements Parcelable { private final @Nullable String mName; private final int mEnableAfterTargetSdk; private final boolean mDisabled; + private final @Nullable String mDescription; public long getId() { return mChangeId; @@ -48,12 +49,18 @@ public class CompatibilityChangeInfo implements Parcelable { return mDisabled; } + public String getDescription() { + return mDescription; + } + public CompatibilityChangeInfo( - Long changeId, String name, int enableAfterTargetSdk, boolean disabled) { + Long changeId, String name, int enableAfterTargetSdk, boolean disabled, + String description) { this.mChangeId = changeId; this.mName = name; this.mEnableAfterTargetSdk = enableAfterTargetSdk; this.mDisabled = disabled; + this.mDescription = description; } private CompatibilityChangeInfo(Parcel in) { @@ -61,6 +68,7 @@ public class CompatibilityChangeInfo implements Parcelable { mName = in.readString(); mEnableAfterTargetSdk = in.readInt(); mDisabled = in.readBoolean(); + mDescription = in.readString(); } @Override @@ -74,6 +82,7 @@ public class CompatibilityChangeInfo implements Parcelable { dest.writeString(mName); dest.writeInt(mEnableAfterTargetSdk); dest.writeBoolean(mDisabled); + dest.writeString(mDescription); } public static final Parcelable.Creator<CompatibilityChangeInfo> CREATOR = diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 25fa65e19260..bc44fcf3691d 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -33,7 +33,6 @@ import android.net.ConnectivityManager; import android.net.INetworkStatsService; import android.net.NetworkStats; import android.net.Uri; -import android.net.wifi.WifiActivityEnergyInfo; import android.net.wifi.WifiManager; import android.os.BatteryManager; import android.os.BatteryStats; @@ -56,6 +55,7 @@ import android.os.WorkSource; import android.os.WorkSource.WorkChain; import android.os.connectivity.CellularBatteryStats; import android.os.connectivity.GpsBatteryStats; +import android.os.connectivity.WifiActivityEnergyInfo; import android.os.connectivity.WifiBatteryStats; import android.provider.Settings; import android.telephony.CellSignalStrength; diff --git a/core/java/com/android/internal/util/AsyncChannel.java b/core/java/com/android/internal/util/AsyncChannel.java index b0888f2c8df9..121ae1a17456 100644 --- a/core/java/com/android/internal/util/AsyncChannel.java +++ b/core/java/com/android/internal/util/AsyncChannel.java @@ -29,7 +29,7 @@ import android.os.Looper; import android.os.Message; import android.os.Messenger; import android.os.RemoteException; -import android.util.Slog; +import android.util.Log; import java.util.Stack; @@ -841,7 +841,7 @@ public class AsyncChannel { msg.replyTo = sm.mMessenger; synchronized (sm.mHandler.mLockObject) { if (sm.mHandler.mResultMsg != null) { - Slog.wtf(TAG, "mResultMsg should be null here"); + Log.wtf(TAG, "mResultMsg should be null here"); sm.mHandler.mResultMsg = null; } dstMessenger.send(msg); @@ -851,9 +851,9 @@ public class AsyncChannel { } } } catch (InterruptedException e) { - Slog.e(TAG, "error in sendMessageSynchronously", e); + Log.e(TAG, "error in sendMessageSynchronously", e); } catch (RemoteException e) { - Slog.e(TAG, "error in sendMessageSynchronously", e); + Log.e(TAG, "error in sendMessageSynchronously", e); } sm.recycle(); return resultMsg; @@ -939,7 +939,7 @@ public class AsyncChannel { * @param s */ private static void log(String s) { - Slog.d(TAG, s); + Log.d(TAG, s); } private final class DeathMonitor implements IBinder.DeathRecipient { diff --git a/core/java/com/android/internal/util/AsyncService.java b/core/java/com/android/internal/util/AsyncService.java index e39a2bfc6abd..58e4a605f88c 100644 --- a/core/java/com/android/internal/util/AsyncService.java +++ b/core/java/com/android/internal/util/AsyncService.java @@ -22,7 +22,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.Messenger; -import android.util.Slog; +import android.util.Log; /** * A service that receives Intents and IBinder transactions @@ -92,7 +92,7 @@ abstract public class AsyncService extends Service { */ @Override public int onStartCommand(Intent intent, int flags, int startId) { - if (DBG) Slog.d(TAG, "onStartCommand"); + if (DBG) Log.d(TAG, "onStartCommand"); Message msg = mHandler.obtainMessage(); msg.what = CMD_ASYNC_SERVICE_ON_START_INTENT; @@ -111,7 +111,7 @@ abstract public class AsyncService extends Service { */ @Override public void onDestroy() { - if (DBG) Slog.d(TAG, "onDestroy"); + if (DBG) Log.d(TAG, "onDestroy"); Message msg = mHandler.obtainMessage(); msg.what = CMD_ASYNC_SERVICE_DESTROY; diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto index ce2717bb2779..9f31b590cf77 100644 --- a/core/proto/android/server/activitymanagerservice.proto +++ b/core/proto/android/server/activitymanagerservice.proto @@ -77,12 +77,14 @@ message ActivityStackSupervisorProto { message ActivityDisplayProto { option (.android.msg_privacy).dest = DEST_AUTOMATIC; - optional .com.android.server.wm.ConfigurationContainerProto configuration_container = 1; + // To be removed soon. + optional .com.android.server.wm.ConfigurationContainerProto configuration_container = 1 [deprecated=true]; optional int32 id = 2; repeated ActivityStackProto stacks = 3; optional int32 focused_stack_id = 4; optional .com.android.server.wm.IdentifierProto resumed_activity = 5; optional bool single_task_instance = 6; + optional .com.android.server.wm.DisplayContentProto display = 7; } message ActivityStackProto { diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 9dc23199ffae..f6e91efd2ad7 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -5070,6 +5070,10 @@ android:permission="android.permission.BIND_JOB_SERVICE" > </service> + <service android:name="com.android.server.usage.UsageStatsIdleService" + android:permission="android.permission.BIND_JOB_SERVICE" > + </service> + <service android:name="com.android.server.net.watchlist.ReportWatchlistJobService" android:permission="android.permission.BIND_JOB_SERVICE" > </service> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 234ffee3baa8..38d3e9ccaf6b 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -3750,6 +3750,14 @@ </p> --> <attr name="canRequestFingerprintGestures" format="boolean" /> + + <!-- Animated image of the accessibility service purpose or behavior, to help users + understand how the service can help them.--> + <attr name="animatedImageDrawable" format="reference"/> + <!-- Html description of the accessibility service, to help users understand + how the service can help them.--> + <attr name="htmlDescription" format="string"/> + <!-- Short description of the accessibility service purpose or behavior.--> <attr name="description" /> <!-- Brief summary of the accessibility service purpose or behavior. --> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 57e0e1b5c931..78c4efec5b72 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -3002,6 +3002,8 @@ <public name="forceQueryable" /> <!-- @hide @SystemApi --> <public name="resourcesMap" /> + <public name="animatedImageDrawable"/> + <public name="htmlDescription"/> </public-group> <public-group type="drawable" first-id="0x010800b5"> diff --git a/core/tests/coretests/src/android/service/controls/ControlActionTest.java b/core/tests/coretests/src/android/service/controls/ControlActionTest.java new file mode 100644 index 000000000000..ef4912fe1f74 --- /dev/null +++ b/core/tests/coretests/src/android/service/controls/ControlActionTest.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.controls; + +import static junit.framework.Assert.assertTrue; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import android.os.Parcel; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class ControlActionTest { + + private static final String TEST_ID = "TEST_ID"; + + @Test + public void testUnparcelingCorrectClass_boolean() { + ControlAction toParcel = new BooleanAction(TEST_ID, true); + + ControlAction fromParcel = parcelAndUnparcel(toParcel); + + assertEquals(ControlAction.TYPE_BOOLEAN, fromParcel.getActionType()); + assertTrue(fromParcel instanceof BooleanAction); + } + + @Test + public void testUnparcelingCorrectClass_float() { + ControlAction toParcel = new FloatAction(TEST_ID, 1); + + ControlAction fromParcel = parcelAndUnparcel(toParcel); + + assertEquals(ControlAction.TYPE_FLOAT, fromParcel.getActionType()); + assertTrue(fromParcel instanceof FloatAction); + } + + private ControlAction parcelAndUnparcel(ControlAction toParcel) { + Parcel parcel = Parcel.obtain(); + + assertNotNull(parcel); + + parcel.setDataPosition(0); + toParcel.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + + return ControlAction.CREATOR.createFromParcel(parcel); + } +} diff --git a/core/tests/coretests/src/android/service/controls/ControlTemplateTest.java b/core/tests/coretests/src/android/service/controls/ControlTemplateTest.java new file mode 100644 index 000000000000..4fa4e1d7b733 --- /dev/null +++ b/core/tests/coretests/src/android/service/controls/ControlTemplateTest.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.controls; + +import static junit.framework.Assert.assertTrue; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import android.annotation.DrawableRes; +import android.graphics.drawable.Icon; +import android.os.Parcel; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.frameworks.coretests.R; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockitoAnnotations; + +import java.security.InvalidParameterException; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class ControlTemplateTest { + + private static final String PACKAGE_NAME = "com.android.frameworks.coretests"; + private static final @DrawableRes int TEST_ICON_ID = R.drawable.box; + private static final String TEST_ID = "TEST_ID"; + private static final CharSequence TEST_CONTENT_DESCRIPTION = "TEST_CONTENT_DESCRIPTION"; + private Icon mIcon; + private ControlButton mControlButton; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mIcon = Icon.createWithResource(PACKAGE_NAME, TEST_ICON_ID); + mControlButton = new ControlButton(true, mIcon, TEST_CONTENT_DESCRIPTION); + } + + @Test + public void testUnparcelingCorrectClass_none() { + ControlTemplate toParcel = ControlTemplate.NO_TEMPLATE; + + ControlTemplate fromParcel = parcelAndUnparcel(toParcel); + + assertEquals(ControlTemplate.NO_TEMPLATE, fromParcel); + } + + @Test + public void testUnparcelingCorrectClass_toggle() { + ControlTemplate toParcel = new ToggleTemplate(TEST_ID, mControlButton); + + ControlTemplate fromParcel = parcelAndUnparcel(toParcel); + + assertEquals(ControlTemplate.TYPE_TOGGLE, fromParcel.getTemplateType()); + assertTrue(fromParcel instanceof ToggleTemplate); + } + + @Test + public void testUnparcelingCorrectClass_range() { + ControlTemplate toParcel = new RangeTemplate(TEST_ID, 0, 2, 1, 1, "%f"); + + ControlTemplate fromParcel = parcelAndUnparcel(toParcel); + + assertEquals(ControlTemplate.TYPE_RANGE, fromParcel.getTemplateType()); + assertTrue(fromParcel instanceof RangeTemplate); + } + + @Test(expected = InvalidParameterException.class) + public void testRangeParameters_minMax() { + RangeTemplate range = new RangeTemplate(TEST_ID, 2, 0, 1, 1, "%f"); + } + + @Test(expected = InvalidParameterException.class) + public void testRangeParameters_minCurrent() { + RangeTemplate range = new RangeTemplate(TEST_ID, 0, 2, -1, 1, "%f"); + } + + @Test(expected = InvalidParameterException.class) + public void testRangeParameters_maxCurrent() { + RangeTemplate range = new RangeTemplate(TEST_ID, 0, 2, 3, 1, "%f"); + } + + @Test(expected = InvalidParameterException.class) + public void testRangeParameters_negativeStep() { + RangeTemplate range = new RangeTemplate(TEST_ID, 0, 2, 1, -1, "%f"); + } + + @Test + public void testUnparcelingCorrectClass_thumbnail() { + ControlTemplate toParcel = new ThumbnailTemplate(TEST_ID, mIcon, TEST_CONTENT_DESCRIPTION); + + ControlTemplate fromParcel = parcelAndUnparcel(toParcel); + + assertEquals(ControlTemplate.TYPE_THUMBNAIL, fromParcel.getTemplateType()); + assertTrue(fromParcel instanceof ThumbnailTemplate); + } + + @Test + public void testUnparcelingCorrectClass_discreteToggle() { + ControlTemplate toParcel = + new DiscreteToggleTemplate(TEST_ID, mControlButton, mControlButton); + + ControlTemplate fromParcel = parcelAndUnparcel(toParcel); + + assertEquals(ControlTemplate.TYPE_DISCRETE_TOGGLE, fromParcel.getTemplateType()); + assertTrue(fromParcel instanceof DiscreteToggleTemplate); + } + + private ControlTemplate parcelAndUnparcel(ControlTemplate toParcel) { + Parcel parcel = Parcel.obtain(); + + assertNotNull(parcel); + + parcel.setDataPosition(0); + toParcel.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + + return ControlTemplate.CREATOR.createFromParcel(parcel); + } +} diff --git a/data/etc/Android.bp b/data/etc/Android.bp index c49d663c52eb..ff521be82599 100644 --- a/data/etc/Android.bp +++ b/data/etc/Android.bp @@ -104,7 +104,7 @@ prebuilt_etc { prebuilt_etc { name: "privapp_whitelist_com.android.settings", - product_specific: true, + system_ext_specific: true, sub_dir: "permissions", src: "com.android.settings.xml", filename_from_src: true, diff --git a/data/etc/CleanSpec.mk b/data/etc/CleanSpec.mk index cffb1babfc08..3fe421e530cb 100644 --- a/data/etc/CleanSpec.mk +++ b/data/etc/CleanSpec.mk @@ -49,6 +49,8 @@ $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/product/etc/permissions/com. $(call add-clean-step, rm -rf $(PRODUCT_OUT)/product/etc/permissions/com.android.emergency.xml) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/product/etc/permissions/com.android.provision.xml) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/product/etc/permissions/com.android.provision.xml) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/product/etc/permissions/com.android.settings.xml) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/product/etc/permissions/com.android.settings.xml) # ****************************************************************** # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST ABOVE THIS BANNER # ****************************************************************** diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index f2a6452d6e44..cf3f51d6599a 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -339,8 +339,10 @@ applications that come with the platform <permission name="android.permission.SYSTEM_CAMERA" /> <!-- Permission required to test ExplicitHealthCheckServiceImpl. --> <permission name="android.permission.BIND_EXPLICIT_HEALTH_CHECK_SERVICE"/> - <!-- Permission required for UiModeManager cts test. --> + <!-- Permission required for UiModeManager CTS test. --> <permission name="android.permission.ENTER_CAR_MODE_PRIORITIZED"/> + <!-- Permission required for Telecom car mode CTS tests. --> + <permission name="android.permission.CONTROL_INCALL_EXPERIENCE"/> </privapp-permissions> <privapp-permissions package="com.android.statementservice"> diff --git a/media/Android.bp b/media/Android.bp index 022fa9b7bb9e..1912930f2081 100644 --- a/media/Android.bp +++ b/media/Android.bp @@ -45,8 +45,8 @@ java_library { filegroup { name: "updatable-media-srcs", srcs: [ - ":mediasession2-srcs", ":mediaparser-srcs", + ":mediasession2-srcs", ], } @@ -73,7 +73,8 @@ filegroup { name: "mediaparser-srcs", srcs: [ "apex/java/android/media/MediaParser.java" - ] + ], + path: "apex/java" } metalava_updatable_media_args = " --error UnhiddenSystemApi " + diff --git a/media/apex/java/android/media/MediaParser.java b/media/apex/java/android/media/MediaParser.java index c06e2837bcdc..8824269ea0c0 100644 --- a/media/apex/java/android/media/MediaParser.java +++ b/media/apex/java/android/media/MediaParser.java @@ -15,10 +15,47 @@ */ package android.media; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.net.Uri; +import android.text.TextUtils; import android.util.Pair; - +import android.util.SparseArray; + +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.extractor.DefaultExtractorInput; +import com.google.android.exoplayer2.extractor.Extractor; +import com.google.android.exoplayer2.extractor.ExtractorInput; +import com.google.android.exoplayer2.extractor.ExtractorOutput; +import com.google.android.exoplayer2.extractor.PositionHolder; +import com.google.android.exoplayer2.extractor.SeekMap.SeekPoints; +import com.google.android.exoplayer2.extractor.TrackOutput; +import com.google.android.exoplayer2.extractor.amr.AmrExtractor; +import com.google.android.exoplayer2.extractor.flv.FlvExtractor; +import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor; +import com.google.android.exoplayer2.extractor.mp3.Mp3Extractor; +import com.google.android.exoplayer2.extractor.mp4.FragmentedMp4Extractor; +import com.google.android.exoplayer2.extractor.mp4.Mp4Extractor; +import com.google.android.exoplayer2.extractor.ogg.OggExtractor; +import com.google.android.exoplayer2.extractor.ts.Ac3Extractor; +import com.google.android.exoplayer2.extractor.ts.Ac4Extractor; +import com.google.android.exoplayer2.extractor.ts.AdtsExtractor; +import com.google.android.exoplayer2.extractor.ts.PsExtractor; +import com.google.android.exoplayer2.extractor.ts.TsExtractor; +import com.google.android.exoplayer2.extractor.wav.WavExtractor; +import com.google.android.exoplayer2.upstream.DataSource; +import com.google.android.exoplayer2.upstream.DataSpec; +import com.google.android.exoplayer2.upstream.TransferListener; +import com.google.android.exoplayer2.util.ParsableByteArray; + +import java.io.EOFException; import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Collections; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; /** * Parses media container formats and extracts contained media samples and metadata. @@ -32,16 +69,93 @@ import java.util.List; * <p>Users must implement the following to use this class. * * <ul> - * <li>{@link Input}: Provides the media containers bytes to parse. - * <li>{@link OutputCallback}: Provides a sink for all extracted data and metadata. + * <li>{@link InputReader}: Provides the media container's bytes to parse. + * <li>{@link OutputConsumer}: Provides a sink for all extracted data and metadata. * </ul> * - * TODO: Add usage example here. + * <p>The following code snippet includes a usage example: + * + * <pre> + * MyOutputConsumer myOutputConsumer = new MyOutputConsumer(); + * MyInputReader myInputReader = new MyInputReader("www.example.com"); + * MediaParser mediaParser = MediaParser.create(myOutputConsumer); + * + * while (mediaParser.advance(myInputReader)) {} + * + * mediaParser.release(); + * mediaParser = null; + * </pre> + * + * <p>The following code snippet provides a rudimentary {@link OutputConsumer} sample implementation + * which extracts and publishes all video samples: + * + * <pre> + * + * class VideoOutputConsumer implements MediaParser.OutputConsumer { + * + * private static final int MAXIMUM_SAMPLE_SIZE = ...; + * private byte[] sampleDataBuffer = new byte[MAXIMUM_SAMPLE_SIZE]; + * private int videoTrackIndex = -1; + * private int bytesWrittenCount = 0; + * + * \@Override + * public void onSeekMap(int i, @NonNull MediaFormat mediaFormat) { \/* Do nothing. *\/ } + * + * \@Override + * public void onFormat(int i, @NonNull MediaFormat mediaFormat) { + * if (videoTrackIndex == -1 && mediaFormat + * .getString(MediaFormat.KEY_MIME, \/* defaultValue= *\/ "").startsWith("video/")) { + * videoTrackIndex = i; + * } + * } + * + * \@Override + * public void onSampleData(int trackIndex, @NonNull InputReader inputReader) + * throws IOException, InterruptedException { + * int numberOfBytesToRead = (int) inputReader.getLength(); + * if (videoTrackIndex != trackIndex) { + * // Discard contents. + * inputReader.read(\/* bytes= *\/ null, \/* offset= *\/ 0, numberOfBytesToRead); + * } + * int bytesRead = inputReader.read(sampleDataBuffer, bytesWrittenCount, numberOfBytesToRead); + * bytesWrittenCount += bytesRead; + * } + * + * \@Override + * public void onSampleCompleted( + * int trackIndex, + * long timeUs, + * int flags, + * int size, + * int offset, + * \@Nullable CryptoInfo cryptoData) { + * if (videoTrackIndex != trackIndex) { + * return; // It's not the video track. Ignore. + * } + * byte[] sampleData = new byte[size]; + * System.arraycopy(sampleDataBuffer, bytesWrittenCount - size - offset, sampleData, \/* + * destPos= *\/ 0, size); + * // Place trailing bytes at the start of the buffer. + * System.arraycopy( + * sampleDataBuffer, + * bytesWrittenCount - offset, + * sampleDataBuffer, + * \/* destPos= *\/ 0, + * \/* size= *\/ offset); + * publishSample(sampleData, timeUs, flags); + * } + * } + * + * </pre> */ -// @HiddenApi public final class MediaParser { - /** Maps seek positions to corresponding positions in the stream. */ + /** + * Maps seek positions to {@link SeekPoint SeekPoints} in the stream. + * + * <p>A {@link SeekPoint} is a position in the stream from which a player may successfully start + * playing media samples. + */ public interface SeekMap { /** Returned by {@link #getDurationUs()} when the duration is unknown. */ @@ -62,13 +176,14 @@ public final class MediaParser { * <p>{@code getSeekPoints(timeUs).first} contains the latest seek point for samples with * timestamp equal to or smaller than {@code timeUs}. * - * <p>{@code getSeekPoints(timeUs).second} contains the earlies seek point for samples with + * <p>{@code getSeekPoints(timeUs).second} contains the earliest seek point for samples with * timestamp equal to or greater than {@code timeUs}. If a seek point exists for {@code * timeUs}, the returned pair will contain the same {@link SeekPoint} twice. * * @param timeUs A seek time in microseconds. * @return The corresponding {@link SeekPoint SeekPoints}. */ + @NonNull Pair<SeekPoint, SeekPoint> getSeekPoints(long timeUs); } @@ -76,30 +191,30 @@ public final class MediaParser { public static final class SeekPoint { /** A {@link SeekPoint} whose time and byte offset are both set to 0. */ - public static final SeekPoint START = new SeekPoint(0, 0); + public static final @NonNull SeekPoint START = new SeekPoint(0, 0); /** The time of the seek point, in microseconds. */ - public final long mTimeUs; + public final long timeUs; /** The byte offset of the seek point. */ - public final long mPosition; + public final long position; /** * @param timeUs The time of the seek point, in microseconds. * @param position The byte offset of the seek point. */ - public SeekPoint(long timeUs, long position) { - this.mTimeUs = timeUs; - this.mPosition = position; + private SeekPoint(long timeUs, long position) { + this.timeUs = timeUs; + this.position = position; } @Override - public String toString() { - return "[timeUs=" + mTimeUs + ", position=" + mPosition + "]"; + public @NonNull String toString() { + return "[timeUs=" + timeUs + ", position=" + position + "]"; } @Override - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { if (this == obj) { return true; } @@ -107,25 +222,26 @@ public final class MediaParser { return false; } SeekPoint other = (SeekPoint) obj; - return mTimeUs == other.mTimeUs && mPosition == other.mPosition; + return timeUs == other.timeUs && position == other.position; } @Override public int hashCode() { - int result = (int) mTimeUs; - result = 31 * result + (int) mPosition; + int result = (int) timeUs; + result = 31 * result + (int) position; return result; } } /** Provides input data to {@link MediaParser}. */ - public interface Input { + public interface InputReader { /** * Reads up to {@code readLength} bytes of data and stores them into {@code buffer}, * starting at index {@code offset}. * - * <p>The call will block until at least one byte of data has been read. + * <p>This method blocks until at least one byte is read, the end of input is detected, or + * an exception is thrown. The read position advances to the first unread byte. * * @param buffer The buffer into which the read data should be stored. * @param offset The start offset into {@code buffer} at which data should be written. @@ -134,7 +250,7 @@ public final class MediaParser { * of the input has been reached. * @throws java.io.IOException If an error occurs reading from the source. */ - int read(byte[] buffer, int offset, int readLength) + int read(@NonNull byte[] buffer, int offset, int readLength) throws IOException, InterruptedException; /** Returns the current read position (byte offset) in the stream. */ @@ -144,22 +260,39 @@ public final class MediaParser { long getLength(); } - /** Receives extracted media sample data and metadata from {@link MediaParser}. */ - public interface OutputCallback { + /** {@link InputReader} that allows setting the read position. */ + public interface SeekableInputReader extends InputReader { /** - * Called when the number of tracks is defined. + * Sets the read position at the given {@code position}. * - * @param numberOfTracks The number of tracks in the stream. + * <p>{@link #advance} will immediately return after calling this method. + * + * @param position The position to seek to, in bytes. */ - void onTracksFound(int numberOfTracks); + void seekToPosition(long position); + } + + /** Receives extracted media sample data and metadata from {@link MediaParser}. */ + public interface OutputConsumer { /** * Called when a {@link SeekMap} has been extracted from the stream. * + * <p>This method is called at least once before any samples are {@link #onSampleCompleted + * complete}. May be called multiple times after that in order to add {@link SeekPoint + * SeekPoints}. + * * @param seekMap The extracted {@link SeekMap}. */ - void onSeekMap(SeekMap seekMap); + void onSeekMap(@NonNull SeekMap seekMap); + + /** + * Called when the number of tracks is found. + * + * @param numberOfTracks The number of tracks in the stream. + */ + void onTracksFound(int numberOfTracks); /** * Called when the {@link MediaFormat} of the track is extracted from the stream. @@ -167,7 +300,7 @@ public final class MediaParser { * @param trackIndex The index of the track for which the {@link MediaFormat} was found. * @param format The extracted {@link MediaFormat}. */ - void onFormat(int trackIndex, MediaFormat format); + void onFormat(int trackIndex, @NonNull MediaFormat format); /** * Called to write sample data to the output. @@ -176,16 +309,15 @@ public final class MediaParser { * thrown {@link IOException} caused by reading from {@code input}. * * @param trackIndex The index of the track to which the sample data corresponds. - * @param input The {@link Input} from which to read the data. - * @return + * @param inputReader The {@link InputReader} from which to read the data. */ - int onSampleData(int trackIndex, Input input) throws IOException, InterruptedException; + void onSampleData(int trackIndex, @NonNull InputReader inputReader) + throws IOException, InterruptedException; /** - * Defines the boundaries and metadata of an extracted sample. + * Called once all the data of a sample has been passed to {@link #onSampleData}. * - * <p>The corresponding sample data will have already been passed to the output via calls to - * {@link #onSampleData}. + * <p>Also includes sample metadata, like presentation timestamp and flags. * * @param trackIndex The index of the track to which the sample corresponds. * @param timeUs The media timestamp associated with the sample, in microseconds. @@ -203,57 +335,22 @@ public final class MediaParser { int flags, int size, int offset, - MediaCodec.CryptoInfo cryptoData); - } - - /** - * Controls the behavior of extractors' implementations. - * - * <p>DESIGN NOTE: For setting flags like workarounds and special behaviors for adaptive - * streaming. - */ - public static final class Parameters { - - // TODO: Implement. - - } - - /** Holds the result of an {@link #advance} invocation. */ - public static final class ResultHolder { - - /** Creates a new instance with {@link #result} holding {@link #ADVANCE_RESULT_CONTINUE}. */ - public ResultHolder() { - result = ADVANCE_RESULT_CONTINUE; - } - - /** - * May hold {@link #ADVANCE_RESULT_END_OF_INPUT}, {@link #ADVANCE_RESULT_CONTINUE}, {@link - * #ADVANCE_RESULT_SEEK}. - */ - public int result; - - /** - * If {@link #result} holds {@link #ADVANCE_RESULT_SEEK}, holds the stream position required - * from the passed {@link Input} to the next {@link #advance} call. If {@link #result} does - * not hold {@link #ADVANCE_RESULT_SEEK}, the value of this variable is undefined and should - * be ignored. - */ - public long seekPosition; + @Nullable MediaCodec.CryptoInfo cryptoData); } /** * Thrown if all extractors implementations provided to {@link #create} failed to sniff the * input content. */ - // @HiddenApi public static final class UnrecognizedInputFormatException extends IOException { /** * Creates a new instance which signals that the extractors with the given names failed to * parse the input. */ - public static UnrecognizedInputFormatException createForExtractors( - String... extractorNames) { + @NonNull + private static UnrecognizedInputFormatException createForExtractors( + @NonNull String... extractorNames) { StringBuilder builder = new StringBuilder(); builder.append("None of the available extractors ( "); builder.append(extractorNames[0]); @@ -270,21 +367,9 @@ public final class MediaParser { } } - // Public constants. + // Private constants. - /** - * Returned by {@link #advance} if the {@link Input} passed to the next {@link #advance} is - * required to provide data continuing from the position in the stream reached by the returning - * call. - */ - public static final int ADVANCE_RESULT_CONTINUE = -1; - /** Returned by {@link #advance} if the end of the {@link Input} was reached. */ - public static final int ADVANCE_RESULT_END_OF_INPUT = -2; - /** - * Returned by {@link #advance} when its next call expects a specific stream position, which - * will be held by {@link ResultHolder#seekPosition}. - */ - public static final int ADVANCE_RESULT_SEEK = -3; + private static final Map<String, ExtractorFactory> EXTRACTOR_FACTORIES_BY_NAME; // Instance creation methods. @@ -293,13 +378,15 @@ public final class MediaParser { * instance will attempt extraction without sniffing the content. * * @param name The name of the extractor that will be associated with the created instance. - * @param outputCallback The {@link OutputCallback} to which track data and samples are pushed. - * @param parameters Parameters that control specific aspects of the behavior of the extractors. + * @param outputConsumer The {@link OutputConsumer} to which track data and samples are pushed. * @return A new instance. + * @throws IllegalArgumentException If an invalid name is provided. */ - public static MediaParser createByName( - String name, OutputCallback outputCallback, Parameters parameters) { - throw new UnsupportedOperationException(); + public static @NonNull MediaParser createByName( + @NonNull String name, @NonNull OutputConsumer outputConsumer) { + String[] nameAsArray = new String[] {name}; + assertValidNames(nameAsArray); + return new MediaParser(outputConsumer, /* sniff= */ false, name); } /** @@ -307,30 +394,46 @@ public final class MediaParser { * the first {@link #advance} call. Extractor implementations will sniff the content in order of * appearance in {@code extractorNames}. * - * @param outputCallback The {@link OutputCallback} to track data and samples are obtained. - * @param parameters Parameters that control specific aspects of the behavior of the extractors. + * @param outputConsumer The {@link OutputConsumer} to which extracted data is output. * @param extractorNames The names of the extractors to sniff the content with. If empty, a * default array of names is used. * @return A new instance. */ - public static MediaParser create( - OutputCallback outputCallback, Parameters parameters, String... extractorNames) { - throw new UnsupportedOperationException(); + public static @NonNull MediaParser create( + @NonNull OutputConsumer outputConsumer, @NonNull String... extractorNames) { + assertValidNames(extractorNames); + if (extractorNames.length == 0) { + extractorNames = EXTRACTOR_FACTORIES_BY_NAME.keySet().toArray(new String[0]); + } + return new MediaParser(outputConsumer, /* sniff= */ true, extractorNames); } // Misc static methods. /** * Returns an immutable list with the names of the extractors that are suitable for container - * formats with the given {@code mimeTypes}. If an empty string is passed, all available - * extractors' names are returned. + * formats with the given {@link MediaFormat}. * - * <p>TODO: Replace string with media type object. + * <p>TODO: List which properties are taken into account. E.g. MimeType. */ - public static List<String> getExtractorNames(String mimeTypes) { + public static @NonNull List<String> getExtractorNames(@NonNull MediaFormat mediaFormat) { throw new UnsupportedOperationException(); } + // Private fields. + + private final OutputConsumer mOutputConsumer; + private final String[] mExtractorNamesPool; + private final PositionHolder mPositionHolder; + private final InputReadingDataSource mDataSource; + private final ExtractorInputAdapter mScratchExtractorInputAdapter; + private final ParsableByteArrayAdapter mScratchParsableByteArrayAdapter; + private String mExtractorName; + private Extractor mExtractor; + private ExtractorInput mExtractorInput; + private long mPendingSeekPosition; + private long mPendingSeekTimeUs; + // Public methods. /** @@ -344,8 +447,8 @@ public final class MediaParser { * @return The name of the backing extractor implementation, or null if the backing extractor * implementation has not yet been selected. */ - public String getExtractorName() { - throw new UnsupportedOperationException(); + public @Nullable String getExtractorName() { + return mExtractorName; } /** @@ -357,26 +460,85 @@ public final class MediaParser { * <p>If this instance was created using {@link #create}. the first call to this method will * sniff the content with the extractors with the provided names. * - * @param input The {@link Input} from which to obtain the media container data. - * @param resultHolder The {@link ResultHolder} into which the result of the operation will be - * written. + * @param seekableInputReader The {@link SeekableInputReader} from which to obtain the media + * container data. + * @return Whether there is any data left to extract. Returns false if the end of input has been + * reached. * @throws UnrecognizedInputFormatException */ - public void advance(Input input, ResultHolder resultHolder) + public boolean advance(@NonNull SeekableInputReader seekableInputReader) throws IOException, InterruptedException { - throw new UnsupportedOperationException(); + if (mExtractorInput == null) { + // TODO: For efficiency, the same implementation should be used, by providing a + // clearBuffers() method, or similar. + mExtractorInput = + new DefaultExtractorInput( + mDataSource, + seekableInputReader.getPosition(), + seekableInputReader.getLength()); + } + mDataSource.mInputReader = seekableInputReader; + + if (mExtractor == null) { + for (String extractorName : mExtractorNamesPool) { + Extractor extractor = + EXTRACTOR_FACTORIES_BY_NAME.get(extractorName).createInstance(); + try { + if (extractor.sniff(mExtractorInput)) { + mExtractorName = extractorName; + mExtractor = extractor; + mExtractor.init(new ExtractorOutputAdapter()); + break; + } + } catch (EOFException e) { + // Do nothing. + } catch (IOException | InterruptedException e) { + throw new IllegalStateException(e); + } finally { + mExtractorInput.resetPeekPosition(); + } + } + if (mExtractor == null) { + UnrecognizedInputFormatException.createForExtractors(mExtractorNamesPool); + } + return true; + } + + if (isPendingSeek()) { + mExtractor.seek(mPendingSeekPosition, mPendingSeekTimeUs); + removePendingSeek(); + } + + mPositionHolder.position = seekableInputReader.getPosition(); + int result = mExtractor.read(mExtractorInput, mPositionHolder); + if (result == Extractor.RESULT_END_OF_INPUT) { + return false; + } + if (result == Extractor.RESULT_SEEK) { + mExtractorInput = null; + seekableInputReader.seekToPosition(mPositionHolder.position); + } + return true; } /** * Seeks within the media container being extracted. * - * <p>Following a call to this method, the {@link Input} passed to the next invocation of {@link - * #advance} must provide data starting from {@link SeekPoint#mPosition} in the stream. + * <p>{@link SeekPoint SeekPoints} can be obtained from the {@link SeekMap} passed to {@link + * OutputConsumer#onSeekMap(SeekMap)}. + * + * <p>Following a call to this method, the {@link InputReader} passed to the next invocation of + * {@link #advance} must provide data starting from {@link SeekPoint#position} in the stream. * * @param seekPoint The {@link SeekPoint} to seek to. */ - public void seek(SeekPoint seekPoint) { - throw new UnsupportedOperationException(); + public void seek(@NonNull SeekPoint seekPoint) { + if (mExtractor == null) { + mPendingSeekPosition = seekPoint.position; + mPendingSeekTimeUs = seekPoint.timeUs; + } else { + mExtractor.seek(seekPoint.position, seekPoint.timeUs); + } } /** @@ -386,6 +548,359 @@ public final class MediaParser { * invoked. DESIGN NOTE: Should be removed. There shouldn't be any resource for releasing. */ public void release() { - throw new UnsupportedOperationException(); + mExtractorInput = null; + mExtractor = null; + } + + // Private methods. + + private MediaParser( + OutputConsumer outputConsumer, boolean sniff, String... extractorNamesPool) { + mOutputConsumer = outputConsumer; + mExtractorNamesPool = extractorNamesPool; + if (!sniff) { + mExtractorName = extractorNamesPool[0]; + mExtractor = EXTRACTOR_FACTORIES_BY_NAME.get(mExtractorName).createInstance(); + } + mPositionHolder = new PositionHolder(); + mDataSource = new InputReadingDataSource(); + removePendingSeek(); + mScratchExtractorInputAdapter = new ExtractorInputAdapter(); + mScratchParsableByteArrayAdapter = new ParsableByteArrayAdapter(); + } + + private boolean isPendingSeek() { + return mPendingSeekPosition >= 0; + } + + private void removePendingSeek() { + mPendingSeekPosition = -1; + mPendingSeekTimeUs = -1; + } + + // Private classes. + + private static final class InputReadingDataSource implements DataSource { + + public InputReader mInputReader; + + @Override + public void addTransferListener(TransferListener transferListener) { + // Do nothing. + } + + @Override + public long open(DataSpec dataSpec) { + throw new UnsupportedOperationException(); + } + + @Override + public int read(byte[] buffer, int offset, int readLength) throws IOException { + // TODO: Reevaluate interruption in Input. + try { + return mInputReader.read(buffer, offset, readLength); + } catch (InterruptedException e) { + // TODO: Remove. + throw new RuntimeException(); + } + } + + @Override + public Uri getUri() { + return null; + } + + @Override + public Map<String, List<String>> getResponseHeaders() { + return null; + } + + @Override + public void close() { + throw new UnsupportedOperationException(); + } + } + + private final class ExtractorOutputAdapter implements ExtractorOutput { + + private final SparseArray<TrackOutput> mTrackOutputAdapters; + private boolean mTracksEnded; + + private ExtractorOutputAdapter() { + mTrackOutputAdapters = new SparseArray<>(); + } + + @Override + public TrackOutput track(int id, int type) { + TrackOutput trackOutput = mTrackOutputAdapters.get(id); + if (trackOutput == null) { + trackOutput = new TrackOutputAdapter(mTrackOutputAdapters.size()); + mTrackOutputAdapters.put(id, trackOutput); + } + return trackOutput; + } + + @Override + public void endTracks() { + mOutputConsumer.onTracksFound(mTrackOutputAdapters.size()); + } + + @Override + public void seekMap(com.google.android.exoplayer2.extractor.SeekMap exoplayerSeekMap) { + mOutputConsumer.onSeekMap(new ExoToMediaParserSeekMapAdapter(exoplayerSeekMap)); + } + } + + private class TrackOutputAdapter implements TrackOutput { + + private final int mTrackIndex; + + private TrackOutputAdapter(int trackIndex) { + mTrackIndex = trackIndex; + } + + @Override + public void format(Format format) { + mOutputConsumer.onFormat(mTrackIndex, toMediaFormat(format)); + } + + @Override + public int sampleData(ExtractorInput input, int length, boolean allowEndOfInput) + throws IOException, InterruptedException { + mScratchExtractorInputAdapter.setExtractorInput(input, length); + long positionBeforeReading = mScratchExtractorInputAdapter.getPosition(); + mOutputConsumer.onSampleData(mTrackIndex, mScratchExtractorInputAdapter); + return (int) (mScratchExtractorInputAdapter.getPosition() - positionBeforeReading); + } + + @Override + public void sampleData(ParsableByteArray data, int length) { + mScratchParsableByteArrayAdapter.resetWithByteArray(data, length); + try { + mOutputConsumer.onSampleData(mTrackIndex, mScratchParsableByteArrayAdapter); + } catch (IOException | InterruptedException e) { + // Unexpected. + throw new RuntimeException(e); + } + } + + @Override + public void sampleMetadata( + long timeUs, int flags, int size, int offset, CryptoData encryptionData) { + mOutputConsumer.onSampleCompleted( + mTrackIndex, timeUs, flags, size, offset, toCryptoInfo(encryptionData)); + } + } + + private static final class ExtractorInputAdapter implements InputReader { + + private ExtractorInput mExtractorInput; + private int mCurrentPosition; + private long mLength; + + public void setExtractorInput(ExtractorInput extractorInput, long length) { + mExtractorInput = extractorInput; + mCurrentPosition = 0; + mLength = length; + } + + // Input implementation. + + @Override + public int read(byte[] buffer, int offset, int readLength) + throws IOException, InterruptedException { + int readBytes = mExtractorInput.read(buffer, offset, readLength); + mCurrentPosition += readBytes; + return readBytes; + } + + @Override + public long getPosition() { + return mCurrentPosition; + } + + @Override + public long getLength() { + return mLength - mCurrentPosition; + } + } + + private static final class ParsableByteArrayAdapter implements InputReader { + + private ParsableByteArray mByteArray; + private long mLength; + private int mCurrentPosition; + + public void resetWithByteArray(ParsableByteArray byteArray, long length) { + mByteArray = byteArray; + mCurrentPosition = 0; + mLength = length; + } + + // Input implementation. + + @Override + public int read(byte[] buffer, int offset, int readLength) { + mByteArray.readBytes(buffer, offset, readLength); + mCurrentPosition += readLength; + return readLength; + } + + @Override + public long getPosition() { + return mCurrentPosition; + } + + @Override + public long getLength() { + return mLength - mCurrentPosition; + } + } + + /** Creates extractor instances. */ + private interface ExtractorFactory { + + /** Returns a new extractor instance. */ + Extractor createInstance(); + } + + private static class ExoToMediaParserSeekMapAdapter implements SeekMap { + + private final com.google.android.exoplayer2.extractor.SeekMap mExoPlayerSeekMap; + + private ExoToMediaParserSeekMapAdapter( + com.google.android.exoplayer2.extractor.SeekMap exoplayerSeekMap) { + mExoPlayerSeekMap = exoplayerSeekMap; + } + + @Override + public boolean isSeekable() { + return mExoPlayerSeekMap.isSeekable(); + } + + @Override + public long getDurationUs() { + return mExoPlayerSeekMap.getDurationUs(); + } + + @Override + public Pair<SeekPoint, SeekPoint> getSeekPoints(long timeUs) { + SeekPoints seekPoints = mExoPlayerSeekMap.getSeekPoints(timeUs); + return new Pair<>(toSeekPoint(seekPoints.first), toSeekPoint(seekPoints.second)); + } + } + + // Private static methods. + + private static MediaFormat toMediaFormat(Format format) { + + // TODO: Add if (value != Format.NO_VALUE); + + MediaFormat result = new MediaFormat(); + result.setInteger(MediaFormat.KEY_BIT_RATE, format.bitrate); + result.setInteger(MediaFormat.KEY_CHANNEL_COUNT, format.channelCount); + if (format.colorInfo != null) { + result.setInteger(MediaFormat.KEY_COLOR_TRANSFER, format.colorInfo.colorTransfer); + result.setInteger(MediaFormat.KEY_COLOR_RANGE, format.colorInfo.colorRange); + result.setInteger(MediaFormat.KEY_COLOR_STANDARD, format.colorInfo.colorSpace); + if (format.colorInfo.hdrStaticInfo != null) { + result.setByteBuffer( + MediaFormat.KEY_HDR_STATIC_INFO, + ByteBuffer.wrap(format.colorInfo.hdrStaticInfo)); + } + } + result.setString(MediaFormat.KEY_MIME, format.sampleMimeType); + result.setFloat(MediaFormat.KEY_FRAME_RATE, format.frameRate); + result.setInteger(MediaFormat.KEY_WIDTH, format.width); + result.setInteger(MediaFormat.KEY_HEIGHT, format.height); + List<byte[]> initData = format.initializationData; + if (initData != null) { + for (int i = 0; i < initData.size(); i++) { + result.setByteBuffer("csd-" + i, ByteBuffer.wrap(initData.get(i))); + } + } + result.setString(MediaFormat.KEY_LANGUAGE, format.language); + result.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, format.maxInputSize); + result.setInteger(MediaFormat.KEY_PCM_ENCODING, format.pcmEncoding); + result.setInteger(MediaFormat.KEY_ROTATION, format.rotationDegrees); + result.setInteger(MediaFormat.KEY_SAMPLE_RATE, format.sampleRate); + + int selectionFlags = format.selectionFlags; + // We avoid setting selection flags in the MediaFormat, unless explicitly signaled by the + // extractor. + if ((selectionFlags & C.SELECTION_FLAG_AUTOSELECT) != 0) { + result.setInteger(MediaFormat.KEY_IS_AUTOSELECT, 1); + } + if ((selectionFlags & C.SELECTION_FLAG_DEFAULT) != 0) { + result.setInteger(MediaFormat.KEY_IS_DEFAULT, 1); + } + if ((selectionFlags & C.SELECTION_FLAG_FORCED) != 0) { + result.setInteger(MediaFormat.KEY_IS_FORCED_SUBTITLE, 1); + } + + // LACK OF SUPPORT FOR: + // format.accessibilityChannel; + // format.codecs; + // format.containerMimeType; + // format.drmInitData; + // format.encoderDelay; + // format.encoderPadding; + // format.id; + // format.metadata; + // format.pixelWidthHeightRatio; + // format.roleFlags; + // format.stereoMode; + // format.subsampleOffsetUs; + return result; + } + + private static int toFrameworkFlags(int flags) { + // TODO: Implement. + return 0; + } + + private static MediaCodec.CryptoInfo toCryptoInfo(TrackOutput.CryptoData encryptionData) { + // TODO: Implement. + return null; + } + + /** Returns a new {@link SeekPoint} equivalent to the given {@code exoPlayerSeekPoint}. */ + private static SeekPoint toSeekPoint( + com.google.android.exoplayer2.extractor.SeekPoint exoPlayerSeekPoint) { + return new SeekPoint(exoPlayerSeekPoint.timeUs, exoPlayerSeekPoint.position); + } + + private static void assertValidNames(@NonNull String[] names) { + for (String name : names) { + if (!EXTRACTOR_FACTORIES_BY_NAME.containsKey(name)) { + throw new IllegalArgumentException( + "Invalid extractor name: " + + name + + ". Supported extractors are: " + + TextUtils.join(", ", EXTRACTOR_FACTORIES_BY_NAME.keySet()) + + "."); + } + } + } + + // Static initialization. + + static { + // Using a LinkedHashMap to keep the insertion order when iterating over the keys. + LinkedHashMap<String, ExtractorFactory> extractorFactoriesByName = new LinkedHashMap<>(); + extractorFactoriesByName.put("exo.Ac3Extractor", Ac3Extractor::new); + extractorFactoriesByName.put("exo.Ac4Extractor", Ac4Extractor::new); + extractorFactoriesByName.put("exo.AdtsExtractor", AdtsExtractor::new); + extractorFactoriesByName.put("exo.AmrExtractor", AmrExtractor::new); + extractorFactoriesByName.put("exo.FlvExtractor", FlvExtractor::new); + extractorFactoriesByName.put("exo.FragmentedMp4Extractor", FragmentedMp4Extractor::new); + extractorFactoriesByName.put("exo.MatroskaExtractor", MatroskaExtractor::new); + extractorFactoriesByName.put("exo.Mp3Extractor", Mp3Extractor::new); + extractorFactoriesByName.put("exo.Mp4Extractor", Mp4Extractor::new); + extractorFactoriesByName.put("exo.OggExtractor", OggExtractor::new); + extractorFactoriesByName.put("exo.PsExtractor", PsExtractor::new); + extractorFactoriesByName.put("exo.TsExtractor", TsExtractor::new); + extractorFactoriesByName.put("exo.WavExtractor", WavExtractor::new); + EXTRACTOR_FACTORIES_BY_NAME = Collections.unmodifiableMap(extractorFactoriesByName); } } diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java index 63657a6c4a5c..6523e30c13cc 100644 --- a/media/java/android/media/MediaDrm.java +++ b/media/java/android/media/MediaDrm.java @@ -36,9 +36,12 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; @@ -203,6 +206,16 @@ public final class MediaDrm implements AutoCloseable { securityLevel); } + /** + * @return list of crypto schemes (as {@link UUID}s) for which + * {@link #isCryptoSchemeSupported(UUID)} returns true; each {@link UUID} + * can be used as input to create {@link MediaDrm} objects via {@link #MediaDrm(UUID)}. + */ + public static final @NonNull List<UUID> getSupportedCryptoSchemes(){ + byte[] uuidBytes = getSupportedCryptoSchemesNative(); + return getUUIDsFromByteArray(uuidBytes); + } + private static final byte[] getByteArrayFromUUID(@NonNull UUID uuid) { long msb = uuid.getMostSignificantBits(); long lsb = uuid.getLeastSignificantBits(); @@ -216,6 +229,28 @@ public final class MediaDrm implements AutoCloseable { return uuidBytes; } + private static final UUID getUUIDFromByteArray(@NonNull byte[] uuidBytes, int off) { + long msb = 0; + long lsb = 0; + + for (int i = 0; i < 8; ++i) { + msb = (msb << 8) | (0xffl & uuidBytes[off + i]); + lsb = (lsb << 8) | (0xffl & uuidBytes[off + i + 8]); + } + + return new UUID(msb, lsb); + } + + private static final List<UUID> getUUIDsFromByteArray(@NonNull byte[] uuidBytes) { + Set<UUID> uuids = new LinkedHashSet<>(); + for (int off = 0; off < uuidBytes.length; off+=16) { + uuids.add(getUUIDFromByteArray(uuidBytes, off)); + } + return new ArrayList<>(uuids); + } + + private static final native byte[] getSupportedCryptoSchemesNative(); + private static final native boolean isCryptoSchemeSupportedNative( @NonNull byte[] uuid, @Nullable String mimeType, @SecurityLevel int securityLevel); diff --git a/media/jni/Android.bp b/media/jni/Android.bp index 064ac75d3a84..12b3e6735d81 100644 --- a/media/jni/Android.bp +++ b/media/jni/Android.bp @@ -59,6 +59,7 @@ cc_library_shared { "libsonivox", "android.hardware.cas@1.0", "android.hardware.cas.native@1.0", + "android.hardware.drm@1.3", "android.hidl.memory@1.0", "android.hidl.token@1.0-utils", ], diff --git a/media/jni/android_media_MediaDrm.cpp b/media/jni/android_media_MediaDrm.cpp index acda18ea3dc2..f38a29c69a3e 100644 --- a/media/jni/android_media_MediaDrm.cpp +++ b/media/jni/android_media_MediaDrm.cpp @@ -27,6 +27,7 @@ #include "jni.h" #include <nativehelper/JNIHelp.h> +#include <android/hardware/drm/1.3/IDrmFactory.h> #include <binder/Parcel.h> #include <binder/PersistableBundle.h> #include <cutils/properties.h> @@ -38,7 +39,7 @@ #include <mediadrm/IDrm.h> using ::android::os::PersistableBundle; - +namespace drm = ::android::hardware::drm; namespace android { @@ -971,6 +972,26 @@ DrmPlugin::SecurityLevel jintToSecurityLevel(jint jlevel) { return level; } +static jbyteArray android_media_MediaDrm_getSupportedCryptoSchemesNative(JNIEnv *env) { + std::vector<uint8_t> bv; + for (auto &factory : DrmUtils::MakeDrmFactories()) { + sp<drm::V1_3::IDrmFactory> factoryV1_3 = drm::V1_3::IDrmFactory::castFrom(factory); + if (factoryV1_3 == nullptr) { + continue; + } + factoryV1_3->getSupportedCryptoSchemes( + [&](const hardware::hidl_vec<hardware::hidl_array<uint8_t, 16>>& schemes) { + for (const auto &scheme : schemes) { + bv.insert(bv.end(), scheme.data(), scheme.data() + scheme.size()); + } + }); + } + + jbyteArray jUuidBytes = env->NewByteArray(bv.size()); + env->SetByteArrayRegion(jUuidBytes, 0, bv.size(), reinterpret_cast<const jbyte *>(bv.data())); + return jUuidBytes; +} + static jboolean android_media_MediaDrm_isCryptoSchemeSupportedNative( JNIEnv *env, jobject /* thiz */, jbyteArray uuidObj, jstring jmimeType, jint jSecurityLevel) { @@ -1941,6 +1962,9 @@ static const JNINativeMethod gMethods[] = { { "native_setup", "(Ljava/lang/Object;[BLjava/lang/String;)V", (void *)android_media_MediaDrm_native_setup }, + { "getSupportedCryptoSchemesNative", "()[B", + (void *)android_media_MediaDrm_getSupportedCryptoSchemesNative }, + { "isCryptoSchemeSupportedNative", "([BLjava/lang/String;I)Z", (void *)android_media_MediaDrm_isCryptoSchemeSupportedNative }, 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 bcb76d7169ec..c8532e006489 100644 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java +++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java @@ -119,11 +119,11 @@ import com.android.systemui.statusbar.phone.NotificationGroupAlertTransferHelper import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.phone.StatusBar; +import com.android.systemui.statusbar.phone.StatusBarComponent; import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.phone.StatusBarNotificationActivityStarter; import com.android.systemui.statusbar.phone.StatusBarWindowController; -import com.android.systemui.statusbar.phone.StatusBarWindowViewController; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; @@ -140,6 +140,7 @@ import java.util.Map; import java.util.Optional; import javax.inject.Named; +import javax.inject.Provider; import dagger.Lazy; @@ -156,7 +157,6 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt private static final float FLING_SPEED_UP_FACTOR = 0.6f; private final ScrimController mScrimController; - private final StatusBarWindowViewController mStatusBarWindowViewController; private final LockscreenLockIconController mLockscreenLockIconController; private float mOpeningVelocity = DEFAULT_FLING_VELOCITY; @@ -174,7 +174,6 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt private final Object mQueueLock = new Object(); private final CarNavigationBarController mCarNavigationBarController; - private final Lazy<DrivingStateHelper> mDrivingStateHelperLazy; private final Lazy<PowerManagerHelper> mPowerManagerHelperLazy; private final CarServiceProvider mCarServiceProvider; @@ -289,7 +288,6 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt NotificationListener notificationListener, ConfigurationController configurationController, StatusBarWindowController statusBarWindowController, - StatusBarWindowViewController statusBarWindowViewController, LockscreenLockIconController lockscreenLockIconController, DozeParameters dozeParameters, ScrimController scrimController, @@ -302,6 +300,7 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt VolumeComponent volumeComponent, CommandQueue commandQueue, Optional<Recents> recents, + Provider<StatusBarComponent.Builder> statusBarComponentBuilder, PluginManager pluginManager, RemoteInputUriController remoteInputUriController, Optional<Divider> dividerOptional, @@ -314,7 +313,6 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt DismissCallbackRegistry dismissCallbackRegistry, /* Car Settings injected components. */ CarServiceProvider carServiceProvider, - Lazy<DrivingStateHelper> drivingStateHelperLazy, Lazy<PowerManagerHelper> powerManagerHelperLazy, Lazy<FullscreenUserSwitcher> fullscreenUserSwitcherLazy, CarNavigationBarController carNavigationBarController) { @@ -368,7 +366,6 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt notificationListener, configurationController, statusBarWindowController, - statusBarWindowViewController, lockscreenLockIconController, dozeParameters, scrimController, @@ -382,6 +379,7 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt volumeComponent, commandQueue, recents, + statusBarComponentBuilder, pluginManager, remoteInputUriController, dividerOptional, @@ -392,11 +390,9 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt viewMediatorCallback, dismissCallbackRegistry); mScrimController = scrimController; - mStatusBarWindowViewController = statusBarWindowViewController; mLockscreenLockIconController = lockscreenLockIconController; mDeviceProvisionedController = deviceProvisionedController; mCarServiceProvider = carServiceProvider; - mDrivingStateHelperLazy = drivingStateHelperLazy; mPowerManagerHelperLazy = powerManagerHelperLazy; mFullscreenUserSwitcherLazy = fullscreenUserSwitcherLazy; mCarNavigationBarController = carNavigationBarController; diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java index 6529868c19ad..eff60fa246a4 100644 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java +++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java @@ -78,11 +78,11 @@ 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.ScrimController; +import com.android.systemui.statusbar.phone.StatusBarComponent; import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.phone.StatusBarNotificationActivityStarter; import com.android.systemui.statusbar.phone.StatusBarWindowController; -import com.android.systemui.statusbar.phone.StatusBarWindowViewController; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; @@ -96,6 +96,7 @@ import com.android.systemui.volume.VolumeComponent; import java.util.Optional; import javax.inject.Named; +import javax.inject.Provider; import javax.inject.Singleton; import dagger.Lazy; @@ -162,7 +163,6 @@ public class CarStatusBarModule { NotificationListener notificationListener, ConfigurationController configurationController, StatusBarWindowController statusBarWindowController, - StatusBarWindowViewController statusBarWindowViewController, LockscreenLockIconController lockscreenLockIconController, DozeParameters dozeParameters, ScrimController scrimController, @@ -175,6 +175,7 @@ public class CarStatusBarModule { VolumeComponent volumeComponent, CommandQueue commandQueue, Optional<Recents> recentsOptional, + Provider<StatusBarComponent.Builder> statusBarComponentBuilder, PluginManager pluginManager, RemoteInputUriController remoteInputUriController, Optional<Divider> dividerOptional, @@ -186,7 +187,6 @@ public class CarStatusBarModule { ViewMediatorCallback viewMediatorCallback, DismissCallbackRegistry dismissCallbackRegistry, CarServiceProvider carServiceProvider, - Lazy<DrivingStateHelper> drivingStateHelperLazy, Lazy<PowerManagerHelper> powerManagerHelperLazy, Lazy<FullscreenUserSwitcher> fullscreenUserSwitcherLazy, CarNavigationBarController carNavigationBarController) { @@ -240,7 +240,6 @@ public class CarStatusBarModule { notificationListener, configurationController, statusBarWindowController, - statusBarWindowViewController, lockscreenLockIconController, dozeParameters, scrimController, @@ -253,6 +252,7 @@ public class CarStatusBarModule { volumeComponent, commandQueue, recentsOptional, + statusBarComponentBuilder, pluginManager, remoteInputUriController, dividerOptional, @@ -263,7 +263,6 @@ public class CarStatusBarModule { viewMediatorCallback, dismissCallbackRegistry, carServiceProvider, - drivingStateHelperLazy, powerManagerHelperLazy, fullscreenUserSwitcherLazy, carNavigationBarController); diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/DrivingStateHelper.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/DrivingStateHelper.java deleted file mode 100644 index 60934ab11532..000000000000 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/DrivingStateHelper.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.car; - -import android.car.Car; -import android.car.drivingstate.CarDrivingStateEvent; -import android.car.drivingstate.CarDrivingStateManager; -import android.car.drivingstate.CarDrivingStateManager.CarDrivingStateEventListener; -import android.util.Log; - -import androidx.annotation.NonNull; - -import com.android.systemui.car.CarServiceProvider; - -import javax.inject.Inject; -import javax.inject.Singleton; - -/** - * Helper class for connecting to the {@link CarDrivingStateManager} and listening for driving state - * changes. - */ -@Singleton -public class DrivingStateHelper { - public static final String TAG = "DrivingStateHelper"; - - private final CarServiceProvider mCarServiceProvider; - - private CarDrivingStateManager mDrivingStateManager; - private CarDrivingStateEventListener mDrivingStateHandler; - - @Inject - public DrivingStateHelper(CarServiceProvider carServiceProvider) { - mCarServiceProvider = carServiceProvider; - } - - /** - * Sets the {@link CarDrivingStateEventListener}. Should be set before calling {@link - * #connectToCarService()}. - */ - public void setCarDrivingStateEventListener( - @NonNull CarDrivingStateEventListener carDrivingStateEventListener) { - mDrivingStateHandler = carDrivingStateEventListener; - } - - /** - * Queries {@link CarDrivingStateManager} for current driving state. Returns {@code true} if car - * is idling or moving, {@code false} otherwise. - */ - public boolean isCurrentlyDriving() { - if (mDrivingStateManager == null) { - return false; - } - CarDrivingStateEvent currentState = mDrivingStateManager.getCurrentCarDrivingState(); - if (currentState != null) { - return currentState.eventValue == CarDrivingStateEvent.DRIVING_STATE_IDLING - || currentState.eventValue == CarDrivingStateEvent.DRIVING_STATE_MOVING; - } - return false; // Default to false. - } - - /** - * Establishes connection with the Car service. - */ - public void connectToCarService() { - mCarServiceProvider.addListener(mCarServiceLifecycleListener); - } - - private final CarServiceProvider.CarServiceOnConnectedListener mCarServiceLifecycleListener = - car -> { - logD("Car Service connected"); - mDrivingStateManager = (CarDrivingStateManager) car.getCarManager( - Car.CAR_DRIVING_STATE_SERVICE); - if (mDrivingStateManager != null) { - mDrivingStateManager.registerListener(mDrivingStateHandler); - mDrivingStateHandler.onDrivingStateChanged( - mDrivingStateManager.getCurrentCarDrivingState()); - } else { - Log.e(TAG, "CarDrivingStateService service not available"); - } - }; - - private void logD(String message) { - if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, message); - } - } -} diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java index 7500bcd9b8f7..0a5f80f4f95e 100644 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java +++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java @@ -21,6 +21,7 @@ import static android.content.DialogInterface.BUTTON_POSITIVE; import static android.os.UserManager.DISALLOW_ADD_USER; import static android.os.UserManager.SWITCHABILITY_STATUS_OK; +import android.annotation.Nullable; import android.app.ActivityManager; import android.app.AlertDialog; import android.app.AlertDialog.Builder; @@ -266,7 +267,10 @@ public class UserGridRecyclerView extends RecyclerView { if (userRecord.mIsStartGuestSession) { notifyUserSelected(userRecord); - mCarUserManagerHelper.startGuestSession(mGuestName); + UserInfo guest = createNewOrFindExistingGuest(mContext); + if (guest != null) { + mCarUserManagerHelper.switchToUser(guest); + } return; } @@ -381,6 +385,24 @@ public class UserGridRecyclerView extends RecyclerView { return circleIcon; } + /** + * Finds the existing Guest user, or creates one if it doesn't exist. + * @param context App context + * @return UserInfo representing the Guest user + */ + @Nullable + public UserInfo createNewOrFindExistingGuest(Context context) { + // CreateGuest will return null if a guest already exists. + UserInfo newGuest = mUserManager.createGuest(context, mGuestName); + if (newGuest != null) { + new UserIconProvider().assignDefaultIcon( + mUserManager, context.getResources(), newGuest); + return newGuest; + } + + return mUserManager.findCurrentGuestUser(); + } + @Override public void onClick(DialogInterface dialog, int which) { if (which == BUTTON_POSITIVE) { diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserIconProvider.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserIconProvider.java index 9464eab2085b..9018290f4955 100644 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserIconProvider.java +++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserIconProvider.java @@ -88,7 +88,7 @@ public class UserIconProvider { * @param userInfo User whose avatar is set to default icon. * @return Bitmap of the user icon. */ - private Bitmap assignDefaultIcon( + public Bitmap assignDefaultIcon( UserManager userManager, Resources resources, UserInfo userInfo) { Bitmap bitmap = userInfo.isGuest() ? getGuestUserDefaultIcon(resources) diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 1a658f49d516..7f1d5280ab77 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -215,9 +215,12 @@ <!-- Permission required for CTS test - CrossProfileAppsHostSideTest --> <uses-permission android:name="android.permission.INTERACT_ACROSS_PROFILES"/> - <!-- Permission requried for CTS test - UiModeManagerTest --> + <!-- Permission required for CTS test - UiModeManagerTest --> <uses-permission android:name="android.permission.ENTER_CAR_MODE_PRIORITIZED"/> + <!-- Permission required for CTS test - CarModeInCallServiceTest --> + <uses-permission android:name="android.permission.CONTROL_INCALL_EXPERIENCE"/> + <application android:label="@string/app_label" android:theme="@android:style/Theme.DeviceDefault.DayNight" android:defaultToDeviceProtectedStorage="true" diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 0a671d9185f0..6f6803817138 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -288,6 +288,11 @@ android:exported="false" android:permission="com.android.systemui.permission.SELF" /> + <service android:name=".assist.AssistHandleService" + android:exported="true" + android:enabled="false" + /> + <!-- started from PhoneWindowManager TODO: Should have an android:permission attribute --> <service android:name=".screenshot.TakeScreenshotService" diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServiceController.java b/packages/SystemUI/src/com/android/systemui/ForegroundServiceController.java index c1a23c8ab003..41dd5bbf272b 100644 --- a/packages/SystemUI/src/com/android/systemui/ForegroundServiceController.java +++ b/packages/SystemUI/src/com/android/systemui/ForegroundServiceController.java @@ -37,7 +37,7 @@ import javax.inject.Singleton; */ @Singleton public class ForegroundServiceController { - private static final int[] APP_OPS = new int[] {AppOpsManager.OP_CAMERA, + public static final int[] APP_OPS = new int[] {AppOpsManager.OP_CAMERA, AppOpsManager.OP_SYSTEM_ALERT_WINDOW, AppOpsManager.OP_RECORD_AUDIO, AppOpsManager.OP_COARSE_LOCATION, @@ -139,6 +139,8 @@ public class ForegroundServiceController { } } + // TODO: (b/145659174) remove when moving to NewNotifPipeline. Replaced by + // ForegroundCoordinator // Update appOp if there's an associated pending or visible notification: final String foregroundKey = getStandardLayoutKey(userId, packageName); if (foregroundKey != null) { diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java b/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java index b983966317f7..8105faa23e89 100644 --- a/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java +++ b/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java @@ -27,6 +27,8 @@ import android.util.Log; import com.android.internal.statusbar.NotificationVisibility; import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.collection.NotifCollection; +import com.android.systemui.statusbar.notification.collection.NotifCollectionListener; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import javax.inject.Inject; @@ -46,9 +48,13 @@ public class ForegroundServiceNotificationListener { @Inject public ForegroundServiceNotificationListener(Context context, ForegroundServiceController foregroundServiceController, - NotificationEntryManager notificationEntryManager) { + NotificationEntryManager notificationEntryManager, + NotifCollection notifCollection) { mContext = context; mForegroundServiceController = foregroundServiceController; + + // TODO: (b/145659174) remove mEntryManager when moving to NewNotifPipeline. Replaced by + // ForegroundCoordinator mEntryManager = notificationEntryManager; mEntryManager.addNotificationEntryListener(new NotificationEntryListener() { @Override @@ -69,8 +75,24 @@ public class ForegroundServiceNotificationListener { removeNotification(entry.getSbn()); } }); - mEntryManager.addNotificationLifetimeExtender(new ForegroundServiceLifetimeExtender()); + + notifCollection.addCollectionListener(new NotifCollectionListener() { + @Override + public void onEntryAdded(NotificationEntry entry) { + addNotification(entry, entry.getImportance()); + } + + @Override + public void onEntryUpdated(NotificationEntry entry) { + updateNotification(entry, entry.getImportance()); + } + + @Override + public void onEntryRemoved(NotificationEntry entry, int reason, boolean removedByUser) { + removeNotification(entry.getSbn()); + } + }); } /** @@ -152,6 +174,8 @@ public class ForegroundServiceNotificationListener { true /* create if not found */); } + // TODO: (b/145659174) remove when moving to NewNotifPipeline. Replaced by + // ForegroundCoordinator private void tagForeground(NotificationEntry entry) { final StatusBarNotification sbn = entry.getSbn(); ArraySet<Integer> activeOps = mForegroundServiceController.getAppOps( diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServicesUserState.java b/packages/SystemUI/src/com/android/systemui/ForegroundServicesUserState.java index a8ae65490b28..2ef46dca317a 100644 --- a/packages/SystemUI/src/com/android/systemui/ForegroundServicesUserState.java +++ b/packages/SystemUI/src/com/android/systemui/ForegroundServicesUserState.java @@ -24,7 +24,7 @@ import java.util.Arrays; /** * Struct to track relevant packages and notifications for a userid's foreground services. */ -class ForegroundServicesUserState { +public class ForegroundServicesUserState { // shelf life of foreground services before they go bad private static final long FG_SERVICE_GRACE_MILLIS = 5000; diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleService.kt b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleService.kt new file mode 100644 index 000000000000..9ceafc674d34 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleService.kt @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.assist + +import android.app.Service +import android.content.Intent +import android.os.IBinder +import dagger.Lazy +import javax.inject.Inject + +class AssistHandleService @Inject constructor(private val assistManager: Lazy<AssistManager>) + : Service() { + + private val binder = object : IAssistHandleService.Stub() { + override fun requestAssistHandles() { + assistManager.get().requestAssistHandles() + } + } + + override fun onBind(intent: Intent?): IBinder? { + return binder + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistModule.java b/packages/SystemUI/src/com/android/systemui/assist/AssistModule.java index 6f5a17dca432..96939b010555 100644 --- a/packages/SystemUI/src/com/android/systemui/assist/AssistModule.java +++ b/packages/SystemUI/src/com/android/systemui/assist/AssistModule.java @@ -16,6 +16,7 @@ package com.android.systemui.assist; +import android.app.Service; import android.content.Context; import android.os.Handler; import android.os.HandlerThread; @@ -33,8 +34,11 @@ import java.util.Map; import javax.inject.Named; import javax.inject.Singleton; +import dagger.Binds; import dagger.Module; import dagger.Provides; +import dagger.multibindings.ClassKey; +import dagger.multibindings.IntoMap; /** Module for dagger injections related to the Assistant. */ @Module @@ -87,4 +91,9 @@ public abstract class AssistModule { static Clock provideSystemClock() { return SystemClock::uptimeMillis; } + + @Binds + @IntoMap + @ClassKey(AssistHandleService.class) + abstract Service bindAssistHandleService(AssistHandleService assistHandleService); } diff --git a/packages/SystemUI/src/com/android/systemui/assist/IAssistHandleService.aidl b/packages/SystemUI/src/com/android/systemui/assist/IAssistHandleService.aidl new file mode 100644 index 000000000000..ef07d9d3f182 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/assist/IAssistHandleService.aidl @@ -0,0 +1,24 @@ +/** + * Copyright (c) 2009, 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.assist; + +/** Interface implemented by AssisthandleService and called by on-device intelligence. */ +interface IAssistHandleService { + + /** Request that the Assistant Handles be shown. */ + oneway void requestAssistHandles(); +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java index 24ee969526cc..46d060e5ebda 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java @@ -199,7 +199,7 @@ class Bubble { mExpandedView = (BubbleExpandedView) inflater.inflate( R.layout.bubble_expanded_view, stackView, false /* attachToRoot */); - mExpandedView.setBubble(this, stackView, mAppName); + mExpandedView.setBubble(this, stackView); mInflated = true; } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java index 512b38e895bb..856b15e7135d 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java @@ -31,15 +31,12 @@ import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Color; import android.graphics.Insets; import android.graphics.Point; import android.graphics.Rect; -import android.graphics.drawable.Drawable; import android.graphics.drawable.ShapeDrawable; import android.os.RemoteException; import android.service.notification.StatusBarNotification; @@ -102,9 +99,7 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList private int mExpandedViewTouchSlop; private Bubble mBubble; - private PackageManager mPm; private String mAppName; - private Drawable mAppIcon; private BubbleController mBubbleController = Dependency.get(BubbleController.class); private WindowManager mWindowManager; @@ -212,7 +207,6 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList public BubbleExpandedView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); - mPm = context.getPackageManager(); mDisplaySize = new Point(); mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); // Get the real size -- this includes screen decorations (notches, statusbar, navbar). @@ -350,29 +344,14 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList /** * Sets the bubble used to populate this view. */ - public void setBubble(Bubble bubble, BubbleStackView stackView, String appName) { + public void setBubble(Bubble bubble, BubbleStackView stackView) { if (DEBUG_BUBBLE_EXPANDED_VIEW) { Log.d(TAG, "setBubble: bubble=" + (bubble != null ? bubble.getKey() : "null")); } - mStackView = stackView; mBubble = bubble; - mAppName = appName; - - try { - ApplicationInfo info = mPm.getApplicationInfo( - bubble.getPackageName(), - PackageManager.MATCH_UNINSTALLED_PACKAGES - | PackageManager.MATCH_DISABLED_COMPONENTS - | PackageManager.MATCH_DIRECT_BOOT_UNAWARE - | PackageManager.MATCH_DIRECT_BOOT_AWARE); - mAppIcon = mPm.getApplicationIcon(info); - } catch (PackageManager.NameNotFoundException e) { - // Do nothing. - } - if (mAppIcon == null) { - mAppIcon = mPm.getDefaultActivityIcon(); - } + mAppName = bubble.getAppName(); + applyThemeAttrs(); showSettingsIcon(); updateExpandedView(); diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index 9bd729edd210..442313d763ec 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -35,7 +35,9 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.NotifL import com.android.systemui.statusbar.notification.people.PeopleHubModule; import com.android.systemui.statusbar.phone.KeyguardLiftController; import com.android.systemui.statusbar.phone.StatusBar; +import com.android.systemui.statusbar.phone.StatusBarComponent; import com.android.systemui.statusbar.policy.HeadsUpManager; +import com.android.systemui.util.concurrency.ConcurrencyModule; import com.android.systemui.util.sensors.AsyncSensorManager; import com.android.systemui.util.time.SystemClock; import com.android.systemui.util.time.SystemClockImpl; @@ -52,7 +54,9 @@ import dagger.Provides; * implementation. */ @Module(includes = {AssistModule.class, - PeopleHubModule.class}) + ConcurrencyModule.class, + PeopleHubModule.class}, + subcomponents = {StatusBarComponent.class}) public abstract class SystemUIModule { @Binds diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIRootComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIRootComponent.java index e926574977d0..e50e0fe0d224 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIRootComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIRootComponent.java @@ -25,7 +25,6 @@ import com.android.systemui.Dependency; import com.android.systemui.SystemUIAppComponentFactory; import com.android.systemui.SystemUIFactory; import com.android.systemui.fragments.FragmentService; -import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.util.InjectionInflationController; @@ -73,12 +72,6 @@ public interface SystemUIRootComponent { Dependency.DependencyInjector createDependency(); /** - * Injects the StatusBar. - */ - @Singleton - StatusBar.StatusBarInjector getStatusBarInjector(); - - /** * FragmentCreator generates all Fragments that need injection. */ @Singleton diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/Background.java b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/Background.java new file mode 100644 index 000000000000..141c9019b3e4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/Background.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.dagger.qualifiers; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import javax.inject.Qualifier; + +@Qualifier +@Documented +@Retention(RUNTIME) +public @interface Background { +} diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/Main.java b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/Main.java new file mode 100644 index 000000000000..7b097740ff11 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/Main.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.dagger.qualifiers; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import javax.inject.Qualifier; + +@Qualifier +@Documented +@Retention(RUNTIME) +public @interface Main { +} 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 0a9100f6c7d5..f30c181b3c99 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java @@ -25,7 +25,6 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.graphics.drawable.Drawable; import android.os.Build; -import android.os.Handler; import android.provider.Settings; import android.service.quicksettings.Tile; import android.service.quicksettings.TileService; @@ -34,8 +33,8 @@ import android.util.ArraySet; import android.widget.Button; import com.android.systemui.R; -import com.android.systemui.dagger.qualifiers.BgHandler; -import com.android.systemui.dagger.qualifiers.MainHandler; +import com.android.systemui.dagger.qualifiers.Background; +import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.qs.QSTile.State; import com.android.systemui.qs.QSTileHost; @@ -47,6 +46,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; +import java.util.concurrent.Executor; import javax.inject.Inject; @@ -55,8 +55,8 @@ public class TileQueryHelper { private final ArrayList<TileInfo> mTiles = new ArrayList<>(); private final ArraySet<String> mSpecs = new ArraySet<>(); - private final Handler mBgHandler; - private final Handler mMainHandler; + private final Executor mMainExecutor; + private final Executor mBgExecutor; private final Context mContext; private TileStateListener mListener; @@ -64,10 +64,10 @@ public class TileQueryHelper { @Inject public TileQueryHelper(Context context, - @MainHandler Handler mainHandler, @BgHandler Handler bgHandler) { + @Main Executor mainExecutor, @Background Executor bgExecutor) { mContext = context; - mMainHandler = mainHandler; - mBgHandler = bgHandler; + mMainExecutor = mainExecutor; + mBgExecutor = bgExecutor; } public void setListener(TileStateListener listener) { @@ -126,7 +126,7 @@ public class TileQueryHelper { tilesToAdd.add(tile); } - mBgHandler.post(() -> { + mBgExecutor.execute(() -> { for (QSTile tile : tilesToAdd) { final QSTile.State state = tile.getState().copy(); // Ignore the current state and get the generic label instead. @@ -139,7 +139,7 @@ public class TileQueryHelper { } private void addPackageTiles(final QSTileHost host) { - mBgHandler.post(() -> { + mBgExecutor.execute(() -> { Collection<QSTile> params = host.getTiles(); PackageManager pm = mContext.getPackageManager(); List<ResolveInfo> services = pm.queryIntentServicesAsUser( @@ -185,7 +185,7 @@ public class TileQueryHelper { private void notifyTilesChanged(final boolean finished) { final ArrayList<TileInfo> tilesToReturn = new ArrayList<>(mTiles); - mMainHandler.post(() -> { + mMainExecutor.execute(() -> { if (mListener != null) { mListener.onTilesChanged(tilesToReturn); } diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java index fcdd23463c5b..f3e2f104621e 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java @@ -162,6 +162,8 @@ public class OverviewProxyRecentsImpl implements RecentsImplementation { if (runningTask.supportsSplitScreenMultiWindow) { if (ActivityManagerWrapper.getInstance().setTaskWindowingModeSplitScreenPrimary( runningTask.id, stackCreateMode, initialBounds)) { + mDividerOptional.ifPresent(Divider::onDockedTopTask); + // The overview service is handling split screen, so just skip the wait for the // first draw and notify the divider to start animating now mDividerOptional.ifPresent(Divider::onRecentsDrawn); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java index 341c49a87156..2005d794c9d3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java @@ -17,14 +17,13 @@ package com.android.systemui.statusbar; import android.annotation.NonNull; -import android.os.Handler; -import android.os.HandlerExecutor; import android.provider.DeviceConfig; import android.util.ArrayMap; -import com.android.systemui.dagger.qualifiers.BgHandler; +import com.android.systemui.dagger.qualifiers.Background; import java.util.Map; +import java.util.concurrent.Executor; import javax.inject.Inject; import javax.inject.Singleton; @@ -49,10 +48,10 @@ public class FeatureFlags { private final Map<String, Boolean> mCachedDeviceConfigFlags = new ArrayMap<>(); @Inject - public FeatureFlags(@BgHandler Handler bgHandler) { + public FeatureFlags(@Background Executor executor) { DeviceConfig.addOnPropertiesChangedListener( "systemui", - new HandlerExecutor(bgHandler), + executor, this::onPropertiesChanged); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java index 4cc5b2144adc..ff4ce9492082 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java @@ -65,8 +65,18 @@ public interface NotificationLockscreenUserManager { boolean needsRedaction(NotificationEntry entry); + /** + * Has the given user chosen to allow their private (full) notifications to be shown even + * when the lockscreen is in "public" (secure & locked) mode? + */ boolean userAllowsPrivateNotificationsInPublic(int currentUserId); + /** + * Has the given user chosen to allow notifications to be shown even when the lockscreen is in + * "public" (secure & locked) mode? + */ + boolean userAllowsNotificationsInPublic(int userId); + /** Notified when the current user changes. */ interface UserChangedListener { void onUserChanged(int userId); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java index f5710a84ddc8..0f3f6b7d9222 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java @@ -435,7 +435,7 @@ public class NotificationLockscreenUserManagerImpl implements * Has the given user chosen to allow notifications to be shown even when the lockscreen is in * "public" (secure & locked) mode? */ - private boolean userAllowsNotificationsInPublic(int userHandle) { + public boolean userAllowsNotificationsInPublic(int userHandle) { if (isCurrentProfile(userHandle) && userHandle != mCurrentUserId) { return true; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java index e5f44bd3b9f7..b61c1ef50cae 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java @@ -36,7 +36,10 @@ import com.android.systemui.statusbar.phone.StatusBar; import javax.inject.Inject; import javax.inject.Singleton; -/** Component which manages the various reasons a notification might be filtered out. */ +/** Component which manages the various reasons a notification might be filtered out.*/ +// TODO: delete NotificationFilter.java after migrating to new NotifPipeline b/145659174. +// Notification filtering is taken care of across the different Coordinators (mostly +// KeyguardCoordinator.java) @Singleton public class NotificationFilter { @@ -109,7 +112,7 @@ public class NotificationFilter { return true; } - if (entry.isSuspended()) { + if (entry.getRanking().isSuspended()) { return true; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationListController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationListController.java index 3f7fd1a4868c..7b1dc074fcbc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationListController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationListController.java @@ -64,6 +64,8 @@ public class NotificationListController { } }; + // TODO: (b/145659174) remove after moving to NewNotifPipeline. Replaced by + // DeviceProvisionedCoordinator private final DeviceProvisionedListener mDeviceProvisionedListener = new DeviceProvisionedListener() { @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java index f9f3266f1afa..9ae3882e8e82 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java @@ -18,6 +18,8 @@ package com.android.systemui.statusbar.notification.collection; import android.annotation.Nullable; +import com.android.internal.annotations.VisibleForTesting; + import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -34,7 +36,8 @@ public class GroupEntry extends ListEntry { private final List<NotificationEntry> mUnmodifiableChildren = Collections.unmodifiableList(mChildren); - GroupEntry(String key) { + @VisibleForTesting + public GroupEntry(String key) { super(key); } @@ -52,7 +55,8 @@ public class GroupEntry extends ListEntry { return mUnmodifiableChildren; } - void setSummary(@Nullable NotificationEntry summary) { + @VisibleForTesting + public void setSummary(@Nullable NotificationEntry summary) { mSummary = summary; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java index dc68c4bdba78..6ce7fd96e6a0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java @@ -18,6 +18,8 @@ package com.android.systemui.statusbar.notification.collection; import android.annotation.Nullable; +import com.android.internal.annotations.VisibleForTesting; + /** * Abstract superclass for top-level entries, i.e. things that can appear in the final notification * list shown to users. In practice, this means either GroupEntries or NotificationEntries. @@ -49,7 +51,8 @@ public abstract class ListEntry { return mParent; } - void setParent(@Nullable GroupEntry parent) { + @VisibleForTesting + public void setParent(@Nullable GroupEntry parent) { mParent = parent; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java index 3eb55ef6bc93..232fb6d3cf67 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java @@ -275,10 +275,6 @@ public final class NotificationEntry extends ListEntry { return mRanking.getSuppressedVisualEffects(); } - public boolean isSuspended() { - return mRanking.isSuspended(); - } - /** @see Ranking#canBubble() */ public boolean canBubble() { return mRanking.canBubble(); @@ -951,6 +947,15 @@ public final class NotificationEntry extends ListEntry { } /** + * Whether or not this row represents a system notification. Note that if this is + * {@code null}, that means we were either unable to retrieve the info or have yet to + * retrieve the info. + */ + public Boolean isSystemNotification() { + return mIsSystemNotification; + } + + /** * Set this notification to be sensitive. * * @param sensitive true if the content of this notification is sensitive right now diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt index 8bce528bab8c..48a4882bcf82 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt @@ -16,37 +16,29 @@ package com.android.systemui.statusbar.notification.collection -import android.app.Notification import android.app.NotificationManager.IMPORTANCE_DEFAULT import android.app.NotificationManager.IMPORTANCE_HIGH import android.app.NotificationManager.IMPORTANCE_LOW import android.app.NotificationManager.IMPORTANCE_MIN -import android.app.Person import android.service.notification.NotificationListenerService.Ranking import android.service.notification.NotificationListenerService.RankingMap import android.service.notification.StatusBarNotification import com.android.internal.annotations.VisibleForTesting - import com.android.systemui.statusbar.NotificationMediaManager import com.android.systemui.statusbar.notification.NotificationFilter import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager import com.android.systemui.statusbar.notification.logging.NotifEvent import com.android.systemui.statusbar.notification.logging.NotifLog +import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_PEOPLE import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT import com.android.systemui.statusbar.phone.NotificationGroupManager import com.android.systemui.statusbar.policy.HeadsUpManager - +import dagger.Lazy import java.util.Objects -import java.util.ArrayList - import javax.inject.Inject -import kotlin.Comparator - -import dagger.Lazy - private const val TAG = "NotifRankingManager" /** @@ -64,7 +56,8 @@ open class NotificationRankingManager @Inject constructor( private val headsUpManager: HeadsUpManager, private val notifFilter: NotificationFilter, private val notifLog: NotifLog, - sectionsFeatureManager: NotificationSectionsFeatureManager + sectionsFeatureManager: NotificationSectionsFeatureManager, + private val peopleNotificationIdentifier: PeopleNotificationIdentifier ) { var rankingMap: RankingMap? = null @@ -79,6 +72,9 @@ open class NotificationRankingManager @Inject constructor( val aRank = a.ranking.rank val bRank = b.ranking.rank + val aIsPeople = a.isPeopleNotification() + val bIsPeople = b.isPeopleNotification() + val aMedia = isImportantMedia(a) val bMedia = isImportantMedia(b) @@ -88,25 +84,19 @@ open class NotificationRankingManager @Inject constructor( val aHeadsUp = a.isRowHeadsUp val bHeadsUp = b.isRowHeadsUp - if (usePeopleFiltering && a.isPeopleNotification() != b.isPeopleNotification()) { - if (a.isPeopleNotification()) -1 else 1 - } else if (aHeadsUp != bHeadsUp) { - if (aHeadsUp) -1 else 1 - } else if (aHeadsUp) { + when { + usePeopleFiltering && aIsPeople != bIsPeople -> if (aIsPeople) -1 else 1 + aHeadsUp != bHeadsUp -> if (aHeadsUp) -1 else 1 // Provide consistent ranking with headsUpManager - headsUpManager.compare(a, b) - } else if (aMedia != bMedia) { + aHeadsUp -> headsUpManager.compare(a, b) // Upsort current media notification. - if (aMedia) -1 else 1 - } else if (aSystemMax != bSystemMax) { + aMedia != bMedia -> if (aMedia) -1 else 1 // Upsort PRIORITY_MAX system notifications - if (aSystemMax) -1 else 1 - } else if (a.isHighPriority != b.isHighPriority) { - -1 * java.lang.Boolean.compare(a.isHighPriority, b.isHighPriority) - } else if (aRank != bRank) { - aRank - bRank - } else { - nb.notification.`when`.compareTo(na.notification.`when`) + aSystemMax != bSystemMax -> if (aSystemMax) -1 else 1 + a.isHighPriority != b.isHighPriority -> + -1 * a.isHighPriority.compareTo(b.isHighPriority) + aRank != bRank -> aRank - bRank + else -> nb.notification.`when`.compareTo(na.notification.`when`) } } @@ -138,10 +128,9 @@ open class NotificationRankingManager @Inject constructor( val c = entry.channel val n = entry.sbn.notification - if (((n.isForegroundService && entry.ranking.importance >= IMPORTANCE_LOW) || - n.hasMediaSession() || - n.hasPerson() || - n.hasStyle(Notification.MessagingStyle::class.java))) { + if ((n.isForegroundService && entry.ranking.importance >= IMPORTANCE_LOW) || + n.hasMediaSession() || + entry.isPeopleNotification()) { // Users who have long pressed and demoted to silent should not see the notification // in the top section if (c != null && c.hasUserSetImportance()) { @@ -204,7 +193,7 @@ open class NotificationRankingManager @Inject constructor( isMedia: Boolean, isSystemMax: Boolean ) { - if (usePeopleFiltering && entry.hasAssociatedPeople()) { + if (usePeopleFiltering && entry.isPeopleNotification()) { entry.bucket = BUCKET_PEOPLE } else if (isHeadsUp || isMedia || isSystemMax || entry.isHighPriority) { entry.bucket = BUCKET_ALERTING @@ -235,6 +224,11 @@ open class NotificationRankingManager @Inject constructor( } } } + + private fun NotificationEntry.isPeopleNotification() = + sbn.isPeopleNotification() + private fun StatusBarNotification.isPeopleNotification() = + peopleNotificationIdentifier.isPeopleNotification(this) } // Convenience functions @@ -245,16 +239,3 @@ private fun NotificationEntry.isSystemMax(): Boolean { private fun StatusBarNotification.isSystemNotification(): Boolean { return "android" == packageName || "com.android.systemui" == packageName } - -private fun Notification.hasPerson(): Boolean { - val people: ArrayList<Person> = - (extras?.getParcelableArrayList(Notification.EXTRA_PEOPLE_LIST)) ?: ArrayList() - return people.isNotEmpty() -} - -private fun Notification.hasStyle(targetStyleClass: Class<*>): Boolean { - return targetStyleClass == notificationStyle -} - -private fun NotificationEntry.isPeopleNotification(): Boolean = - sbn.notification.hasStyle(Notification.MessagingStyle::class.java) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/Coordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/Coordinator.java new file mode 100644 index 000000000000..898918eb076d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/Coordinator.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.collection.coordinator; + +import com.android.systemui.statusbar.notification.collection.NotifCollection; +import com.android.systemui.statusbar.notification.collection.NotifCollectionListener; +import com.android.systemui.statusbar.notification.collection.NotifLifetimeExtender; +import com.android.systemui.statusbar.notification.collection.init.NewNotifPipeline; +import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder; +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable; + +/** + * Interface for registering callbacks to the {@link NewNotifPipeline}. + * + * This includes registering: + * {@link Pluggable}s to the {@link NotifListBuilder} + * {@link NotifCollectionListener}s and {@link NotifLifetimeExtender}s to {@link NotifCollection} + */ +public interface Coordinator { + + /** + * Called after the NewNotifPipeline is initialized. + * Coordinators should register their {@link Pluggable}s to the notifListBuilder + * and their {@link NotifCollectionListener}s and {@link NotifLifetimeExtender}s + * to the notifCollection in this method. + */ + void attach(NotifCollection notifCollection, NotifListBuilder notifListBuilder); +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java new file mode 100644 index 000000000000..511aafc2d12a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.collection.coordinator; + +import android.Manifest; +import android.app.AppGlobals; +import android.app.Notification; +import android.content.pm.IPackageManager; +import android.content.pm.PackageManager; +import android.os.RemoteException; +import android.service.notification.StatusBarNotification; + +import com.android.systemui.statusbar.notification.collection.NotifCollection; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder; +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; +import com.android.systemui.statusbar.policy.DeviceProvisionedController; + +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * Filters out most notifications when the device is unprovisioned. + * Special notifications with extra permissions and tags won't be filtered out even when the + * device is unprovisioned. + */ +@Singleton +public class DeviceProvisionedCoordinator implements Coordinator { + private static final String TAG = "DeviceProvisionedCoordinator"; + + private final DeviceProvisionedController mDeviceProvisionedController; + + @Inject + public DeviceProvisionedCoordinator(DeviceProvisionedController deviceProvisionedController) { + mDeviceProvisionedController = deviceProvisionedController; + } + + @Override + public void attach(NotifCollection notifCollection, NotifListBuilder notifListBuilder) { + mDeviceProvisionedController.addCallback(mDeviceProvisionedListener); + + notifListBuilder.addFilter(mNotifFilter); + } + + protected final NotifFilter mNotifFilter = new NotifFilter(TAG) { + @Override + public boolean shouldFilterOut(NotificationEntry entry, long now) { + return !mDeviceProvisionedController.isDeviceProvisioned() + && !showNotificationEvenIfUnprovisioned(entry.getSbn()); + } + }; + + /** + * Only notifications coming from packages with permission + * android.permission.NOTIFICATION_DURING_SETUP that also have special tags + * marking them as relevant for setup are allowed to show when device is unprovisioned + */ + private boolean showNotificationEvenIfUnprovisioned(StatusBarNotification sbn) { + final boolean hasPermission = checkUidPermission(AppGlobals.getPackageManager(), + Manifest.permission.NOTIFICATION_DURING_SETUP, + sbn.getUid()) == PackageManager.PERMISSION_GRANTED; + return hasPermission + && sbn.getNotification().extras.getBoolean(Notification.EXTRA_ALLOW_DURING_SETUP); + } + + private static int checkUidPermission(IPackageManager packageManager, String permission, + int uid) { + try { + return packageManager.checkUidPermission(permission, uid); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + private final DeviceProvisionedController.DeviceProvisionedListener mDeviceProvisionedListener = + new DeviceProvisionedController.DeviceProvisionedListener() { + @Override + public void onDeviceProvisionedChanged() { + mNotifFilter.invalidateList(); + } + }; +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinator.java new file mode 100644 index 000000000000..4803cf478327 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinator.java @@ -0,0 +1,243 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.collection.coordinator; + +import android.app.Notification; +import android.os.Handler; +import android.os.UserHandle; +import android.service.notification.StatusBarNotification; +import android.util.ArraySet; + +import com.android.systemui.ForegroundServiceController; +import com.android.systemui.appops.AppOpsController; +import com.android.systemui.dagger.qualifiers.BgHandler; +import com.android.systemui.dagger.qualifiers.MainHandler; +import com.android.systemui.statusbar.notification.collection.NotifCollection; +import com.android.systemui.statusbar.notification.collection.NotifCollectionListener; +import com.android.systemui.statusbar.notification.collection.NotifLifetimeExtender; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder; +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; + +import java.util.HashMap; +import java.util.Map; + +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * Handles ForegroundService interactions with notifications. + * Tags notifications with appOps. + * Lifetime extends notifications associated with an ongoing ForegroundService. + * Filters out notifications that represent foreground services that are no longer running + * + * Previously this logic lived in + * frameworks/base/packages/SystemUI/src/com/android/systemui/ForegroundServiceController + * frameworks/base/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener + * frameworks/base/packages/SystemUI/src/com/android/systemui/ForegroundServiceLifetimeExtender + */ +@Singleton +public class ForegroundCoordinator implements Coordinator { + private static final String TAG = "ForegroundNotificationCoordinator"; + + private final ForegroundServiceController mForegroundServiceController; + private final AppOpsController mAppOpsController; + private final Handler mMainHandler; + private final Handler mBgHandler; + + private NotifCollection mNotifCollection; + + @Inject + public ForegroundCoordinator( + ForegroundServiceController foregroundServiceController, + AppOpsController appOpsController, + @MainHandler Handler mainHandler, + @BgHandler Handler bgHandler) { + mForegroundServiceController = foregroundServiceController; + mAppOpsController = appOpsController; + mMainHandler = mainHandler; + mBgHandler = bgHandler; + } + + @Override + public void attach(NotifCollection notifCollection, NotifListBuilder notifListBuilder) { + mNotifCollection = notifCollection; + + // extend the lifetime of foreground notification services to show for at least 5 seconds + mNotifCollection.addNotificationLifetimeExtender(mForegroundLifetimeExtender); + + // listen for new notifications to add appOps + mNotifCollection.addCollectionListener(mNotifCollectionListener); + + // when appOps change, update any relevant notifications to update appOps for + mAppOpsController.addCallback(ForegroundServiceController.APP_OPS, this::onAppOpsChanged); + + // filter out foreground service notifications that aren't necessary anymore + notifListBuilder.addFilter(mNotifFilter); + } + + /** + * Filters out notifications that represent foreground services that are no longer running. + */ + protected final NotifFilter mNotifFilter = new NotifFilter(TAG) { + @Override + public boolean shouldFilterOut(NotificationEntry entry, long now) { + StatusBarNotification sbn = entry.getSbn(); + if (mForegroundServiceController.isDisclosureNotification(sbn) + && !mForegroundServiceController.isDisclosureNeededForUser(sbn.getUserId())) { + return true; + } + + if (mForegroundServiceController.isSystemAlertNotification(sbn)) { + final String[] apps = sbn.getNotification().extras.getStringArray( + Notification.EXTRA_FOREGROUND_APPS); + if (apps != null && apps.length >= 1) { + if (!mForegroundServiceController.isSystemAlertWarningNeeded( + sbn.getUserId(), apps[0])) { + return true; + } + } + } + return false; + } + }; + + /** + * Extends the lifetime of foreground notification services such that they show for at least + * five seconds + */ + private final NotifLifetimeExtender mForegroundLifetimeExtender = new NotifLifetimeExtender() { + private static final int MIN_FGS_TIME_MS = 5000; + private OnEndLifetimeExtensionCallback mEndCallback; + private Map<String, Runnable> mEndRunnables = new HashMap<>(); + + @Override + public String getName() { + return TAG; + } + + @Override + public void setCallback(OnEndLifetimeExtensionCallback callback) { + mEndCallback = callback; + } + + @Override + public boolean shouldExtendLifetime(NotificationEntry entry, int reason) { + if ((entry.getSbn().getNotification().flags + & Notification.FLAG_FOREGROUND_SERVICE) == 0) { + return false; + } + + final long currTime = System.currentTimeMillis(); + final boolean extendLife = currTime - entry.getSbn().getPostTime() < MIN_FGS_TIME_MS; + + if (extendLife) { + if (!mEndRunnables.containsKey(entry.getKey())) { + final Runnable runnable = new Runnable() { + @Override + public void run() { + mEndCallback.onEndLifetimeExtension(mForegroundLifetimeExtender, entry); + } + }; + mEndRunnables.put(entry.getKey(), runnable); + mBgHandler.postDelayed(runnable, MIN_FGS_TIME_MS + - (currTime - entry.getSbn().getPostTime())); + } + } + + return extendLife; + } + + @Override + public void cancelLifetimeExtension(NotificationEntry entry) { + if (mEndRunnables.containsKey(entry.getKey())) { + Runnable endRunnable = mEndRunnables.remove(entry.getKey()); + mBgHandler.removeCallbacks(endRunnable); + } + } + }; + + /** + * Adds appOps to incoming and updating notifications + */ + private NotifCollectionListener mNotifCollectionListener = new NotifCollectionListener() { + @Override + public void onEntryAdded(NotificationEntry entry) { + tagForeground(entry); + } + + @Override + public void onEntryUpdated(NotificationEntry entry) { + tagForeground(entry); + } + + private void tagForeground(NotificationEntry entry) { + final StatusBarNotification sbn = entry.getSbn(); + // note: requires that the ForegroundServiceController is updating their appOps first + ArraySet<Integer> activeOps = mForegroundServiceController.getAppOps(sbn.getUserId(), + sbn.getPackageName()); + if (activeOps != null) { + synchronized (entry.mActiveAppOps) { + entry.mActiveAppOps.clear(); + entry.mActiveAppOps.addAll(activeOps); + } + } + } + }; + + /** + * Update the appOp for the posted notification associated with the current foreground service + * @param code code for appOp to add/remove + * @param uid of user the notification is sent to + * @param packageName package that created the notification + * @param active whether the appOpCode is active or not + */ + private void onAppOpsChanged(int code, int uid, String packageName, boolean active) { + int userId = UserHandle.getUserId(uid); + + // Update appOp if there's an associated posted notification: + final String foregroundKey = mForegroundServiceController.getStandardLayoutKey(userId, + packageName); + if (foregroundKey != null) { + final NotificationEntry entry = findNotificationEntryWithKey(foregroundKey); + if (entry != null + && uid == entry.getSbn().getUid() + && packageName.equals(entry.getSbn().getPackageName())) { + boolean changed; + synchronized (entry.mActiveAppOps) { + if (active) { + changed = entry.mActiveAppOps.add(code); + } else { + changed = entry.mActiveAppOps.remove(code); + } + } + if (changed) { + mMainHandler.post(mNotifFilter::invalidateList); + } + } + } + } + + private NotificationEntry findNotificationEntryWithKey(String key) { + for (NotificationEntry entry : mNotifCollection.getNotifs()) { + if (entry.getKey().equals(key)) { + return entry; + } + } + return null; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java new file mode 100644 index 000000000000..6daf3fc50b30 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java @@ -0,0 +1,244 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.collection.coordinator; + +import static android.app.Notification.VISIBILITY_SECRET; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Handler; +import android.os.UserHandle; +import android.provider.Settings; +import android.service.notification.StatusBarNotification; + +import androidx.annotation.MainThread; + +import com.android.keyguard.KeyguardUpdateMonitor; +import com.android.keyguard.KeyguardUpdateMonitorCallback; +import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.statusbar.NotificationLockscreenUserManager; +import com.android.systemui.statusbar.notification.NotificationUtils; +import com.android.systemui.statusbar.notification.collection.NotifCollection; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder; +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; +import com.android.systemui.statusbar.policy.KeyguardStateController; + +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * Filters low priority and privacy-sensitive notifications from the lockscreen. + */ +@Singleton +public class KeyguardCoordinator implements Coordinator { + private static final String TAG = "KeyguardNotificationCoordinator"; + + private final Context mContext; + private final Handler mMainHandler; + private final KeyguardStateController mKeyguardStateController; + private final NotificationLockscreenUserManager mLockscreenUserManager; + private final BroadcastDispatcher mBroadcastDispatcher; + private final StatusBarStateController mStatusBarStateController; + private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; + + @Inject + public KeyguardCoordinator( + Context context, + @MainThread Handler mainThreadHandler, + KeyguardStateController keyguardStateController, + NotificationLockscreenUserManager lockscreenUserManager, + BroadcastDispatcher broadcastDispatcher, + StatusBarStateController statusBarStateController, + KeyguardUpdateMonitor keyguardUpdateMonitor) { + mContext = context; + mMainHandler = mainThreadHandler; + mKeyguardStateController = keyguardStateController; + mLockscreenUserManager = lockscreenUserManager; + + mBroadcastDispatcher = broadcastDispatcher; + mStatusBarStateController = statusBarStateController; + mKeyguardUpdateMonitor = keyguardUpdateMonitor; + } + + @Override + public void attach(NotifCollection notifCollection, NotifListBuilder notifListBuilder) { + setupInvalidateNotifListCallbacks(); + notifListBuilder.addFilter(mNotifFilter); + } + + protected final NotifFilter mNotifFilter = new NotifFilter(TAG) { + @Override + public boolean shouldFilterOut(NotificationEntry entry, long now) { + final StatusBarNotification sbn = entry.getSbn(); + + // FILTER OUT the notification when the notification isn't for the current profile + if (!mLockscreenUserManager.isCurrentProfile(sbn.getUserId())) { + return true; + } + + // FILTER OUT the notification when the keyguard is showing and... + if (mKeyguardStateController.isShowing()) { + // ... user settings or the device policy manager doesn't allow lockscreen + // notifications; + if (!mLockscreenUserManager.shouldShowLockscreenNotifications()) { + return true; + } + + final int currUserId = mLockscreenUserManager.getCurrentUserId(); + final int notifUserId = (sbn.getUser().getIdentifier() == UserHandle.USER_ALL) + ? currUserId : sbn.getUser().getIdentifier(); + + // ... user is in lockdown + if (mKeyguardUpdateMonitor.isUserInLockdown(currUserId) + || mKeyguardUpdateMonitor.isUserInLockdown(notifUserId)) { + return true; + } + + // ... device is in public mode and the user's settings doesn't allow + // notifications to show in public mode + if (mLockscreenUserManager.isLockscreenPublicMode(currUserId) + || mLockscreenUserManager.isLockscreenPublicMode(notifUserId)) { + if (entry.getRanking().getVisibilityOverride() == VISIBILITY_SECRET) { + return true; + } + + if (!mLockscreenUserManager.userAllowsNotificationsInPublic(currUserId) + || !mLockscreenUserManager.userAllowsNotificationsInPublic( + notifUserId)) { + return true; + } + } + + // ... neither this notification nor its summary have high enough priority + // to be shown on the lockscreen + // TODO: grouping hasn't happened yet (b/145134683) + if (entry.getParent() != null) { + final NotificationEntry summary = entry.getParent().getRepresentativeEntry(); + if (priorityExceedsLockscreenShowingThreshold(summary)) { + return false; + } + } + return !priorityExceedsLockscreenShowingThreshold(entry); + } + return false; + } + }; + + private boolean priorityExceedsLockscreenShowingThreshold(NotificationEntry entry) { + if (entry == null) { + return false; + } + if (NotificationUtils.useNewInterruptionModel(mContext) + && hideSilentNotificationsOnLockscreen()) { + // TODO: make sure in the NewNotifPipeline that entry.isHighPriority() has been + // correctly updated before reaching this point (b/145134683) + return entry.isHighPriority(); + } else { + return !entry.getRanking().isAmbient(); + } + } + + private boolean hideSilentNotificationsOnLockscreen() { + return Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, 1) == 0; + } + + private void setupInvalidateNotifListCallbacks() { + // register onKeyguardShowing callback + mKeyguardStateController.addCallback(mKeyguardCallback); + mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateCallback); + + // register lockscreen settings changed callbacks: + final ContentObserver settingsObserver = new ContentObserver(mMainHandler) { + @Override + public void onChange(boolean selfChange, Uri uri) { + if (mKeyguardStateController.isShowing()) { + invalidateListFromFilter("Settings " + uri + " changed"); + } + } + }; + + mContext.getContentResolver().registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS), + false, + settingsObserver, + UserHandle.USER_ALL); + + mContext.getContentResolver().registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS), + true, + settingsObserver, + UserHandle.USER_ALL); + + mContext.getContentResolver().registerContentObserver( + Settings.Global.getUriFor(Settings.Global.ZEN_MODE), + false, + settingsObserver); + + // register (maybe) public mode changed callbacks: + mStatusBarStateController.addCallback(mStatusBarStateListener); + mBroadcastDispatcher.registerReceiver(new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (mKeyguardStateController.isShowing()) { + // maybe public mode changed + invalidateListFromFilter(intent.getAction()); + } + }}, new IntentFilter(Intent.ACTION_USER_SWITCHED)); + } + + private void invalidateListFromFilter(String reason) { + mNotifFilter.invalidateList(); + } + + private final KeyguardStateController.Callback mKeyguardCallback = + new KeyguardStateController.Callback() { + @Override + public void onUnlockedChanged() { + invalidateListFromFilter("onUnlockedChanged"); + } + + @Override + public void onKeyguardShowingChanged() { + invalidateListFromFilter("onKeyguardShowingChanged"); + } + }; + + private final StatusBarStateController.StateListener mStatusBarStateListener = + new StatusBarStateController.StateListener() { + @Override + public void onStateChanged(int newState) { + // maybe public mode changed + invalidateListFromFilter("onStatusBarStateChanged"); + } + }; + + private final KeyguardUpdateMonitorCallback mKeyguardUpdateCallback = + new KeyguardUpdateMonitorCallback() { + @Override + public void onStrongAuthStateChanged(int userId) { + // maybe lockdown mode changed + invalidateListFromFilter("onStrongAuthStateChanged"); + } + }; +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java new file mode 100644 index 000000000000..132471933bb2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.collection.coordinator; + +import com.android.systemui.Dumpable; +import com.android.systemui.statusbar.notification.collection.NotifCollection; +import com.android.systemui.statusbar.notification.collection.NotifCollectionListener; +import com.android.systemui.statusbar.notification.collection.NotifLifetimeExtender; +import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder; +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * Handles the attachment of the {@link NotifListBuilder} and {@link NotifCollection} to the + * {@link Coordinator}s, so that the Coordinators can register their respective callbacks. + */ +@Singleton +public class NotifCoordinators implements Dumpable { + private static final String TAG = "NotifCoordinators"; + private final List<Coordinator> mCoordinators = new ArrayList<>(); + + /** + * Creates all the coordinators. + */ + @Inject + public NotifCoordinators( + KeyguardCoordinator keyguardCoordinator, + RankingCoordinator rankingCoordinator, + ForegroundCoordinator foregroundCoordinator, + DeviceProvisionedCoordinator deviceProvisionedCoordinator) { + mCoordinators.add(keyguardCoordinator); + mCoordinators.add(rankingCoordinator); + mCoordinators.add(foregroundCoordinator); + mCoordinators.add(deviceProvisionedCoordinator); + // TODO: add new Coordinators here! (b/145134683, b/112656837) + } + + /** + * Sends the initialized notifListBuilder and notifCollection to each + * coordinator to indicate the notifListBuilder is ready to accept {@link Pluggable}s + * and the notifCollection is ready to accept {@link NotifCollectionListener}s and + * {@link NotifLifetimeExtender}s. + */ + public void attach(NotifCollection notifCollection, NotifListBuilder notifListBuilder) { + for (Coordinator c : mCoordinators) { + c.attach(notifCollection, notifListBuilder); + } + } + + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println(TAG + ":"); + for (Coordinator c : mCoordinators) { + pw.println("\t" + c.getClass()); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java new file mode 100644 index 000000000000..c390f96f004d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.collection.coordinator; + +import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.statusbar.notification.collection.NotifCollection; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder; +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; + +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * Filters out NotificationEntries based on its Ranking. + */ +@Singleton +public class RankingCoordinator implements Coordinator { + private static final String TAG = "RankingNotificationCoordinator"; + + private final StatusBarStateController mStatusBarStateController; + + @Inject + public RankingCoordinator(StatusBarStateController statusBarStateController) { + mStatusBarStateController = statusBarStateController; + } + + @Override + public void attach(NotifCollection notifCollection, NotifListBuilder notifListBuilder) { + mStatusBarStateController.addCallback(mStatusBarStateCallback); + + notifListBuilder.addFilter(mNotifFilter); + } + + /** + * Checks whether to filter out the given notification based the notification's Ranking object. + * NotifListBuilder invalidates the notification list each time the ranking is updated, + * so we don't need to explicitly invalidate this filter on ranking update. + */ + protected final NotifFilter mNotifFilter = new NotifFilter(TAG) { + @Override + public boolean shouldFilterOut(NotificationEntry entry, long now) { + // App suspended from Ranking + if (entry.getRanking().isSuspended()) { + return true; + } + + // Dozing + DND Settings from Ranking object + if (mStatusBarStateController.isDozing() && entry.shouldSuppressAmbient()) { + return true; + } + + if (!mStatusBarStateController.isDozing() && entry.shouldSuppressNotificationList()) { + return true; + } + + return false; + } + }; + + + private final StatusBarStateController.StateListener mStatusBarStateCallback = + new StatusBarStateController.StateListener() { + @Override + public void onDozingChanged(boolean isDozing) { + mNotifFilter.invalidateList(); + } + }; +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NewNotifPipeline.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NewNotifPipeline.java index 3b3e7e2a2933..5fc55daa6b6b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NewNotifPipeline.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NewNotifPipeline.java @@ -23,6 +23,7 @@ import com.android.systemui.Dumpable; import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.notification.collection.NotifCollection; import com.android.systemui.statusbar.notification.collection.NotifListBuilderImpl; +import com.android.systemui.statusbar.notification.collection.coordinator.NotifCoordinators; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -37,6 +38,7 @@ import javax.inject.Singleton; public class NewNotifPipeline implements Dumpable { private final NotifCollection mNotifCollection; private final NotifListBuilderImpl mNotifPipeline; + private final NotifCoordinators mNotifPluggableCoordinators; private final DumpController mDumpController; private final FakePipelineConsumer mFakePipelineConsumer = new FakePipelineConsumer(); @@ -45,9 +47,11 @@ public class NewNotifPipeline implements Dumpable { public NewNotifPipeline( NotifCollection notifCollection, NotifListBuilderImpl notifPipeline, + NotifCoordinators notifCoordinators, DumpController dumpController) { mNotifCollection = notifCollection; mNotifPipeline = notifPipeline; + mNotifPluggableCoordinators = notifCoordinators; mDumpController = dumpController; } @@ -57,6 +61,7 @@ public class NewNotifPipeline implements Dumpable { mFakePipelineConsumer.attach(mNotifPipeline); mNotifPipeline.attach(mNotifCollection); mNotifCollection.attach(notificationService); + mNotifPluggableCoordinators.attach(mNotifCollection, mNotifPipeline); Log.d(TAG, "Notif pipeline initialized"); @@ -66,6 +71,7 @@ public class NewNotifPipeline implements Dumpable { @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { mFakePipelineConsumer.dump(fd, pw, args); + mNotifPluggableCoordinators.dump(fd, pw, args); } private static final String TAG = "NewNotifPipeline"; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java index fdd51e9e7790..8e9a051e7d43 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java @@ -318,7 +318,7 @@ public class NotificationSectionsManager implements StackScrollAlgorithm.Section } mParent.addView(mPeopleHubView, targetIndex); return true; - } else if (currentHubIndex != targetIndex - 1) { + } else if (currentHubIndex != targetIndex) { if (currentHubIndex < targetIndex) { targetIndex--; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java index 063ad855343f..09ebb6468a11 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java @@ -88,10 +88,9 @@ public final class DozeServiceHost implements DozeHost { private final PulseExpansionHandler mPulseExpansionHandler; private final StatusBarWindowController mStatusBarWindowController; private final NotificationWakeUpCoordinator mNotificationWakeUpCoordinator; - private final StatusBarWindowViewController mStatusBarWindowViewController; + private StatusBarWindowViewController mStatusBarWindowViewController; private final LockscreenLockIconController mLockscreenLockIconController; private NotificationIconAreaController mNotificationIconAreaController; - private StatusBarWindowView mStatusBarWindow; private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; private NotificationPanelView mNotificationPanel; private View mAmbientIndicationContainer; @@ -112,7 +111,6 @@ public final class DozeServiceHost implements DozeHost { PulseExpansionHandler pulseExpansionHandler, StatusBarWindowController statusBarWindowController, NotificationWakeUpCoordinator notificationWakeUpCoordinator, - StatusBarWindowViewController statusBarWindowViewController, LockscreenLockIconController lockscreenLockIconController) { super(); mDozeLog = dozeLog; @@ -132,7 +130,6 @@ public final class DozeServiceHost implements DozeHost { mPulseExpansionHandler = pulseExpansionHandler; mStatusBarWindowController = statusBarWindowController; mNotificationWakeUpCoordinator = notificationWakeUpCoordinator; - mStatusBarWindowViewController = statusBarWindowViewController; mLockscreenLockIconController = lockscreenLockIconController; } @@ -143,14 +140,14 @@ public final class DozeServiceHost implements DozeHost { */ public void initialize(StatusBar statusBar, NotificationIconAreaController notificationIconAreaController, - StatusBarWindowView statusBarWindow, StatusBarKeyguardViewManager statusBarKeyguardViewManager, + StatusBarWindowViewController statusBarWindowViewController, NotificationPanelView notificationPanel, View ambientIndicationContainer) { mStatusBar = statusBar; mNotificationIconAreaController = notificationIconAreaController; - mStatusBarWindow = statusBarWindow; mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; mNotificationPanel = notificationPanel; + mStatusBarWindowViewController = statusBarWindowViewController; mAmbientIndicationContainer = ambientIndicationContainer; mBiometricUnlockController = mBiometricUnlockControllerLazy.get(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java index 4d6b54ccfaff..f3e9b6b3d155 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java @@ -134,6 +134,7 @@ public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implemen if (fd != null) { try { BitmapFactory.Options options = new BitmapFactory.Options(); + options.inPreferredConfig = Bitmap.Config.HARDWARE; return LoaderResult.success(BitmapFactory.decodeFileDescriptor( fd.getFileDescriptor(), null, options)); } catch (OutOfMemoryError e) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 3bfe29791edb..5c71a57cba77 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -237,9 +237,9 @@ import java.util.Map; import java.util.Optional; import javax.inject.Named; +import javax.inject.Provider; import dagger.Lazy; -import dagger.Subcomponent; public class StatusBar extends SystemUI implements DemoMode, ActivityStarter, KeyguardStateController.Callback, @@ -377,9 +377,10 @@ public class StatusBar extends SystemUI implements DemoMode, private final FalsingManager mFalsingManager; private final BroadcastDispatcher mBroadcastDispatcher; private final ConfigurationController mConfigurationController; - private final StatusBarWindowViewController mStatusBarWindowViewController; + protected StatusBarWindowViewController mStatusBarWindowViewController; private final DozeParameters mDozeParameters; private final Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy; + private final Provider<StatusBarComponent.Builder> mStatusBarComponentBuilder; private final PluginManager mPluginManager; private final RemoteInputUriController mRemoteInputUriController; private final Optional<Divider> mDividerOptional; @@ -583,6 +584,8 @@ public class StatusBar extends SystemUI implements DemoMode, } } + // TODO: (b/145659174) remove when moving to NewNotifPipeline. Replaced by + // KeyguardCoordinator @Override public void onStrongAuthStateChanged(int userId) { super.onStrongAuthStateChanged(userId); @@ -659,7 +662,6 @@ public class StatusBar extends SystemUI implements DemoMode, NotificationListener notificationListener, ConfigurationController configurationController, StatusBarWindowController statusBarWindowController, - StatusBarWindowViewController statusBarWindowViewController, LockscreenLockIconController lockscreenLockIconController, DozeParameters dozeParameters, ScrimController scrimController, @@ -673,6 +675,7 @@ public class StatusBar extends SystemUI implements DemoMode, VolumeComponent volumeComponent, CommandQueue commandQueue, Optional<Recents> recentsOptional, + Provider<StatusBarComponent.Builder> statusBarComponentBuilder, PluginManager pluginManager, RemoteInputUriController remoteInputUriController, Optional<Divider> dividerOptional, @@ -732,7 +735,6 @@ public class StatusBar extends SystemUI implements DemoMode, mNotificationListener = notificationListener; mConfigurationController = configurationController; mStatusBarWindowController = statusBarWindowController; - mStatusBarWindowViewController = statusBarWindowViewController; mLockscreenLockIconController = lockscreenLockIconController; mDozeServiceHost = dozeServiceHost; mPowerManager = powerManager; @@ -746,6 +748,7 @@ public class StatusBar extends SystemUI implements DemoMode, mVolumeComponent = volumeComponent; mCommandQueue = commandQueue; mRecentsOptional = recentsOptional; + mStatusBarComponentBuilder = statusBarComponentBuilder; mPluginManager = pluginManager; mRemoteInputUriController = remoteInputUriController; mDividerOptional = dividerOptional; @@ -895,7 +898,8 @@ public class StatusBar extends SystemUI implements DemoMode, mKeyguardUpdateMonitor.registerCallback(mUpdateCallback); mDozeServiceHost.initialize(this, mNotificationIconAreaController, - mStatusBarWindow, mStatusBarKeyguardViewManager, + mStatusBarKeyguardViewManager, + mStatusBarWindowViewController, mNotificationPanel, mAmbientIndicationContainer); Dependency.get(ActivityStarterDelegate.class).setActivityStarterImpl(this); @@ -962,7 +966,7 @@ public class StatusBar extends SystemUI implements DemoMode, updateResources(); updateTheme(); - inflateStatusBarWindow(context); + inflateStatusBarWindow(); mStatusBarWindowViewController.setService(this); mStatusBarWindow.setOnTouchListener(getStatusBarWindowTouchListener()); @@ -1381,8 +1385,11 @@ public class StatusBar extends SystemUI implements DemoMode, mNotificationPanel); } - protected void inflateStatusBarWindow(Context context) { + private void inflateStatusBarWindow() { mStatusBarWindow = mSuperStatusBarViewFactory.getStatusBarWindowView(); + StatusBarComponent statusBarComponent = mStatusBarComponentBuilder.get() + .statusBarWindowView(mStatusBarWindow).build(); + mStatusBarWindowViewController = statusBarComponent.getStatusBarWindowViewController(); mStatusBarWindowViewController.setupExpandedStatusBar(); } @@ -4397,11 +4404,6 @@ public class StatusBar extends SystemUI implements DemoMode, return mGutsManager; } - @Subcomponent - public interface StatusBarInjector { - void createStatusBar(StatusBar statusbar); - } - boolean isTransientShown() { return mTransientShown; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarComponent.java new file mode 100644 index 000000000000..f3c843c6d62d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarComponent.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.phone; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import javax.inject.Scope; + +import dagger.BindsInstance; +import dagger.Subcomponent; + +/** + * Dagger subcomponent tied to the lifecycle of StatusBar views. + */ +@Subcomponent +public interface StatusBarComponent { + /** + * Builder for {@link StatusBarComponent}. + */ + @Subcomponent.Builder + interface Builder { + @BindsInstance Builder statusBarWindowView(StatusBarWindowView statusBarWindowView); + StatusBarComponent build(); + } + + /** + * Scope annotation for singleton items within the StatusBarComponent. + */ + @Documented + @Retention(RUNTIME) + @Scope + @interface StatusBarScope {} + + /** + * Creates a StatusBarWindowViewController. + */ + @StatusBarScope + StatusBarWindowViewController getStatusBarWindowViewController(); + +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java index 5d69409d1d4c..312c85f01275 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java @@ -77,6 +77,7 @@ import com.android.systemui.volume.VolumeComponent; import java.util.Optional; import javax.inject.Named; +import javax.inject.Provider; import javax.inject.Singleton; import dagger.Lazy; @@ -143,7 +144,6 @@ public class StatusBarModule { NotificationListener notificationListener, ConfigurationController configurationController, StatusBarWindowController statusBarWindowController, - StatusBarWindowViewController statusBarWindowViewController, LockscreenLockIconController lockscreenLockIconController, DozeParameters dozeParameters, ScrimController scrimController, @@ -157,6 +157,7 @@ public class StatusBarModule { VolumeComponent volumeComponent, CommandQueue commandQueue, Optional<Recents> recentsOptional, + Provider<StatusBarComponent.Builder> statusBarComponentBuilder, PluginManager pluginManager, RemoteInputUriController remoteInputUriController, Optional<Divider> dividerOptional, @@ -217,7 +218,6 @@ public class StatusBarModule { notificationListener, configurationController, statusBarWindowController, - statusBarWindowViewController, lockscreenLockIconController, dozeParameters, scrimController, @@ -231,6 +231,7 @@ public class StatusBarModule { volumeComponent, commandQueue, recentsOptional, + statusBarComponentBuilder, pluginManager, remoteInputUriController, dividerOptional, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowViewController.java index feac5da38138..3935df02fd91 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowViewController.java @@ -43,7 +43,6 @@ import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.DragDownHelper; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.PulseExpansionHandler; -import com.android.systemui.statusbar.SuperStatusBarViewFactory; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.notification.DynamicPrivacyController; import com.android.systemui.statusbar.notification.NotificationEntryManager; @@ -57,14 +56,13 @@ import java.io.FileDescriptor; import java.io.PrintWriter; import javax.inject.Inject; -import javax.inject.Singleton; import dagger.Lazy; /** * Controller for {@link StatusBarWindowView}. */ -@Singleton +//@Singleton public class StatusBarWindowViewController { private final InjectionInflationController mInjectionInflationController; private final NotificationWakeUpCoordinator mCoordinator; @@ -116,9 +114,9 @@ public class StatusBarWindowViewController { DozeLog dozeLog, DozeParameters dozeParameters, CommandQueue commandQueue, - SuperStatusBarViewFactory superStatusBarViewFactory, Lazy<ShadeController> shadeControllerLazy, - DockManager dockManager) { + DockManager dockManager, + StatusBarWindowView statusBarWindowView) { mInjectionInflationController = injectionInflationController; mCoordinator = coordinator; mPulseExpansionHandler = pulseExpansionHandler; @@ -134,7 +132,7 @@ public class StatusBarWindowViewController { mDozeLog = dozeLog; mDozeParameters = dozeParameters; mCommandQueue = commandQueue; - mView = superStatusBarViewFactory.getStatusBarWindowView(); + mView = statusBarWindowView; mShadeControllerLazy = shadeControllerLazy; mDockManager = dockManager; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java index 3f25bb63a614..b0cd90c3fb76 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java @@ -50,9 +50,9 @@ import com.android.systemui.statusbar.policy.NetworkControllerImpl.SubscriptionD import java.io.PrintWriter; import java.util.BitSet; -import java.util.concurrent.Executor; -import java.util.Objects; import java.util.List; +import java.util.Objects; +import java.util.concurrent.Executor; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -552,8 +552,8 @@ public class MobileSignalController extends SignalController< // If this is the data subscription, update the currentState data name if (mCurrentState.networkNameData.equals(mNetworkNameDefault) && mServiceState != null && mCurrentState.dataSim - && !TextUtils.isEmpty(mServiceState.getDataOperatorAlphaShort())) { - mCurrentState.networkNameData = mServiceState.getDataOperatorAlphaShort(); + && !TextUtils.isEmpty(mServiceState.getOperatorAlphaShort())) { + mCurrentState.networkNameData = mServiceState.getOperatorAlphaShort(); } notifyListenersIfNecessary(); diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java new file mode 100644 index 000000000000..24f49ff99879 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.util.concurrency; + +import android.content.Context; +import android.os.Handler; + +import com.android.systemui.dagger.qualifiers.Background; +import com.android.systemui.dagger.qualifiers.BgHandler; +import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.dagger.qualifiers.MainHandler; + +import java.util.concurrent.Executor; + +import dagger.Module; +import dagger.Provides; + +/** + * Dagger Module for classes found within the concurrent package. + */ +@Module +public abstract class ConcurrencyModule { + /** + * Provide a Background-Thread Executor by default. + */ + @Provides + public static Executor provideExecutor(@BgHandler Handler handler) { + return new ExecutorImpl(handler); + } + + /** + * Provide a Background-Thread Executor. + */ + @Provides + @Background + public static Executor provideBackgroundExecutor(@BgHandler Handler handler) { + return new ExecutorImpl(handler); + } + + /** + * Provide a Main-Thread Executor. + */ + @Provides + @Main + public static Executor provideMainExecutor(Context context) { + return context.getMainExecutor(); + } + + /** + * Provide a Background-Thread Executor by default. + */ + @Provides + public static DelayableExecutor provideDelayableExecutor(@BgHandler Handler handler) { + return new ExecutorImpl(handler); + } + + /** + * Provide a Background-Thread Executor. + */ + @Provides + @Background + public static DelayableExecutor provideBackgroundDelayableExecutor(@BgHandler Handler handler) { + return new ExecutorImpl(handler); + } + + /** + * Provide a Main-Thread Executor. + */ + @Provides + @Main + public static DelayableExecutor provideMainDelayableExecutor(@MainHandler Handler handler) { + return new ExecutorImpl(handler); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/DelayableExecutor.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/DelayableExecutor.java new file mode 100644 index 000000000000..2d6c4a62047d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/DelayableExecutor.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.util.concurrency; + +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; + +/** + * A sub-class of {@link Executor} that allows Runnables to be delayed and/or cancelled. + */ +public interface DelayableExecutor extends Executor { + /** + * Execute supplied Runnable on the Executors thread after a specified delay. + * + * See {@link android.os.Handler#postDelayed(Runnable, long)}. + * + * @return A Runnable that, when run, removes the supplied argument from the Executor queue. + */ + default Runnable executeDelayed(Runnable r, long delayMillis) { + return executeDelayed(r, delayMillis, TimeUnit.MILLISECONDS); + } + + /** + * Execute supplied Runnable on the Executors thread after a specified delay. + * + * See {@link android.os.Handler#postDelayed(Runnable, long)}. + * + * @return A Runnable that, when run, removes the supplied argument from the Executor queue.. + */ + Runnable executeDelayed(Runnable r, long delay, TimeUnit unit); + + /** + * Execute supplied Runnable on the Executors thread at a specified uptime. + * + * See {@link android.os.Handler#postAtTime(Runnable, long)}. + * + * @return A Runnable that, when run, removes the supplied argument from the Executor queue. + */ + default Runnable executeAtTime(Runnable r, long uptime) { + return executeAtTime(r, uptime, TimeUnit.MILLISECONDS); + } + + /** + * Execute supplied Runnable on the Executors thread at a specified uptime. + * + * See {@link android.os.Handler#postAtTime(Runnable, long)}. + * + * @return A Runnable that, when run, removes the supplied argument from the Executor queue. + */ + Runnable executeAtTime(Runnable r, long uptimeMillis, TimeUnit unit); + + /** + * Remove all pending Runnables. + * + */ + void removeAll(); + +} diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/ExecutorImpl.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/ExecutorImpl.java new file mode 100644 index 000000000000..2a99de3ac5ad --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/ExecutorImpl.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.util.concurrency; + +import android.os.Handler; +import android.os.HandlerExecutor; +import android.os.Message; + +import java.util.concurrent.TimeUnit; + +/** + * Implementations of {@link DelayableExecutor} for SystemUI. + */ +public class ExecutorImpl extends HandlerExecutor implements DelayableExecutor { + private final Handler mHandler; + + public ExecutorImpl(Handler handler) { + super(handler); + mHandler = handler; + } + + @Override + public Runnable executeDelayed(Runnable r, long delay, TimeUnit unit) { + Object token = new Object(); + Message m = mHandler.obtainMessage(0, token); + mHandler.sendMessageDelayed(m, unit.toMillis(delay)); + + return () -> mHandler.removeCallbacksAndMessages(token); + } + + @Override + public Runnable executeAtTime(Runnable r, long uptimeMillis, TimeUnit unit) { + Object token = new Object(); + Message m = mHandler.obtainMessage(0, token); + mHandler.sendMessageAtTime(m, unit.toMillis(uptimeMillis)); + + return () -> mHandler.removeCallbacksAndMessages(token); + } + + @Override + public void removeAll() { + mHandler.removeCallbacksAndMessages(null); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java index 0990e2274d8a..4cb54728a64c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java @@ -49,6 +49,7 @@ import com.android.systemui.appops.AppOpsController; import com.android.systemui.statusbar.NotificationEntryBuilder; import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.collection.NotifCollection; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import junit.framework.Assert; @@ -70,6 +71,7 @@ public class ForegroundServiceControllerTest extends SysuiTestCase { @Mock private NotificationEntryManager mEntryManager; @Mock private AppOpsController mAppOpsController; @Mock private Handler mMainHandler; + @Mock private NotifCollection mNotifCollection; @Before public void setUp() throws Exception { @@ -79,7 +81,7 @@ public class ForegroundServiceControllerTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); mFsc = new ForegroundServiceController(mEntryManager, mAppOpsController, mMainHandler); mListener = new ForegroundServiceNotificationListener( - mContext, mFsc, mEntryManager); + mContext, mFsc, mEntryManager, mNotifCollection); ArgumentCaptor<NotificationEntryListener> entryListenerCaptor = ArgumentCaptor.forClass(NotificationEntryListener.class); verify(mEntryManager).addNotificationEntryListener( 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 b41512c9a935..39ce8c1209ac 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 @@ -36,13 +36,9 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; -import android.os.Handler; -import android.os.Looper; import android.provider.Settings; import android.service.quicksettings.Tile; import android.testing.AndroidTestingRunner; -import android.testing.TestableLooper; -import android.testing.TestableLooper.RunWithLooper; import android.text.TextUtils; import android.util.ArraySet; @@ -52,6 +48,8 @@ import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.qs.QSTileHost; +import com.android.systemui.util.concurrency.FakeExecutor; +import com.android.systemui.util.time.FakeSystemClock; import org.junit.Before; import org.junit.Test; @@ -69,7 +67,6 @@ import java.util.Set; @SmallTest @RunWith(AndroidTestingRunner.class) -@RunWithLooper public class TileQueryHelperTest extends SysuiTestCase { private static final String CURRENT_TILES = "wifi,dnd,nfc"; private static final String ONLY_STOCK_TILES = "wifi,dnd"; @@ -98,14 +95,13 @@ public class TileQueryHelperTest extends SysuiTestCase { private ArgumentCaptor<List<TileQueryHelper.TileInfo>> mCaptor; private QSTile.State mState; - private TestableLooper mBGLooper; private TileQueryHelper mTileQueryHelper; - private Handler mMainHandler; + private FakeExecutor mMainExecutor; + private FakeExecutor mBgExecutor; @Before public void setup() { MockitoAnnotations.initMocks(this); - mBGLooper = TestableLooper.get(this); mContext.setMockPackageManager(mPackageManager); mState = new QSTile.State(); @@ -123,9 +119,11 @@ public class TileQueryHelperTest extends SysuiTestCase { } ).when(mQSTileHost).createTile(anyString()); - mMainHandler = new Handler(Looper.getMainLooper()); - mTileQueryHelper = new TileQueryHelper(mContext, mMainHandler, - new Handler(mBGLooper.getLooper())); + FakeSystemClock clock = new FakeSystemClock(); + clock.setAutoIncrement(false); + mMainExecutor = new FakeExecutor(clock); + mBgExecutor = new FakeExecutor(clock); + mTileQueryHelper = new TileQueryHelper(mContext, mMainExecutor, mBgExecutor); mTileQueryHelper.setListener(mListener); } @@ -138,8 +136,7 @@ public class TileQueryHelperTest extends SysuiTestCase { public void testIsFinished_trueAfterQuerying() { mTileQueryHelper.queryTiles(mQSTileHost); - mBGLooper.processAllMessages(); - waitForIdleSync(mMainHandler); + FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor); assertTrue(mTileQueryHelper.isFinished()); } @@ -148,8 +145,7 @@ public class TileQueryHelperTest extends SysuiTestCase { public void testQueryTiles_callsListenerTwice() { mTileQueryHelper.queryTiles(mQSTileHost); - mBGLooper.processAllMessages(); - waitForIdleSync(mMainHandler); + FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor); verify(mListener, times(2)).onTilesChanged(any()); } @@ -163,8 +159,7 @@ public class TileQueryHelperTest extends SysuiTestCase { mTileQueryHelper.queryTiles(mQSTileHost); - mBGLooper.processAllMessages(); - waitForIdleSync(mMainHandler); + FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor); assertTrue(mTileQueryHelper.isFinished()); } @@ -178,8 +173,7 @@ public class TileQueryHelperTest extends SysuiTestCase { mTileQueryHelper.queryTiles(mQSTileHost); - mBGLooper.processAllMessages(); - waitForIdleSync(mMainHandler); + FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor); verify(mListener, atLeastOnce()).onTilesChanged(mCaptor.capture()); List<String> specs = new ArrayList<>(); @@ -199,8 +193,7 @@ public class TileQueryHelperTest extends SysuiTestCase { mTileQueryHelper.queryTiles(mQSTileHost); - mBGLooper.processAllMessages(); - waitForIdleSync(mMainHandler); + FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor); verify(mListener, atLeastOnce()).onTilesChanged(mCaptor.capture()); List<String> specs = new ArrayList<>(); @@ -220,8 +213,7 @@ public class TileQueryHelperTest extends SysuiTestCase { mTileQueryHelper.queryTiles(mQSTileHost); - mBGLooper.processAllMessages(); - waitForIdleSync(mMainHandler); + FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor); verify(mListener, atLeastOnce()).onTilesChanged(mCaptor.capture()); List<String> specs = new ArrayList<>(); @@ -251,8 +243,7 @@ public class TileQueryHelperTest extends SysuiTestCase { ""); mTileQueryHelper.queryTiles(mQSTileHost); - mBGLooper.processAllMessages(); - waitForIdleSync(mMainHandler); + FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor); verify(mListener, atLeastOnce()).onTilesChanged(mCaptor.capture()); List<TileQueryHelper.TileInfo> tileInfos = mCaptor.getValue(); 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 a98945f49b99..e0dfe7e66ad5 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 @@ -85,6 +85,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationRowBin import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl; import com.android.systemui.statusbar.notification.logging.NotifLog; import com.android.systemui.statusbar.notification.logging.NotificationLogger; +import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.row.RowInflaterTask; @@ -228,7 +229,8 @@ public class NotificationEntryManagerTest extends SysuiTestCase { mHeadsUpManager, mock(NotificationFilter.class), mNotifLog, - mock(NotificationSectionsFeatureManager.class)), + mock(NotificationSectionsFeatureManager.class), + mock(PeopleNotificationIdentifier.class)), mEnvironment ); Dependency.get(InitController.class).executePostInitTasks(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt index 01b2f8932d9b..cda1538e899d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt @@ -20,7 +20,6 @@ import android.app.Notification import android.app.NotificationChannel import android.app.NotificationManager.IMPORTANCE_DEFAULT import android.app.NotificationManager.IMPORTANCE_LOW -import android.app.Person import android.service.notification.NotificationListenerService.RankingMap import android.service.notification.StatusBarNotification import android.testing.AndroidTestingRunner @@ -36,6 +35,7 @@ import com.android.systemui.statusbar.NotificationMediaManager import com.android.systemui.statusbar.notification.NotificationFilter import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager import com.android.systemui.statusbar.notification.logging.NotifLog +import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT import com.android.systemui.statusbar.phone.NotificationGroupManager @@ -52,62 +52,41 @@ import org.mockito.Mockito.mock @SmallTest @RunWith(AndroidTestingRunner::class) -class NotificationRankingManagerTest - : SysuiTestCase() { +class NotificationRankingManagerTest : SysuiTestCase() { - private var lazyMedia: Lazy<NotificationMediaManager> = Lazy { - mock<NotificationMediaManager>(NotificationMediaManager::class.java) + private val lazyMedia: Lazy<NotificationMediaManager> = Lazy { + mock(NotificationMediaManager::class.java) } - - private val rankingManager = TestableNotificationRankingManager( - lazyMedia, - mock<NotificationGroupManager>(NotificationGroupManager::class.java), - mock<HeadsUpManager>(HeadsUpManager::class.java), - mock<NotificationFilter>(NotificationFilter::class.java), - mock<NotifLog>(NotifLog::class.java), - mock<NotificationSectionsFeatureManager>(NotificationSectionsFeatureManager::class.java) - ) + private lateinit var personNotificationIdentifier: PeopleNotificationIdentifier + private lateinit var rankingManager: TestableNotificationRankingManager @Before fun setup() { + personNotificationIdentifier = + mock(PeopleNotificationIdentifier::class.java) + rankingManager = TestableNotificationRankingManager( + lazyMedia, + mock(NotificationGroupManager::class.java), + mock(HeadsUpManager::class.java), + mock(NotificationFilter::class.java), + mock(NotifLog::class.java), + mock(NotificationSectionsFeatureManager::class.java), + personNotificationIdentifier + ) } @Test fun testPeopleNotification_isHighPriority() { - val person = Person.Builder() - .setName("name") - .setKey("abc") - .setUri("uri") - .setBot(true) - .build() - val notification = Notification.Builder(mContext, "test") - .addPerson(person) .build() val sbn = StatusBarNotification("pkg", "pkg", 0, "tag", 0, 0, notification, mContext.user, "", 0) - val e = NotificationEntryBuilder() - .setNotification(notification) - .setSbn(sbn) - .build() - - assertTrue(rankingManager.isHighPriority2(e)) - } - - @Test - fun messagingStyleHighPriority() { - - val notif = Notification.Builder(mContext, "test") - .setStyle(Notification.MessagingStyle("")) - .build() - - val sbn = StatusBarNotification("pkg", "pkg", 0, "tag", 0, 0, - notif, mContext.getUser(), "", 0) + `when`(personNotificationIdentifier.isPeopleNotification(sbn)).thenReturn(true) val e = NotificationEntryBuilder() - .setNotification(notif) + .setNotification(notification) .setSbn(sbn) .build() @@ -117,7 +96,7 @@ class NotificationRankingManagerTest @Test fun lowForegroundHighPriority() { val notification = mock(Notification::class.java) - `when`<Boolean>(notification.isForegroundService).thenReturn(true) + `when`(notification.isForegroundService).thenReturn(true) val sbn = StatusBarNotification("pkg", "pkg", 0, "tag", 0, 0, notification, mContext.user, "", 0) @@ -136,15 +115,7 @@ class NotificationRankingManagerTest @Test fun userChangeTrumpsHighPriorityCharacteristics() { - val person = Person.Builder() - .setName("name") - .setKey("abc") - .setUri("uri") - .setBot(true) - .build() - val notification = Notification.Builder(mContext, "test") - .addPerson(person) .setStyle(Notification.MessagingStyle("")) .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true) .build() @@ -152,6 +123,8 @@ class NotificationRankingManagerTest val sbn = StatusBarNotification("pkg", "pkg", 0, "tag", 0, 0, notification, mContext.user, "", 0) + `when`(personNotificationIdentifier.isPeopleNotification(sbn)).thenReturn(true) + val channel = NotificationChannel("a", "a", IMPORTANCE_LOW) channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE) @@ -297,14 +270,16 @@ class NotificationRankingManagerTest headsUpManager: HeadsUpManager, filter: NotificationFilter, notifLog: NotifLog, - sectionsFeatureManager: NotificationSectionsFeatureManager + sectionsFeatureManager: NotificationSectionsFeatureManager, + peopleNotificationIdentifier: PeopleNotificationIdentifier ) : NotificationRankingManager( mediaManager, groupManager, headsUpManager, filter, notifLog, - sectionsFeatureManager + sectionsFeatureManager, + peopleNotificationIdentifier ) { fun isHighPriority2(e: NotificationEntry): Boolean { @@ -315,4 +290,4 @@ class NotificationRankingManagerTest rankingMap = r } } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java new file mode 100644 index 000000000000..87b3783d1984 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java @@ -0,0 +1,243 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.collection.coordinator; + +import static android.app.Notification.VISIBILITY_PUBLIC; +import static android.app.Notification.VISIBILITY_SECRET; +import static android.app.NotificationManager.IMPORTANCE_HIGH; +import static android.app.NotificationManager.IMPORTANCE_MIN; + +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; + +import static org.mockito.Mockito.when; + +import android.os.Handler; +import android.os.UserHandle; +import android.testing.AndroidTestingRunner; + +import androidx.test.filters.SmallTest; + +import com.android.keyguard.KeyguardUpdateMonitor; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.statusbar.NotificationEntryBuilder; +import com.android.systemui.statusbar.NotificationLockscreenUserManager; +import com.android.systemui.statusbar.RankingBuilder; +import com.android.systemui.statusbar.notification.collection.GroupEntry; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.policy.KeyguardStateController; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class KeyguardCoordinatorTest extends SysuiTestCase { + private static final int NOTIF_USER_ID = 0; + private static final int CURR_USER_ID = 1; + + @Mock private Handler mMainHandler; + @Mock private KeyguardStateController mKeyguardStateController; + @Mock private NotificationLockscreenUserManager mLockscreenUserManager; + @Mock private BroadcastDispatcher mBroadcastDispatcher; + @Mock private StatusBarStateController mStatusBarStateController; + @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor; + + private NotificationEntry mEntry; + private KeyguardCoordinator mKeyguardNotificationCoordinator; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mKeyguardNotificationCoordinator = new KeyguardCoordinator( + mContext, mMainHandler, mKeyguardStateController, mLockscreenUserManager, + mBroadcastDispatcher, mStatusBarStateController, + mKeyguardUpdateMonitor); + + mEntry = new NotificationEntryBuilder() + .setUser(new UserHandle(NOTIF_USER_ID)) + .build(); + } + + @Test + public void unfilteredState() { + // GIVEN an 'unfiltered-keyguard-showing' state + setupUnfilteredState(); + + // THEN don't filter out the entry + assertFalse(mKeyguardNotificationCoordinator.mNotifFilter.shouldFilterOut(mEntry, 0)); + } + + @Test + public void notificationNotForCurrentProfile() { + // GIVEN the notification isn't for the given user + setupUnfilteredState(); + when(mLockscreenUserManager.isCurrentProfile(NOTIF_USER_ID)).thenReturn(false); + + // THEN filter out the entry + assertTrue(mKeyguardNotificationCoordinator.mNotifFilter.shouldFilterOut(mEntry, 0)); + } + + @Test + public void keyguardNotShowing() { + // GIVEN the lockscreen isn't showing + setupUnfilteredState(); + when(mKeyguardStateController.isShowing()).thenReturn(false); + + // THEN don't filter out the entry + assertFalse(mKeyguardNotificationCoordinator.mNotifFilter.shouldFilterOut(mEntry, 0)); + } + + @Test + public void doNotShowLockscreenNotifications() { + // GIVEN an 'unfiltered-keyguard-showing' state + setupUnfilteredState(); + + // WHEN we shouldn't show any lockscreen notifications + when(mLockscreenUserManager.shouldShowLockscreenNotifications()).thenReturn(false); + + // THEN filter out the entry + assertTrue(mKeyguardNotificationCoordinator.mNotifFilter.shouldFilterOut(mEntry, 0)); + } + + @Test + public void lockdown() { + // GIVEN an 'unfiltered-keyguard-showing' state + setupUnfilteredState(); + + // WHEN the notification's user is in lockdown: + when(mKeyguardUpdateMonitor.isUserInLockdown(NOTIF_USER_ID)).thenReturn(true); + + // THEN filter out the entry + assertTrue(mKeyguardNotificationCoordinator.mNotifFilter.shouldFilterOut(mEntry, 0)); + } + + @Test + public void publicMode_settingsDisallow() { + // GIVEN an 'unfiltered-keyguard-showing' state + setupUnfilteredState(); + + // WHEN the notification's user is in public mode and settings are configured to disallow + // notifications in public mode + when(mLockscreenUserManager.isLockscreenPublicMode(NOTIF_USER_ID)).thenReturn(true); + when(mLockscreenUserManager.userAllowsNotificationsInPublic(NOTIF_USER_ID)) + .thenReturn(false); + + // THEN filter out the entry + assertTrue(mKeyguardNotificationCoordinator.mNotifFilter.shouldFilterOut(mEntry, 0)); + } + + @Test + public void publicMode_notifDisallowed() { + // GIVEN an 'unfiltered-keyguard-showing' state + setupUnfilteredState(); + + // WHEN the notification's user is in public mode and settings are configured to disallow + // notifications in public mode + when(mLockscreenUserManager.isLockscreenPublicMode(CURR_USER_ID)).thenReturn(true); + mEntry.setRanking(new RankingBuilder() + .setKey(mEntry.getKey()) + .setVisibilityOverride(VISIBILITY_SECRET).build()); + + // THEN filter out the entry + assertTrue(mKeyguardNotificationCoordinator.mNotifFilter.shouldFilterOut(mEntry, 0)); + } + + @Test + public void doesNotExceedThresholdToShow() { + // GIVEN an 'unfiltered-keyguard-showing' state + setupUnfilteredState(); + + // WHEN the notification doesn't exceed the threshold to show on the lockscreen + mEntry.setRanking(new RankingBuilder() + .setKey(mEntry.getKey()) + .setImportance(IMPORTANCE_MIN) + .build()); + + // THEN filter out the entry + assertTrue(mKeyguardNotificationCoordinator.mNotifFilter.shouldFilterOut(mEntry, 0)); + } + + @Test + public void summaryExceedsThresholdToShow() { + // GIVEN an 'unfiltered-keyguard-showing' state + setupUnfilteredState(); + + // WHEN the notification doesn't exceed the threshold to show on the lockscreen + // but its summary does + mEntry.setRanking(new RankingBuilder() + .setKey(mEntry.getKey()) + .setImportance(IMPORTANCE_MIN) + .build()); + + final NotificationEntry summary = new NotificationEntryBuilder().build(); + summary.setRanking(new RankingBuilder() + .setKey(summary.getKey()) + .setImportance(IMPORTANCE_HIGH) + .build()); + final GroupEntry group = new GroupEntry(mEntry.getSbn().getGroupKey()); + group.setSummary(summary); + mEntry.setParent(group); + + // THEN don't filter out the entry + assertFalse(mKeyguardNotificationCoordinator.mNotifFilter.shouldFilterOut(mEntry, 0)); + } + + /** + * setup a state where the notification will not be filtered by the + * KeyguardNotificationCoordinator when the keyguard is showing. + */ + private void setupUnfilteredState() { + // notification is for current profile + when(mLockscreenUserManager.isCurrentProfile(NOTIF_USER_ID)).thenReturn(true); + + // keyguard is showing + when(mKeyguardStateController.isShowing()).thenReturn(true); + + // show notifications on the lockscreen + when(mLockscreenUserManager.shouldShowLockscreenNotifications()).thenReturn(true); + + // neither the current user nor the notification's user is in lockdown + when(mLockscreenUserManager.getCurrentUserId()).thenReturn(CURR_USER_ID); + when(mKeyguardUpdateMonitor.isUserInLockdown(NOTIF_USER_ID)).thenReturn(false); + when(mKeyguardUpdateMonitor.isUserInLockdown(CURR_USER_ID)).thenReturn(false); + + // not in public mode + when(mLockscreenUserManager.isLockscreenPublicMode(CURR_USER_ID)).thenReturn(false); + when(mLockscreenUserManager.isLockscreenPublicMode(NOTIF_USER_ID)).thenReturn(false); + + // entry's ranking - should show on all lockscreens + // + priority of the notification exceeds the threshold to be shown on the lockscreen + mEntry.setRanking(new RankingBuilder() + .setKey(mEntry.getKey()) + .setVisibilityOverride(VISIBILITY_PUBLIC) + .setImportance(IMPORTANCE_HIGH) + .build()); + + // settings allows notifications in public mode + when(mLockscreenUserManager.userAllowsNotificationsInPublic(CURR_USER_ID)).thenReturn(true); + when(mLockscreenUserManager.userAllowsNotificationsInPublic(NOTIF_USER_ID)) + .thenReturn(true); + + // notification doesn't have a summary + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java index d20a37a18d2e..d2b4a20ca3a9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java @@ -74,6 +74,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationRankingManager; import com.android.systemui.statusbar.notification.logging.NotifLog; import com.android.systemui.statusbar.notification.people.PeopleHubSectionFooterViewAdapter; +import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.FooterView; import com.android.systemui.statusbar.notification.row.NotificationBlockingHelperManager; @@ -164,7 +165,8 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { mHeadsUpManager, mock(NotificationFilter.class), mock(NotifLog.class), - mock(NotificationSectionsFeatureManager.class) + mock(NotificationSectionsFeatureManager.class), + mock(PeopleNotificationIdentifier.class) ), mock(NotificationEntryManager.KeyguardEnvironment.class)); mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java index 222fcb696c8b..46f6cfe39e3c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java @@ -85,7 +85,6 @@ public class DozeServiceHostTest extends SysuiTestCase { @Mock private StatusBar mStatusBar; @Mock private NotificationIconAreaController mNotificationIconAreaController; @Mock private StatusBarWindowViewController mStatusBarWindowViewController; - @Mock private StatusBarWindowView mStatusBarWindow; @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; @Mock private NotificationPanelView mNotificationPanel; @Mock private View mAmbientIndicationContainer; @@ -101,10 +100,11 @@ public class DozeServiceHostTest extends SysuiTestCase { mKeyguardViewMediator, () -> mAssistManager, mDozeScrimController, mKeyguardUpdateMonitor, mVisualStabilityManager, mPulseExpansionHandler, mStatusBarWindowController, mNotificationWakeUpCoordinator, - mStatusBarWindowViewController, mLockscreenLockIconController); + mLockscreenLockIconController); - mDozeServiceHost.initialize(mStatusBar, mNotificationIconAreaController, mStatusBarWindow, - mStatusBarKeyguardViewManager, mNotificationPanel, mAmbientIndicationContainer); + mDozeServiceHost.initialize(mStatusBar, mNotificationIconAreaController, + mStatusBarKeyguardViewManager, mStatusBarWindowViewController, mNotificationPanel, + mAmbientIndicationContainer); } @Test 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 e78fb9e521e1..be6809732963 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 @@ -145,6 +145,8 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.Optional; +import javax.inject.Provider; + import dagger.Lazy; @SmallTest @@ -227,6 +229,9 @@ public class StatusBarTest extends SysuiTestCase { @Mock private VolumeComponent mVolumeComponent; @Mock private CommandQueue mCommandQueue; @Mock private Recents mRecents; + @Mock private Provider<StatusBarComponent.Builder> mStatusBarComponentBuilderProvider; + @Mock private StatusBarComponent.Builder mStatusBarComponentBuilder; + @Mock private StatusBarComponent mStatusBarComponent; @Mock private PluginManager mPluginManager; @Mock private Divider mDivider; @Mock private SuperStatusBarViewFactory mSuperStatusBarViewFactory; @@ -300,6 +305,11 @@ public class StatusBarTest extends SysuiTestCase { when(mLockscreenWallpaperLazy.get()).thenReturn(mLockscreenWallpaper); when(mBiometricUnlockControllerLazy.get()).thenReturn(mBiometricUnlockController); + when(mStatusBarComponentBuilderProvider.get()).thenReturn(mStatusBarComponentBuilder); + when(mStatusBarComponentBuilder.build()).thenReturn(mStatusBarComponent); + when(mStatusBarComponent.getStatusBarWindowViewController()).thenReturn( + mStatusBarWindowViewController); + mStatusBar = new StatusBar( mContext, mFeatureFlags, @@ -354,7 +364,6 @@ public class StatusBarTest extends SysuiTestCase { mNotificationListener, configurationController, mStatusBarWindowController, - mStatusBarWindowViewController, mLockscreenLockIconController, mDozeParameters, mScrimController, @@ -367,6 +376,7 @@ public class StatusBarTest extends SysuiTestCase { mVolumeComponent, mCommandQueue, Optional.of(mRecents), + mStatusBarComponentBuilderProvider, mPluginManager, mRemoteInputUriController, Optional.of(mDivider), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowViewTest.java index ee9ea9f26a95..529333effa2c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowViewTest.java @@ -36,7 +36,6 @@ import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.DragDownHelper; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.PulseExpansionHandler; -import com.android.systemui.statusbar.SuperStatusBarViewFactory; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.notification.DynamicPrivacyController; import com.android.systemui.statusbar.notification.NotificationEntryManager; @@ -74,7 +73,6 @@ public class StatusBarWindowViewTest extends SysuiTestCase { @Mock private StatusBar mStatusBar; @Mock private DozeLog mDozeLog; @Mock private DozeParameters mDozeParameters; - @Mock private SuperStatusBarViewFactory mSuperStatusBarViewFactory; @Mock private DockManager mDockManager; @Before @@ -85,7 +83,6 @@ public class StatusBarWindowViewTest extends SysuiTestCase { when(mStatusBar.isDozing()).thenReturn(false); mDependency.injectTestDependency(ShadeController.class, mShadeController); - when(mSuperStatusBarViewFactory.getStatusBarWindowView()).thenReturn(mView); when(mDockManager.isDocked()).thenReturn(false); mController = new StatusBarWindowViewController( @@ -105,9 +102,9 @@ public class StatusBarWindowViewTest extends SysuiTestCase { mDozeLog, mDozeParameters, new CommandQueue(mContext), - mSuperStatusBarViewFactory, () -> mShadeController, - mDockManager); + mDockManager, + mView); mController.setupExpandedStatusBar(); mController.setService(mStatusBar); mController.setDragDownHelper(mDragDownHelper); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java index ab74caa95e61..95b055c7d0b5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java @@ -500,7 +500,7 @@ public class NetworkControllerDataTest extends NetworkControllerBaseTest { public void testUpdateDataNetworkName() { setupDefaultSignal(); String newDataName = "TestDataName"; - when(mServiceState.getDataOperatorAlphaShort()).thenReturn(newDataName); + when(mServiceState.getOperatorAlphaShort()).thenReturn(newDataName); updateServiceState(); assertDataNetworkNameEquals(newDataName); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutor.java b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutor.java new file mode 100644 index 000000000000..3ed6c5b2b11b --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutor.java @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.util.concurrency; + +import com.android.systemui.util.time.FakeSystemClock; +import com.android.systemui.util.time.FakeSystemClock.ClockTickListener; + +import java.util.Collections; +import java.util.PriorityQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +public class FakeExecutor implements DelayableExecutor { + private final FakeSystemClock mClock; + private PriorityQueue<QueuedRunnable> mQueuedRunnables = new PriorityQueue<>(); + private boolean mIgnoreClockUpdates; + + private ClockTickListener mClockTickListener = new ClockTickListener() { + @Override + public void onUptimeMillis(long uptimeMillis) { + if (!mIgnoreClockUpdates) { + runAllReady(); + } + } + }; + + /** + * Initializes a fake executor. + * + * @param clock FakeSystemClock allowing control over delayed runnables. It is strongly + * recommended that this clock have its auto-increment setting set to false to + * prevent unexpected advancement of the time. + */ + public FakeExecutor(FakeSystemClock clock) { + mClock = clock; + mClock.addListener(mClockTickListener); + } + + /** + * Runs a single runnable if it's scheduled to run according to the internal clock. + * + * If constructed to advance the clock automatically, this will advance the clock enough to + * run the next pending item. + * + * This method does not advance the clock past the item that was run. + * + * @return Returns true if an item was run. + */ + public boolean runNextReady() { + if (!mQueuedRunnables.isEmpty() && mQueuedRunnables.peek().mWhen <= mClock.uptimeMillis()) { + mQueuedRunnables.poll().mRunnable.run(); + return true; + } + + return false; + } + + /** + * Runs all Runnables that are scheduled to run according to the internal clock. + * + * If constructed to advance the clock automatically, this will advance the clock enough to + * run all the pending items. This method does not advance the clock past items that were + * run. It is equivalent to calling {@link #runNextReady()} in a loop. + * + * @return Returns the number of items that ran. + */ + public int runAllReady() { + int num = 0; + while (runNextReady()) { + num++; + } + + return num; + } + + /** + * Advances the internal clock to the next item to run. + * + * The clock will only move forward. If the next item is set to run in the past or there is no + * next item, the clock does not change. + * + * Note that this will cause one or more items to actually run. + * + * @return The delta in uptimeMillis that the clock advanced, or 0 if the clock did not advance. + */ + public long advanceClockToNext() { + if (mQueuedRunnables.isEmpty()) { + return 0; + } + + long startTime = mClock.uptimeMillis(); + long nextTime = mQueuedRunnables.peek().mWhen; + if (nextTime <= startTime) { + return 0; + } + updateClock(nextTime); + + return nextTime - startTime; + } + + + /** + * Advances the internal clock to the last item to run. + * + * The clock will only move forward. If the last item is set to run in the past or there is no + * next item, the clock does not change. + * + * @return The delta in uptimeMillis that the clock advanced, or 0 if the clock did not advance. + */ + public long advanceClockToLast() { + if (mQueuedRunnables.isEmpty()) { + return 0; + } + + long startTime = mClock.uptimeMillis(); + long nextTime = Collections.max(mQueuedRunnables).mWhen; + if (nextTime <= startTime) { + return 0; + } + + updateClock(nextTime); + + return nextTime - startTime; + } + + /** + * Returns the number of un-executed runnables waiting to run. + */ + public int numPending() { + return mQueuedRunnables.size(); + } + + @Override + public Runnable executeDelayed(Runnable r, long delay, TimeUnit unit) { + if (delay < 0) { + delay = 0; + } + return executeAtTime(r, mClock.uptimeMillis() + unit.toMillis(delay)); + } + + @Override + public Runnable executeAtTime(Runnable r, long uptime, TimeUnit unit) { + long uptimeMillis = unit.toMillis(uptime); + + QueuedRunnable container = new QueuedRunnable(r, uptimeMillis); + + mQueuedRunnables.offer(container); + + return () -> mQueuedRunnables.remove(container); + } + + @Override + public void execute(Runnable command) { + executeDelayed(command, 0); + } + + @Override + public void removeAll() { + mQueuedRunnables.clear(); + } + + /** + * Run all Executors in a loop until they all report they have no ready work to do. + * + * Useful if you have Executors the post work to other Executors, and you simply want to + * run them all until they stop posting work. + */ + public static void exhaustExecutors(FakeExecutor ...executors) { + boolean didAnything; + do { + didAnything = false; + for (FakeExecutor executor : executors) { + didAnything = didAnything || executor.runAllReady() != 0; + } + } while (didAnything); + } + + private void updateClock(long nextTime) { + mIgnoreClockUpdates = true; + mClock.setUptimeMillis(nextTime); + mIgnoreClockUpdates = false; + } + + private static class QueuedRunnable implements Comparable<QueuedRunnable> { + private static AtomicInteger sCounter = new AtomicInteger(); + + Runnable mRunnable; + long mWhen; + private int mCounter; + + private QueuedRunnable(Runnable r, long when) { + mRunnable = r; + mWhen = when; + + // PrioirityQueue orders items arbitrarily when equal. We want to ensure that + // otherwise-equal elements are ordered according to their insertion order. Because this + // class only is constructed right before insertion, we use a static counter to track + // insertion order of otherwise equal elements. + mCounter = sCounter.incrementAndGet(); + } + + @Override + public int compareTo(QueuedRunnable other) { + long diff = mWhen - other.mWhen; + + if (diff == 0) { + return mCounter - other.mCounter; + } + + return diff > 0 ? 1 : -1; + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutorTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutorTest.java new file mode 100644 index 000000000000..7fd694244afa --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutorTest.java @@ -0,0 +1,366 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.util.concurrency; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.testing.AndroidTestingRunner; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.util.time.FakeSystemClock; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; +import java.util.List; + +import kotlin.jvm.functions.Function4; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class FakeExecutorTest extends SysuiTestCase { + @Before + public void setUp() throws Exception { + } + + /** + * Test FakeExecutor that receives non-delayed items to execute. + */ + @Test + public void testNoDelay() { + FakeSystemClock clock = new FakeSystemClock(); + clock.setAutoIncrement(false); + FakeExecutor fakeExecutor = new FakeExecutor(clock); + RunnableImpl runnable = new RunnableImpl(); + + assertEquals(0, clock.uptimeMillis()); + assertEquals(0, runnable.mRunCount); + + // Execute two runnables. They should not run and should be left pending. + fakeExecutor.execute(runnable); + assertEquals(0, runnable.mRunCount); + assertEquals(0, clock.uptimeMillis()); + assertEquals(1, fakeExecutor.numPending()); + fakeExecutor.execute(runnable); + assertEquals(0, runnable.mRunCount); + assertEquals(0, clock.uptimeMillis()); + assertEquals(2, fakeExecutor.numPending()); + + // Run one pending runnable. + assertTrue(fakeExecutor.runNextReady()); + assertEquals(1, runnable.mRunCount); + assertEquals(0, clock.uptimeMillis()); + assertEquals(1, fakeExecutor.numPending()); + // Run a second pending runnable. + assertTrue(fakeExecutor.runNextReady()); + assertEquals(2, runnable.mRunCount); + assertEquals(0, clock.uptimeMillis()); + assertEquals(0, fakeExecutor.numPending()); + + // No more runnables to run. + assertFalse(fakeExecutor.runNextReady()); + + // Add two more runnables. + fakeExecutor.execute(runnable); + fakeExecutor.execute(runnable); + assertEquals(2, runnable.mRunCount); + assertEquals(0, clock.uptimeMillis()); + assertEquals(2, fakeExecutor.numPending()); + // Execute all pending runnables in batch. + assertEquals(2, fakeExecutor.runAllReady()); + assertEquals(4, runnable.mRunCount); + assertEquals(0, clock.uptimeMillis()); + assertEquals(0, fakeExecutor.runAllReady()); + } + + /** + * Test FakeExecutor that is told to delay execution on items. + */ + @Test + public void testDelayed() { + FakeSystemClock clock = new FakeSystemClock(); + clock.setAutoIncrement(false); + FakeExecutor fakeExecutor = new FakeExecutor(clock); + RunnableImpl runnable = new RunnableImpl(); + + // Add three delayed runnables. + fakeExecutor.executeDelayed(runnable, 1); + fakeExecutor.executeDelayed(runnable, 50); + fakeExecutor.executeDelayed(runnable, 100); + assertEquals(0, runnable.mRunCount); + assertEquals(0, clock.uptimeMillis()); + assertEquals(3, fakeExecutor.numPending()); + // Delayed runnables should not advance the clock and therefore should not run. + assertFalse(fakeExecutor.runNextReady()); + assertEquals(0, fakeExecutor.runAllReady()); + assertEquals(3, fakeExecutor.numPending()); + + // Advance the clock to the next runnable. One runnable should execute. + assertEquals(1, fakeExecutor.advanceClockToNext()); + assertEquals(1, fakeExecutor.runAllReady()); + assertEquals(2, fakeExecutor.numPending()); + assertEquals(1, runnable.mRunCount); + // Advance the clock to the last runnable. + assertEquals(99, fakeExecutor.advanceClockToLast()); + assertEquals(2, fakeExecutor.runAllReady()); + // Now all remaining runnables should execute. + assertEquals(0, fakeExecutor.numPending()); + assertEquals(3, runnable.mRunCount); + } + + /** + * Test FakeExecutor that is told to delay execution on items. + */ + @Test + public void testDelayed_AdvanceAndRun() { + FakeSystemClock clock = new FakeSystemClock(); + clock.setAutoIncrement(false); + FakeExecutor fakeExecutor = new FakeExecutor(clock); + RunnableImpl runnable = new RunnableImpl(); + + // Add three delayed runnables. + fakeExecutor.executeDelayed(runnable, 1); + fakeExecutor.executeDelayed(runnable, 50); + fakeExecutor.executeDelayed(runnable, 100); + assertEquals(0, runnable.mRunCount); + assertEquals(0, clock.uptimeMillis()); + assertEquals(3, fakeExecutor.numPending()); + // Delayed runnables should not advance the clock and therefore should not run. + assertFalse(fakeExecutor.runNextReady()); + assertEquals(0, fakeExecutor.runAllReady()); + assertEquals(3, fakeExecutor.numPending()); + + // Advance the clock to the next runnable. Check that it is run. + assertEquals(1, fakeExecutor.advanceClockToNext()); + assertEquals(1, fakeExecutor.runAllReady()); + assertEquals(1, clock.uptimeMillis()); + assertEquals(2, fakeExecutor.numPending()); + assertEquals(1, runnable.mRunCount); + assertEquals(49, fakeExecutor.advanceClockToNext()); + assertEquals(1, fakeExecutor.runAllReady()); + assertEquals(50, clock.uptimeMillis()); + assertEquals(1, fakeExecutor.numPending()); + assertEquals(2, runnable.mRunCount); + assertEquals(50, fakeExecutor.advanceClockToNext()); + assertEquals(1, fakeExecutor.runAllReady()); + assertEquals(100, clock.uptimeMillis()); + assertEquals(0, fakeExecutor.numPending()); + assertEquals(3, runnable.mRunCount); + + // Nothing left to do + assertEquals(0, fakeExecutor.advanceClockToNext()); + assertEquals(0, fakeExecutor.runAllReady()); + assertEquals(100, clock.uptimeMillis()); + assertEquals(0, fakeExecutor.numPending()); + assertEquals(3, runnable.mRunCount); + } + + /** + * Test execution order. + */ + @Test + public void testExecutionOrder() { + FakeSystemClock clock = new FakeSystemClock(); + clock.setAutoIncrement(false); + FakeExecutor fakeExecutor = new FakeExecutor(clock); + RunnableImpl runnableA = new RunnableImpl(); + RunnableImpl runnableB = new RunnableImpl(); + RunnableImpl runnableC = new RunnableImpl(); + RunnableImpl runnableD = new RunnableImpl(); + + Function4<Integer, Integer, Integer, Integer, Void> checkRunCounts = + (Integer argA, Integer argB, Integer argC, Integer argD) -> { + assertEquals("RunnableA run count wrong", argA.intValue(), runnableA.mRunCount); + assertEquals("RunnableB run count wrong", argB.intValue(), runnableB.mRunCount); + assertEquals("RunnableC run count wrong", argC.intValue(), runnableC.mRunCount); + assertEquals("RunnableD run count wrong", argD.intValue(), runnableD.mRunCount); + return null; + }; + + assertEquals(0, clock.uptimeMillis()); + checkRunCounts.invoke(0, 0, 0, 0); + + fakeExecutor.execute(runnableA); + fakeExecutor.execute(runnableB); + fakeExecutor.execute(runnableC); + fakeExecutor.execute(runnableD); + + fakeExecutor.runNextReady(); + checkRunCounts.invoke(1, 0, 0, 0); + fakeExecutor.runNextReady(); + checkRunCounts.invoke(1, 1, 0, 0); + fakeExecutor.runNextReady(); + checkRunCounts.invoke(1, 1, 1, 0); + fakeExecutor.runNextReady(); + checkRunCounts.invoke(1, 1, 1, 1); + + fakeExecutor.executeDelayed(runnableA, 100); + fakeExecutor.execute(runnableB); + fakeExecutor.executeDelayed(runnableC, 50); + fakeExecutor.execute(runnableD); + + fakeExecutor.advanceClockToNext(); + fakeExecutor.runAllReady(); + checkRunCounts.invoke(1, 2, 1, 2); + fakeExecutor.advanceClockToNext(); + fakeExecutor.runAllReady(); + checkRunCounts.invoke(1, 2, 2, 2); + fakeExecutor.advanceClockToNext(); + fakeExecutor.runAllReady(); + checkRunCounts.invoke(2, 2, 2, 2); + + fakeExecutor.execute(runnableA); + fakeExecutor.executeAtTime(runnableB, 0); // this is in the past! + fakeExecutor.executeAtTime(runnableC, 1000); + fakeExecutor.executeAtTime(runnableD, 500); + + fakeExecutor.advanceClockToNext(); + fakeExecutor.runAllReady(); + checkRunCounts.invoke(3, 3, 2, 2); + fakeExecutor.advanceClockToNext(); + fakeExecutor.runAllReady(); + checkRunCounts.invoke(3, 3, 2, 3); + fakeExecutor.advanceClockToNext(); + fakeExecutor.runAllReady(); + checkRunCounts.invoke(3, 3, 3, 3); + } + + /** + * Test removing a single item. + */ + @Test + public void testRemoval_single() { + FakeSystemClock clock = new FakeSystemClock(); + clock.setAutoIncrement(false); + FakeExecutor fakeExecutor = new FakeExecutor(clock); + RunnableImpl runnable = new RunnableImpl(); + Runnable removeFunction; + + // Nothing to remove. + assertEquals(0, runnable.mRunCount); + assertEquals(0, fakeExecutor.numPending()); + + // Two pending items that have not yet run. + // We will try to remove the second item. + fakeExecutor.executeDelayed(runnable, 100); + removeFunction = fakeExecutor.executeDelayed(runnable, 200); + assertEquals(2, fakeExecutor.numPending()); + assertEquals(0, runnable.mRunCount); + + // Remove the item. + removeFunction.run(); + assertEquals(1, fakeExecutor.numPending()); + assertEquals(0, runnable.mRunCount); + + // One item to run. + fakeExecutor.advanceClockToLast(); + fakeExecutor.runAllReady(); + assertEquals(0, fakeExecutor.numPending()); + assertEquals(1, runnable.mRunCount); + + // Nothing to remove. + removeFunction.run(); + fakeExecutor.runAllReady(); + assertEquals(0, fakeExecutor.numPending()); + assertEquals(1, runnable.mRunCount); + } + + /** + * Test removing multiple items. + */ + @Test + public void testRemoval_multi() { + FakeSystemClock clock = new FakeSystemClock(); + clock.setAutoIncrement(false); + FakeExecutor fakeExecutor = new FakeExecutor(clock); + List<Runnable> removeFunctions = new ArrayList<>(); + RunnableImpl runnable = new RunnableImpl(); + + // Nothing to remove. + assertEquals(0, runnable.mRunCount); + assertEquals(0, fakeExecutor.numPending()); + + // Three pending items that have not yet run. + // We will try to remove the first and third items. + removeFunctions.add(fakeExecutor.executeDelayed(runnable, 100)); + fakeExecutor.executeDelayed(runnable, 200); + removeFunctions.add(fakeExecutor.executeDelayed(runnable, 300)); + assertEquals(3, fakeExecutor.numPending()); + assertEquals(0, runnable.mRunCount); + + // Remove the items. + removeFunctions.forEach(Runnable::run); + assertEquals(1, fakeExecutor.numPending()); + assertEquals(0, runnable.mRunCount); + + // One item to run. + fakeExecutor.advanceClockToLast(); + fakeExecutor.runAllReady(); + assertEquals(0, fakeExecutor.numPending()); + assertEquals(1, runnable.mRunCount); + + // Nothing to remove. + removeFunctions.forEach(Runnable::run); + assertEquals(0, fakeExecutor.numPending()); + assertEquals(1, runnable.mRunCount); + } + + /** + * Test removing everything + */ + @Test + public void testRemoval_all() { + FakeSystemClock clock = new FakeSystemClock(); + clock.setAutoIncrement(false); + FakeExecutor fakeExecutor = new FakeExecutor(clock); + RunnableImpl runnable = new RunnableImpl(); + + // Nothing to remove. + assertEquals(0, runnable.mRunCount); + assertEquals(0, fakeExecutor.numPending()); + + // Two pending items that have not yet run. + fakeExecutor.executeDelayed(runnable, 100); + fakeExecutor.executeDelayed(runnable, 200); + assertEquals(2, fakeExecutor.numPending()); + assertEquals(0, runnable.mRunCount); + + // Remove the items. + fakeExecutor.removeAll(); + + // Nothing to run + fakeExecutor.advanceClockToLast(); + assertEquals(0, fakeExecutor.runAllReady()); + assertEquals(0, fakeExecutor.numPending()); + assertEquals(0, runnable.mRunCount); + } + + private static class RunnableImpl implements Runnable { + int mRunCount; + + @Override + public void run() { + mRunCount++; + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/time/FakeSystemClock.java b/packages/SystemUI/tests/src/com/android/systemui/util/time/FakeSystemClock.java index 7b5417cd5c36..65e5902c84df 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/time/FakeSystemClock.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/time/FakeSystemClock.java @@ -16,6 +16,9 @@ package com.android.systemui.util.time; +import java.util.ArrayList; +import java.util.List; + public class FakeSystemClock implements SystemClock { private boolean mAutoIncrement = true; @@ -26,11 +29,13 @@ public class FakeSystemClock implements SystemClock { private long mCurrentThreadTimeMicro; private long mCurrentTimeMicro; + List<ClockTickListener> mListeners = new ArrayList<>(); + @Override public long uptimeMillis() { long value = mUptimeMillis; if (mAutoIncrement) { - mUptimeMillis++; + setUptimeMillis(mUptimeMillis + 1); } return value; } @@ -39,7 +44,7 @@ public class FakeSystemClock implements SystemClock { public long elapsedRealtime() { long value = mElapsedRealtime; if (mAutoIncrement) { - mElapsedRealtime++; + setElapsedRealtime(mElapsedRealtime + 1); } return value; } @@ -48,7 +53,7 @@ public class FakeSystemClock implements SystemClock { public long elapsedRealtimeNanos() { long value = mElapsedRealtimeNanos; if (mAutoIncrement) { - mElapsedRealtimeNanos++; + setElapsedRealtimeNanos(mElapsedRealtimeNanos + 1); } return value; } @@ -57,7 +62,7 @@ public class FakeSystemClock implements SystemClock { public long currentThreadTimeMillis() { long value = mCurrentThreadTimeMillis; if (mAutoIncrement) { - mCurrentThreadTimeMillis++; + setCurrentThreadTimeMillis(mCurrentThreadTimeMillis + 1); } return value; } @@ -66,7 +71,7 @@ public class FakeSystemClock implements SystemClock { public long currentThreadTimeMicro() { long value = mCurrentThreadTimeMicro; if (mAutoIncrement) { - mCurrentThreadTimeMicro++; + setCurrentThreadTimeMicro(mCurrentThreadTimeMicro + 1); } return value; } @@ -75,37 +80,90 @@ public class FakeSystemClock implements SystemClock { public long currentTimeMicro() { long value = mCurrentTimeMicro; if (mAutoIncrement) { - mCurrentTimeMicro++; + setCurrentTimeMicro(mCurrentTimeMicro + 1); } return value; } public void setUptimeMillis(long uptimeMillis) { mUptimeMillis = uptimeMillis; + for (ClockTickListener listener : mListeners) { + listener.onUptimeMillis(mUptimeMillis); + } } public void setElapsedRealtime(long elapsedRealtime) { mElapsedRealtime = elapsedRealtime; + for (ClockTickListener listener : mListeners) { + listener.onElapsedRealtime(mElapsedRealtime); + } } public void setElapsedRealtimeNanos(long elapsedRealtimeNanos) { mElapsedRealtimeNanos = elapsedRealtimeNanos; + for (ClockTickListener listener : mListeners) { + listener.onElapsedRealtimeNanos(mElapsedRealtimeNanos); + } } public void setCurrentThreadTimeMillis(long currentThreadTimeMillis) { mCurrentThreadTimeMillis = currentThreadTimeMillis; + for (ClockTickListener listener : mListeners) { + listener.onCurrentThreadTimeMillis(mCurrentThreadTimeMillis); + } } public void setCurrentThreadTimeMicro(long currentThreadTimeMicro) { mCurrentThreadTimeMicro = currentThreadTimeMicro; + for (ClockTickListener listener : mListeners) { + listener.onCurrentThreadTimeMicro(mCurrentThreadTimeMicro); + } } public void setCurrentTimeMicro(long currentTimeMicro) { mCurrentTimeMicro = currentTimeMicro; + for (ClockTickListener listener : mListeners) { + listener.onCurrentTimeMicro(mCurrentTimeMicro); + } } /** If true, each call to get____ will be one higher than the previous call to that method. */ public void setAutoIncrement(boolean autoIncrement) { mAutoIncrement = autoIncrement; } + + public void addListener(ClockTickListener listener) { + mListeners.add(listener); + } + + public void removeListener(ClockTickListener listener) { + mListeners.remove(listener); + } + + /** Alert all the listeners about the current time. */ + public void synchronizeListeners() { + for (ClockTickListener listener : mListeners) { + listener.onUptimeMillis(mUptimeMillis); + listener.onElapsedRealtime(mElapsedRealtime); + listener.onElapsedRealtimeNanos(mElapsedRealtimeNanos); + listener.onCurrentThreadTimeMillis(mCurrentThreadTimeMillis); + listener.onCurrentThreadTimeMicro(mCurrentThreadTimeMicro); + listener.onCurrentTimeMicro(mCurrentTimeMicro); + } + } + + + public interface ClockTickListener { + default void onUptimeMillis(long uptimeMillis) {} + + default void onElapsedRealtime(long elapsedRealtime) {} + + default void onElapsedRealtimeNanos(long elapsedRealtimeNanos) {} + + default void onCurrentThreadTimeMillis(long currentThreadTimeMillis) {} + + default void onCurrentThreadTimeMicro(long currentThreadTimeMicro) {} + + default void onCurrentTimeMicro(long currentTimeMicro) {} + } } diff --git a/services/Android.bp b/services/Android.bp index 041631b74380..3b566078bd51 100644 --- a/services/Android.bp +++ b/services/Android.bp @@ -96,3 +96,8 @@ platform_compat_config { name: "services-platform-compat-config", src: ":services", } + +filegroup { + name: "art-profile", + srcs: ["art-profile"], +} diff --git a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java index 19ac0d3c1024..17549268503e 100644 --- a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java +++ b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java @@ -17,6 +17,8 @@ package com.android.server.accessibility; import android.accessibilityservice.AccessibilityService; +import android.app.PendingIntent; +import android.app.RemoteAction; import android.app.StatusBarManager; import android.content.Context; import android.hardware.input.InputManager; @@ -25,81 +27,272 @@ import android.os.Handler; import android.os.Looper; import android.os.PowerManager; import android.os.SystemClock; +import android.util.ArrayMap; +import android.util.Slog; import android.view.InputDevice; import android.view.KeyCharacterMap; import android.view.KeyEvent; +import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; +import com.android.internal.R; +import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ScreenshotHelper; import com.android.server.LocalServices; import com.android.server.statusbar.StatusBarManagerInternal; import com.android.server.wm.WindowManagerInternal; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; import java.util.function.Supplier; /** - * Handle the back-end of AccessibilityService#performGlobalAction + * Handle the back-end of system AccessibilityAction. + * + * This class should support three use cases with combined usage of new API and legacy API: + * + * Use case 1: SystemUI doesn't use the new system action registration API. Accessibility + * service doesn't use the new system action API to obtain action list. Accessibility + * service uses legacy global action id to perform predefined system actions. + * Use case 2: SystemUI uses the new system action registration API to register available system + * actions. Accessibility service doesn't use the new system action API to obtain action + * list. Accessibility service uses legacy global action id to trigger the system + * actions registered by SystemUI. + * Use case 3: SystemUI doesn't use the new system action registration API.Accessibility service + * obtains the available system actions using new AccessibilityService API and trigger + * the predefined system actions. */ public class SystemActionPerformer { + private static final String TAG = "SystemActionPerformer"; + + interface SystemActionsChangedListener { + void onSystemActionsChanged(); + } + private final SystemActionsChangedListener mListener; + + private final Object mSystemActionLock = new Object(); + // Resource id based ActionId -> RemoteAction + @GuardedBy("mSystemActionLock") + private final Map<Integer, RemoteAction> mRegisteredSystemActions = new ArrayMap<>(); + + // Legacy system actions. + private final AccessibilityAction mLegacyHomeAction; + private final AccessibilityAction mLegacyBackAction; + private final AccessibilityAction mLegacyRecentsAction; + private final AccessibilityAction mLegacyNotificationsAction; + private final AccessibilityAction mLegacyQuickSettingsAction; + private final AccessibilityAction mLegacyPowerDialogAction; + private final AccessibilityAction mLegacyToggleSplitScreenAction; + private final AccessibilityAction mLegacyLockScreenAction; + private final AccessibilityAction mLegacyTakeScreenshotAction; + private final WindowManagerInternal mWindowManagerService; private final Context mContext; private Supplier<ScreenshotHelper> mScreenshotHelperSupplier; - public SystemActionPerformer(Context context, WindowManagerInternal windowManagerInternal) { - mContext = context; - mWindowManagerService = windowManagerInternal; - mScreenshotHelperSupplier = null; + public SystemActionPerformer( + Context context, + WindowManagerInternal windowManagerInternal) { + this(context, windowManagerInternal, null, null); } // Used to mock ScreenshotHelper @VisibleForTesting - public SystemActionPerformer(Context context, WindowManagerInternal windowManagerInternal, + public SystemActionPerformer( + Context context, + WindowManagerInternal windowManagerInternal, Supplier<ScreenshotHelper> screenshotHelperSupplier) { - this(context, windowManagerInternal); + this(context, windowManagerInternal, screenshotHelperSupplier, null); + } + + public SystemActionPerformer( + Context context, + WindowManagerInternal windowManagerInternal, + Supplier<ScreenshotHelper> screenshotHelperSupplier, + SystemActionsChangedListener listener) { + mContext = context; + mWindowManagerService = windowManagerInternal; + mListener = listener; mScreenshotHelperSupplier = screenshotHelperSupplier; + + mLegacyHomeAction = new AccessibilityAction( + AccessibilityService.GLOBAL_ACTION_HOME, + mContext.getResources().getString( + R.string.accessibility_system_action_home_label)); + mLegacyBackAction = new AccessibilityAction( + AccessibilityService.GLOBAL_ACTION_BACK, + mContext.getResources().getString( + R.string.accessibility_system_action_back_label)); + mLegacyRecentsAction = new AccessibilityAction( + AccessibilityService.GLOBAL_ACTION_RECENTS, + mContext.getResources().getString( + R.string.accessibility_system_action_recents_label)); + mLegacyNotificationsAction = new AccessibilityAction( + AccessibilityService.GLOBAL_ACTION_NOTIFICATIONS, + mContext.getResources().getString( + R.string.accessibility_system_action_notifications_label)); + mLegacyQuickSettingsAction = new AccessibilityAction( + AccessibilityService.GLOBAL_ACTION_QUICK_SETTINGS, + mContext.getResources().getString( + R.string.accessibility_system_action_quick_settings_label)); + mLegacyPowerDialogAction = new AccessibilityAction( + AccessibilityService.GLOBAL_ACTION_POWER_DIALOG, + mContext.getResources().getString( + R.string.accessibility_system_action_power_dialog_label)); + mLegacyToggleSplitScreenAction = new AccessibilityAction( + AccessibilityService.GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN, + mContext.getResources().getString( + R.string.accessibility_system_action_toggle_split_screen_label)); + mLegacyLockScreenAction = new AccessibilityAction( + AccessibilityService.GLOBAL_ACTION_LOCK_SCREEN, + mContext.getResources().getString( + R.string.accessibility_system_action_lock_screen_label)); + mLegacyTakeScreenshotAction = new AccessibilityAction( + AccessibilityService.GLOBAL_ACTION_TAKE_SCREENSHOT, + mContext.getResources().getString( + R.string.accessibility_system_action_screenshot_label)); + } + + /** + * This method is called to register a system action. If a system action is already registered + * with the given id, the existing system action will be overwritten. + */ + void registerSystemAction(int id, RemoteAction action) { + synchronized (mSystemActionLock) { + mRegisteredSystemActions.put(id, action); + } + if (mListener != null) { + mListener.onSystemActionsChanged(); + } + } + + /** + * This method is called to unregister a system action previously registered through + * registerSystemAction. + */ + void unregisterSystemAction(int id) { + synchronized (mSystemActionLock) { + mRegisteredSystemActions.remove(id); + } + if (mListener != null) { + mListener.onSystemActionsChanged(); + } } /** - * Performe the system action matching the given action id. + * This method returns the list of available system actions. */ - public boolean performSystemAction(int action) { + List<AccessibilityAction> getSystemActions() { + List<AccessibilityAction> systemActions = new ArrayList<>(); + synchronized (mSystemActionLock) { + for (Map.Entry<Integer, RemoteAction> entry : mRegisteredSystemActions.entrySet()) { + AccessibilityAction systemAction = new AccessibilityAction( + entry.getKey(), + entry.getValue().getTitle()); + systemActions.add(systemAction); + } + + // add AccessibilitySystemAction entry for legacy system actions if not overwritten + addLegacySystemActions(systemActions); + } + return systemActions; + } + + private void addLegacySystemActions(List<AccessibilityAction> systemActions) { + if (!mRegisteredSystemActions.containsKey(AccessibilityService.GLOBAL_ACTION_BACK)) { + systemActions.add(mLegacyBackAction); + } + if (!mRegisteredSystemActions.containsKey(AccessibilityService.GLOBAL_ACTION_HOME)) { + systemActions.add(mLegacyHomeAction); + } + if (!mRegisteredSystemActions.containsKey(AccessibilityService.GLOBAL_ACTION_RECENTS)) { + systemActions.add(mLegacyRecentsAction); + } + if (!mRegisteredSystemActions.containsKey( + AccessibilityService.GLOBAL_ACTION_NOTIFICATIONS)) { + systemActions.add(mLegacyNotificationsAction); + } + if (!mRegisteredSystemActions.containsKey( + AccessibilityService.GLOBAL_ACTION_QUICK_SETTINGS)) { + systemActions.add(mLegacyQuickSettingsAction); + } + if (!mRegisteredSystemActions.containsKey( + AccessibilityService.GLOBAL_ACTION_POWER_DIALOG)) { + systemActions.add(mLegacyPowerDialogAction); + } + if (!mRegisteredSystemActions.containsKey( + AccessibilityService.GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN)) { + systemActions.add(mLegacyToggleSplitScreenAction); + } + if (!mRegisteredSystemActions.containsKey( + AccessibilityService.GLOBAL_ACTION_LOCK_SCREEN)) { + systemActions.add(mLegacyLockScreenAction); + } + if (!mRegisteredSystemActions.containsKey( + AccessibilityService.GLOBAL_ACTION_TAKE_SCREENSHOT)) { + systemActions.add(mLegacyTakeScreenshotAction); + } + } + + /** + * Trigger the registered action by the matching action id. + */ + public boolean performSystemAction(int actionId) { final long identity = Binder.clearCallingIdentity(); try { - switch (action) { + synchronized (mSystemActionLock) { + // If a system action is registered with the given actionId, call the corresponding + // RemoteAction. + RemoteAction registeredAction = mRegisteredSystemActions.get(actionId); + if (registeredAction != null) { + try { + registeredAction.getActionIntent().send(); + return true; + } catch (PendingIntent.CanceledException ex) { + Slog.e(TAG, + "canceled PendingIntent for global action " + + registeredAction.getTitle(), + ex); + } + return false; + } + } + + // No RemoteAction registered with the given actionId, try the default legacy system + // actions. + switch (actionId) { case AccessibilityService.GLOBAL_ACTION_BACK: { sendDownAndUpKeyEvents(KeyEvent.KEYCODE_BACK); + return true; } - return true; case AccessibilityService.GLOBAL_ACTION_HOME: { sendDownAndUpKeyEvents(KeyEvent.KEYCODE_HOME); + return true; } - return true; - case AccessibilityService.GLOBAL_ACTION_RECENTS: { + case AccessibilityService.GLOBAL_ACTION_RECENTS: return openRecents(); - } case AccessibilityService.GLOBAL_ACTION_NOTIFICATIONS: { expandNotifications(); + return true; } - return true; case AccessibilityService.GLOBAL_ACTION_QUICK_SETTINGS: { expandQuickSettings(); + return true; } - return true; case AccessibilityService.GLOBAL_ACTION_POWER_DIALOG: { showGlobalActions(); + return true; } - return true; - case AccessibilityService.GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN: { + case AccessibilityService.GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN: return toggleSplitScreen(); - } - case AccessibilityService.GLOBAL_ACTION_LOCK_SCREEN: { + case AccessibilityService.GLOBAL_ACTION_LOCK_SCREEN: return lockScreen(); - } - case AccessibilityService.GLOBAL_ACTION_TAKE_SCREENSHOT: { + case AccessibilityService.GLOBAL_ACTION_TAKE_SCREENSHOT: return takeScreenshot(); - } + default: + return false; } - return false; } finally { Binder.restoreCallingIdentity(identity); } diff --git a/services/core/java/android/app/usage/UsageStatsManagerInternal.java b/services/core/java/android/app/usage/UsageStatsManagerInternal.java index 6641b5be651d..2f8c506d5ea7 100644 --- a/services/core/java/android/app/usage/UsageStatsManagerInternal.java +++ b/services/core/java/android/app/usage/UsageStatsManagerInternal.java @@ -281,4 +281,13 @@ public abstract class UsageStatsManagerInternal { return mUsageRemaining; } } + + /** + * Called by {@link com.android.server.usage.UsageStatsIdleService} when the device is idle to + * prune usage stats data for uninstalled packages. + * + * @param userId the user associated with the job + * @return {@code true} if the pruning was successful, {@code false} otherwise + */ + public abstract boolean pruneUninstalledPackagesData(@UserIdInt int userId); } diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 0ad275f9024b..753c1171aeb3 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -6464,20 +6464,77 @@ public class ConnectivityService extends IConnectivityManager.Stub newNetwork.name(), score, newNetwork.getCurrentScore())); } + // Notify requested networks are available after the default net is switched, but + // before LegacyTypeTracker sends legacy broadcasts + for (NetworkRequestInfo nri : addedRequests) notifyNetworkAvailable(newNetwork, nri); + // Second pass: process all listens. if (wasBackgroundNetwork != newNetwork.isBackgroundNetwork()) { - // If the network went from background to foreground or vice versa, we need to update - // its foreground state. It is safe to do this after rematching the requests because - // NET_CAPABILITY_FOREGROUND does not affect requests, as is not a requestable - // capability and does not affect the network's score (see the Slog.wtf call above). - updateCapabilities(score, newNetwork, newNetwork.networkCapabilities); + // TODO : most of the following is useless because the only thing that changed + // here is whether the network is a background network. Clean this up. + + NetworkCapabilities newNc = mixInCapabilities(newNetwork, + newNetwork.networkCapabilities); + + if (Objects.equals(newNetwork.networkCapabilities, newNc)) return; + + final int oldPermission = getNetworkPermission(newNetwork.networkCapabilities); + final int newPermission = getNetworkPermission(newNc); + if (oldPermission != newPermission && newNetwork.created && !newNetwork.isVPN()) { + try { + mNMS.setNetworkPermission(newNetwork.network.netId, newPermission); + } catch (RemoteException e) { + loge("Exception in setNetworkPermission: " + e); + } + } + + final NetworkCapabilities prevNc; + synchronized (newNetwork) { + prevNc = newNetwork.networkCapabilities; + newNetwork.setNetworkCapabilities(newNc); + } + + updateUids(newNetwork, prevNc, newNc); + + if (newNetwork.getCurrentScore() == score + && newNc.equalRequestableCapabilities(prevNc)) { + // If the requestable capabilities haven't changed, and the score hasn't changed, + // then the change we're processing can't affect any requests, it can only affect + // the listens on this network. + processListenRequests(newNetwork, true); + } else { + rematchAllNetworksAndRequests(); + notifyNetworkCallbacks(newNetwork, ConnectivityManager.CALLBACK_CAP_CHANGED); + } + + if (prevNc != null) { + final boolean oldMetered = prevNc.isMetered(); + final boolean newMetered = newNc.isMetered(); + final boolean meteredChanged = oldMetered != newMetered; + + if (meteredChanged) { + maybeNotifyNetworkBlocked(newNetwork, oldMetered, newMetered, + mRestrictBackground, mRestrictBackground); + } + + final boolean roamingChanged = prevNc.hasCapability(NET_CAPABILITY_NOT_ROAMING) + != newNc.hasCapability(NET_CAPABILITY_NOT_ROAMING); + + // Report changes that are interesting for network statistics tracking. + if (meteredChanged || roamingChanged) { + notifyIfacesChangedForNetworkStats(); + } + } + + if (!newNc.hasTransport(TRANSPORT_VPN)) { + // Tell VPNs about updated capabilities, since they may need to + // bubble those changes through. + updateAllVpnsCapabilities(); + } + } else { processListenRequests(newNetwork, false); } - - // do this after the default net is switched, but - // before LegacyTypeTracker sends legacy broadcasts - for (NetworkRequestInfo nri : addedRequests) notifyNetworkAvailable(newNetwork, nri); } /** diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java index e2fe76e94db3..12debbff3e90 100644 --- a/services/core/java/com/android/server/PackageWatchdog.java +++ b/services/core/java/com/android/server/PackageWatchdog.java @@ -42,6 +42,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.BackgroundThread; import com.android.internal.util.FastXmlSerializer; +import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.XmlUtils; import libcore.io.IoUtils; @@ -200,7 +201,8 @@ public class PackageWatchdog { } /** - * Registers {@code observer} to listen for package failures + * Registers {@code observer} to listen for package failures. Add a new ObserverInternal for + * this observer if it does not already exist. * * <p>Observers are expected to call this on boot. It does not specify any packages but * it will resume observing any packages requested from a previous boot. @@ -210,6 +212,11 @@ public class PackageWatchdog { ObserverInternal internalObserver = mAllObservers.get(observer.getName()); if (internalObserver != null) { internalObserver.registeredObserver = observer; + } else { + internalObserver = new ObserverInternal(observer.getName(), new ArrayList<>()); + internalObserver.registeredObserver = observer; + mAllObservers.put(observer.getName(), internalObserver); + syncState("added new observer"); } } } @@ -415,6 +422,14 @@ public class PackageWatchdog { * watchdog may drop observing packages with the old name. */ String getName(); + + /** + * An observer will not be pruned if this is set, even if the observer is not explicitly + * monitoring any packages. + */ + default boolean isPersistent() { + return false; + } } long getTriggerFailureCount() { @@ -626,7 +641,8 @@ public class PackageWatchdog { if (!failedPackages.isEmpty()) { onHealthCheckFailed(observer, failedPackages); } - if (observer.packages.isEmpty()) { + if (observer.packages.isEmpty() && (observer.registeredObserver == null + || !observer.registeredObserver.isPersistent())) { Slog.i(TAG, "Discarding observer " + observer.name + ". All packages expired"); it.remove(); } @@ -802,6 +818,21 @@ public class PackageWatchdog { } } + /** Dump status of every observer in mAllObservers. */ + public void dump(IndentingPrintWriter pw) { + pw.println("Package Watchdog status"); + pw.increaseIndent(); + synchronized (mLock) { + for (String observerName : mAllObservers.keySet()) { + pw.println("Observer name: " + observerName); + pw.increaseIndent(); + ObserverInternal observerInternal = mAllObservers.get(observerName); + observerInternal.dump(pw); + pw.decreaseIndent(); + } + } + } + /** * Represents an observer monitoring a set of packages along with the failure thresholds for * each package. @@ -944,6 +975,22 @@ public class PackageWatchdog { } return new ObserverInternal(observerName, packages); } + + /** Dumps information about this observer and the packages it watches. */ + public void dump(IndentingPrintWriter pw) { + boolean isPersistent = registeredObserver != null && registeredObserver.isPersistent(); + pw.println("Persistent: " + isPersistent); + for (String packageName : packages.keySet()) { + MonitoredPackage p = packages.get(packageName); + pw.println(packageName + ": "); + pw.increaseIndent(); + pw.println("# Failures: " + p.mFailureHistory.size()); + pw.println("Monitoring duration remaining: " + p.mDurationMs + "ms"); + pw.println("Explicit health check duration: " + p.mHealthCheckDurationMs + "ms"); + pw.println("Health check state: " + p.toString(p.mHealthCheckState)); + pw.decreaseIndent(); + } + } } @Retention(SOURCE) diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index 908ec6b844c2..7f1d5a33f56a 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -3065,7 +3065,7 @@ final class ActivityManagerShellCommand extends ShellCommand { pw.println(" even if in the background."); pw.println(" instrument [-r] [-e <NAME> <VALUE>] [-p <FILE>] [-w]"); pw.println(" [--user <USER_ID> | current]"); - pw.println(" [--no-hidden-api-checks [--no-test-api-checks]]"); + pw.println(" [--no-hidden-api-checks [--no-test-api-access]]"); pw.println(" [--no-isolated-storage]"); pw.println(" [--no-window-animation] [--abi <ABI>] <COMPONENT>"); pw.println(" Start an Instrumentation. Typically this target <COMPONENT> is in the"); @@ -3085,7 +3085,7 @@ final class ActivityManagerShellCommand extends ShellCommand { pw.println(" --user <USER_ID> | current: Specify user instrumentation runs in;"); pw.println(" current user if not specified."); pw.println(" --no-hidden-api-checks: disable restrictions on use of hidden API."); - pw.println(" --no-test-api-checks: disable restrictions to test APIs, if hidden"); + pw.println(" --no-test-api-access: do not allow access to test APIs, if hidden"); pw.println(" API checks are enabled."); pw.println(" --no-isolated-storage: don't use isolated storage sandbox and "); pw.println(" mount full external storage"); diff --git a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java index 638111e01d2b..38030c248139 100644 --- a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java +++ b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java @@ -15,22 +15,20 @@ */ package com.android.server.am; -import static android.net.wifi.WifiManager.WIFI_FEATURE_LINK_LAYER_STATS; - import android.annotation.Nullable; import android.bluetooth.BluetoothActivityEnergyInfo; import android.bluetooth.BluetoothAdapter; import android.content.Context; -import android.net.wifi.IWifiManager; -import android.net.wifi.WifiActivityEnergyInfo; +import android.net.wifi.WifiManager; import android.os.BatteryStats; +import android.os.Bundle; import android.os.Parcelable; import android.os.Process; -import android.os.RemoteException; import android.os.ServiceManager; import android.os.SynchronousResultReceiver; import android.os.SystemClock; import android.os.ThreadLocalWorkSource; +import android.os.connectivity.WifiActivityEnergyInfo; import android.telephony.ModemActivityInfo; import android.telephony.TelephonyManager; import android.util.IntArray; @@ -45,6 +43,7 @@ import com.android.internal.util.function.pooled.PooledLambda; import libcore.util.EmptyArray; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; @@ -117,7 +116,7 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { private final Object mWorkerLock = new Object(); @GuardedBy("mWorkerLock") - private IWifiManager mWifiManager = null; + private WifiManager mWifiManager = null; @GuardedBy("mWorkerLock") private TelephonyManager mTelephony = null; @@ -411,21 +410,33 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { if ((updateFlags & BatteryStatsImpl.ExternalStatsSync.UPDATE_WIFI) != 0) { // We were asked to fetch WiFi data. - if (mWifiManager == null) { - mWifiManager = IWifiManager.Stub.asInterface(ServiceManager.getService( - Context.WIFI_SERVICE)); + if (mWifiManager == null && ServiceManager.getService(Context.WIFI_SERVICE) != null) { + // this code is reached very early in the boot process, before Wifi Service has + // been registered. Check that ServiceManager.getService() returns a non null + // value before calling mContext.getSystemService(), since otherwise + // getSystemService() will throw a ServiceNotFoundException. + mWifiManager = mContext.getSystemService(WifiManager.class); } - if (mWifiManager != null) { - try { - // Only fetch WiFi power data if it is supported. - if ((mWifiManager.getSupportedFeatures() & WIFI_FEATURE_LINK_LAYER_STATS) != 0) { - wifiReceiver = new SynchronousResultReceiver("wifi"); - mWifiManager.requestActivityInfo(wifiReceiver); - } - } catch (RemoteException e) { - // Oh well. - } + // Only fetch WiFi power data if it is supported. + if (mWifiManager != null && mWifiManager.isEnhancedPowerReportingSupported()) { + SynchronousResultReceiver tempWifiReceiver = new SynchronousResultReceiver("wifi"); + mWifiManager.getWifiActivityEnergyInfoAsync( + new Executor() { + @Override + public void execute(Runnable runnable) { + // run the listener on the binder thread, if it was run on the main + // thread it would deadlock since we would be waiting on ourselves + runnable.run(); + } + }, + info -> { + Bundle bundle = new Bundle(); + bundle.putParcelable(BatteryStats.RESULT_RECEIVER_CONTROLLER_KEY, info); + tempWifiReceiver.send(0, bundle); + } + ); + wifiReceiver = tempWifiReceiver; } synchronized (mStats) { mStats.updateRailStatsLocked(); diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index 8c60d0c759dc..37026fd42f23 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -23,7 +23,6 @@ import android.content.ContentResolver; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; -import android.net.wifi.WifiActivityEnergyInfo; import android.os.BatteryStats; import android.os.BatteryStatsInternal; import android.os.Binder; @@ -43,6 +42,7 @@ import android.os.UserManagerInternal; import android.os.WorkSource; import android.os.connectivity.CellularBatteryStats; import android.os.connectivity.GpsBatteryStats; +import android.os.connectivity.WifiActivityEnergyInfo; import android.os.connectivity.WifiBatteryStats; import android.os.health.HealthStatsParceler; import android.os.health.HealthStatsWriter; diff --git a/services/core/java/com/android/server/compat/CompatChange.java b/services/core/java/com/android/server/compat/CompatChange.java index 95582f73035a..2eec4199217f 100644 --- a/services/core/java/com/android/server/compat/CompatChange.java +++ b/services/core/java/com/android/server/compat/CompatChange.java @@ -55,7 +55,7 @@ public final class CompatChange extends CompatibilityChangeInfo { private Map<String, Boolean> mPackageOverrides; public CompatChange(long changeId) { - this(changeId, null, -1, false); + this(changeId, null, -1, false, null); } /** @@ -66,8 +66,8 @@ public final class CompatChange extends CompatibilityChangeInfo { * @param disabled If {@code true}, overrides any {@code enableAfterTargetSdk} set. */ public CompatChange(long changeId, @Nullable String name, int enableAfterTargetSdk, - boolean disabled) { - super(changeId, name, enableAfterTargetSdk, disabled); + boolean disabled, String description) { + super(changeId, name, enableAfterTargetSdk, disabled, description); } /** @@ -75,7 +75,7 @@ public final class CompatChange extends CompatibilityChangeInfo { */ public CompatChange(Change change) { super(change.getId(), change.getName(), change.getEnableAfterTargetSdk(), - change.getDisabled()); + change.getDisabled(), change.getDescription()); } void registerListener(ChangeListener listener) { diff --git a/services/core/java/com/android/server/compat/CompatConfig.java b/services/core/java/com/android/server/compat/CompatConfig.java index 39c6e7552e3c..cf83dd630a29 100644 --- a/services/core/java/com/android/server/compat/CompatConfig.java +++ b/services/core/java/com/android/server/compat/CompatConfig.java @@ -319,7 +319,8 @@ final class CompatConfig { changeInfos[i] = new CompatibilityChangeInfo(change.getId(), change.getName(), change.getEnableAfterTargetSdk(), - change.getDisabled()); + change.getDisabled(), + change.getDescription()); } return changeInfos; } diff --git a/services/core/java/com/android/server/location/ContextHubClientBroker.java b/services/core/java/com/android/server/location/ContextHubClientBroker.java index 675e59e8f960..45d9bae23e26 100644 --- a/services/core/java/com/android/server/location/ContextHubClientBroker.java +++ b/services/core/java/com/android/server/location/ContextHubClientBroker.java @@ -29,10 +29,12 @@ import android.hardware.location.ContextHubTransaction; import android.hardware.location.IContextHubClient; import android.hardware.location.IContextHubClientCallback; import android.hardware.location.NanoAppMessage; +import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Supplier; /** @@ -97,6 +99,16 @@ public class ContextHubClientBroker extends IContextHubClient.Stub private final PendingIntentRequest mPendingIntentRequest; /* + * The host package associated with this client. + */ + private final String mPackage; + + /* + * True if a PendingIntent has been cancelled. + */ + private AtomicBoolean mIsPendingIntentCancelled = new AtomicBoolean(false); + + /* * Helper class to manage registered PendingIntent requests from the client. */ private class PendingIntentRequest { @@ -110,11 +122,14 @@ public class ContextHubClientBroker extends IContextHubClient.Stub */ private long mNanoAppId; + private boolean mValid = false; + PendingIntentRequest() {} PendingIntentRequest(PendingIntent pendingIntent, long nanoAppId) { mPendingIntent = pendingIntent; mNanoAppId = nanoAppId; + mValid = true; } public long getNanoAppId() { @@ -132,6 +147,10 @@ public class ContextHubClientBroker extends IContextHubClient.Stub public void clear() { mPendingIntent = null; } + + public boolean isValid() { + return mValid; + } } /* package */ ContextHubClientBroker( @@ -145,6 +164,7 @@ public class ContextHubClientBroker extends IContextHubClient.Stub mHostEndPointId = hostEndPointId; mCallbackInterface = callback; mPendingIntentRequest = new PendingIntentRequest(); + mPackage = mContext.getPackageManager().getNameForUid(Binder.getCallingUid()); } /* package */ ContextHubClientBroker( @@ -157,6 +177,7 @@ public class ContextHubClientBroker extends IContextHubClient.Stub mAttachedContextHubInfo = contextHubInfo; mHostEndPointId = hostEndPointId; mPendingIntentRequest = new PendingIntentRequest(pendingIntent, nanoAppId); + mPackage = pendingIntent.getCreatorPackage(); } /** @@ -313,6 +334,13 @@ public class ContextHubClientBroker extends IContextHubClient.Stub } /** + * @return true if the client is a PendingIntent client that has been cancelled. + */ + /* package */ boolean isPendingIntentCancelled() { + return mIsPendingIntentCancelled.get(); + } + + /** * Helper function to invoke a specified client callback, if the connection is open. * * @param consumer the consumer specifying the callback to invoke @@ -392,6 +420,7 @@ public class ContextHubClientBroker extends IContextHubClient.Stub Manifest.permission.LOCATION_HARDWARE /* requiredPermission */, null /* options */); } catch (PendingIntent.CanceledException e) { + mIsPendingIntentCancelled.set(true); // The PendingIntent is no longer valid Log.w(TAG, "PendingIntent has been canceled, unregistering from client" + " (host endpoint ID " + mHostEndPointId + ")"); @@ -419,4 +448,20 @@ public class ContextHubClientBroker extends IContextHubClient.Stub mRegistered = false; } } + + @Override + public String toString() { + String out = "[ContextHubClient "; + out += "endpointID: " + getHostEndPointId() + ", "; + out += "contextHub: " + getAttachedContextHubId() + ", "; + if (mPendingIntentRequest.isValid()) { + out += "intentCreatorPackage: " + mPackage + ", "; + out += "nanoAppId: 0x" + Long.toHexString(mPendingIntentRequest.getNanoAppId()); + } else { + out += "package: " + mPackage; + } + out += "]"; + + return out; + } } diff --git a/services/core/java/com/android/server/location/ContextHubClientManager.java b/services/core/java/com/android/server/location/ContextHubClientManager.java index 00b7d62738c0..46db8dc5dd77 100644 --- a/services/core/java/com/android/server/location/ContextHubClientManager.java +++ b/services/core/java/com/android/server/location/ContextHubClientManager.java @@ -16,6 +16,7 @@ package com.android.server.location; +import android.annotation.IntDef; import android.app.PendingIntent; import android.content.Context; import android.hardware.contexthub.V1_0.ContextHubMsg; @@ -27,7 +28,12 @@ import android.hardware.location.NanoAppMessage; import android.os.RemoteException; import android.util.Log; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Calendar; +import java.util.Iterator; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedDeque; import java.util.function.Consumer; /** @@ -71,6 +77,79 @@ import java.util.function.Consumer; */ private int mNextHostEndPointId = 0; + /* + * The list of previous registration records. + */ + private static final int NUM_CLIENT_RECORDS = 20; + private final ConcurrentLinkedEvictingDeque<RegistrationRecord> mRegistrationRecordDeque = + new ConcurrentLinkedEvictingDeque<>(NUM_CLIENT_RECORDS); + + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "ACTION_" }, value = { + ACTION_REGISTERED, + ACTION_UNREGISTERED, + ACTION_CANCELLED, + }) + public @interface Action {} + public static final int ACTION_REGISTERED = 0; + public static final int ACTION_UNREGISTERED = 1; + public static final int ACTION_CANCELLED = 2; + + /** + * Helper class to make a ConcurrentLinkedDeque fixed-size, evicting old entries when full. + */ + private class ConcurrentLinkedEvictingDeque<E> extends ConcurrentLinkedDeque<E> { + private int mSize; + + ConcurrentLinkedEvictingDeque(int size) { + mSize = size; + } + + @Override + public boolean add(E elem) { + synchronized (this) { + if (size() == mSize) { + poll(); + } + + return super.add(elem); + } + } + } + + /** + * A container class to store a record of ContextHubClient registration. + */ + private class RegistrationRecord { + private final String mBroker; + private final int mAction; + private final String mDate; + + RegistrationRecord(String broker, @Action int action) { + mBroker = broker; + mAction = action; + Calendar instance = Calendar.getInstance(); + mDate = String.format("%02d", instance.get(Calendar.MONTH) + 1) // Jan == 0 + + "/" + String.format("%02d", instance.get(Calendar.DAY_OF_MONTH)) + + " " + String.format("%02d", instance.get(Calendar.HOUR_OF_DAY)) + + ":" + String.format("%02d", instance.get(Calendar.MINUTE)) + + ":" + String.format("%02d", instance.get(Calendar.SECOND)) + + "." + String.format("%03d", instance.get(Calendar.MILLISECOND)); + } + + @Override + public String toString() { + String out = ""; + out += mDate + " "; + out += mAction == ACTION_REGISTERED ? "+ " : "- "; + out += mBroker; + if (mAction == ACTION_CANCELLED) { + out += " (cancelled)"; + } + return out; + } + } + /* package */ ContextHubClientManager( Context context, IContexthub contextHubProxy) { mContext = context; @@ -96,6 +175,8 @@ import java.util.function.Consumer; mContext, mContextHubProxy, this /* clientManager */, contextHubInfo, hostEndPointId, clientCallback); mHostEndPointIdToClientMap.put(hostEndPointId, broker); + mRegistrationRecordDeque.add( + new RegistrationRecord(broker.toString(), ACTION_REGISTERED)); } try { @@ -136,6 +217,8 @@ import java.util.function.Consumer; hostEndPointId, pendingIntent, nanoAppId); mHostEndPointIdToClientMap.put(hostEndPointId, broker); registerString = "Registered"; + mRegistrationRecordDeque.add( + new RegistrationRecord(broker.toString(), ACTION_REGISTERED)); } } @@ -178,6 +261,13 @@ import java.util.function.Consumer; * @param hostEndPointId the host endpoint ID of the client that has died */ /* package */ void unregisterClient(short hostEndPointId) { + ContextHubClientBroker broker = mHostEndPointIdToClientMap.get(hostEndPointId); + if (broker != null) { + @Action int action = + broker.isPendingIntentCancelled() ? ACTION_CANCELLED : ACTION_UNREGISTERED; + mRegistrationRecordDeque.add(new RegistrationRecord(broker.toString(), action)); + } + if (mHostEndPointIdToClientMap.remove(hostEndPointId) != null) { Log.d(TAG, "Unregistered client with host endpoint ID " + hostEndPointId); } else { @@ -285,4 +375,20 @@ import java.util.function.Consumer; return null; } + + @Override + public String toString() { + String out = ""; + for (ContextHubClientBroker broker : mHostEndPointIdToClientMap.values()) { + out += broker + "\n"; + } + + out += "\nRegistration history:\n"; + Iterator<RegistrationRecord> it = mRegistrationRecordDeque.descendingIterator(); + while (it.hasNext()) { + out += it.next() + "\n"; + } + + return out; + } } diff --git a/services/core/java/com/android/server/location/ContextHubService.java b/services/core/java/com/android/server/location/ContextHubService.java index 36b0342c21d8..787a8007920d 100644 --- a/services/core/java/com/android/server/location/ContextHubService.java +++ b/services/core/java/com/android/server/location/ContextHubService.java @@ -795,6 +795,10 @@ public class ContextHubService extends IContextHubService.Stub { // Dump nanoAppHash mNanoAppStateManager.foreachNanoAppInstanceInfo((info) -> pw.println(info)); + pw.println(""); + pw.println("=================== CLIENTS ===================="); + pw.println(mClientManager); + // dump eventLog } diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java index ec11a97cfc30..14ef2d3a38e1 100644 --- a/services/core/java/com/android/server/pm/AppsFilter.java +++ b/services/core/java/com/android/server/pm/AppsFilter.java @@ -136,8 +136,11 @@ public class AppsFilter { DeviceConfig.addOnPropertiesChangedListener( NAMESPACE_PACKAGE_MANAGER_SERVICE, FgThread.getExecutor(), properties -> { - synchronized (FeatureConfigImpl.this) { - mFeatureEnabled = properties.getBoolean(FILTERING_ENABLED_NAME, false); + if (properties.getKeyset().contains(FILTERING_ENABLED_NAME)) { + synchronized (FeatureConfigImpl.this) { + mFeatureEnabled = properties.getBoolean(FILTERING_ENABLED_NAME, + false); + } } }); } diff --git a/services/core/java/com/android/server/pm/DataLoaderManagerService.java b/services/core/java/com/android/server/pm/DataLoaderManagerService.java new file mode 100644 index 000000000000..0719797ed85a --- /dev/null +++ b/services/core/java/com/android/server/pm/DataLoaderManagerService.java @@ -0,0 +1,246 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm; + +import android.annotation.Nullable; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.pm.ApplicationInfo; +import android.content.pm.IDataLoader; +import android.content.pm.IDataLoaderManager; +import android.content.pm.IDataLoaderStatusListener; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.UserHandle; +import android.util.Slog; +import android.util.SparseArray; + +import com.android.internal.annotations.GuardedBy; +import com.android.server.SystemService; + +import java.util.List; + +/** + * Data loader manager service manages data loader binder services. + * + * @hide + */ +public class DataLoaderManagerService extends SystemService { + private static final String TAG = "DataLoaderManager"; + private final Context mContext; + private final DataLoaderManagerBinderService mBinderService; + private final Object mLock = new Object(); + @GuardedBy("mLock") + private SparseArray<DataLoaderServiceConnection> mServiceConnections; + + public DataLoaderManagerService(Context context) { + super(context); + mContext = context; + mBinderService = new DataLoaderManagerBinderService(); + } + + @Override + public void onStart() { + publishBinderService(Context.DATA_LOADER_MANAGER_SERVICE, mBinderService); + } + + final class DataLoaderManagerBinderService extends IDataLoaderManager.Stub { + @Override + public boolean initializeDataLoader(int dataLoaderId, Bundle params, + IDataLoaderStatusListener listener) { + synchronized (mLock) { + if (mServiceConnections == null) { + mServiceConnections = new SparseArray<>(); + } + if (mServiceConnections.get(dataLoaderId) != null) { + Slog.e(TAG, "Data loader of ID=" + dataLoaderId + " already exists."); + return false; + } + } + CharSequence packageNameSeq = params.getCharSequence("packageName"); + if (packageNameSeq == null) { + Slog.e(TAG, "Must specify package name."); + return false; + } + String packageName = packageNameSeq.toString(); + ComponentName dataLoaderComponent = getDataLoaderServiceName(packageName); + if (dataLoaderComponent == null) { + return false; + } + // Binds to the specific data loader service + DataLoaderServiceConnection connection = + new DataLoaderServiceConnection(dataLoaderId, params, listener); + Intent intent = new Intent(); + intent.setComponent(dataLoaderComponent); + if (!mContext.bindServiceAsUser(intent, connection, Context.BIND_AUTO_CREATE, + UserHandle.of(UserHandle.getCallingUserId()))) { + Slog.e(TAG, "Failed to bind to data loader binder service."); + mContext.unbindService(connection); + return false; + } + return true; + } + + /** + * Find the ComponentName of the data loader service provider, given its package name. + * + * @param packageName the package name of the provider. + * @return ComponentName of the data loader service provider. Null if provider not found. + */ + private @Nullable ComponentName getDataLoaderServiceName(String packageName) { + final PackageManager pm = mContext.getPackageManager(); + if (pm == null) { + Slog.e(TAG, "PackageManager is not available."); + return null; + } + Intent intent = new Intent(Intent.ACTION_LOAD_DATA); + intent.setPackage(packageName); + List<ResolveInfo> services = + pm.queryIntentServicesAsUser(intent, 0, UserHandle.getCallingUserId()); + if (services == null || services.isEmpty()) { + Slog.e(TAG, + "Failed to find data loader service provider in package " + packageName); + return null; + } + + // TODO(b/136132412): better way to enable privileged data loaders in tests + boolean checkLoader = + android.os.SystemProperties.getBoolean("incremental.check_loader", false); + int numServices = services.size(); + for (int i = 0; i < numServices; i++) { + ResolveInfo ri = services.get(i); + ComponentName componentName = new ComponentName( + ri.serviceInfo.packageName, ri.serviceInfo.name); + // There should only be one matching provider inside the given package. + // If there's more than one, return the first one found. + try { + ApplicationInfo ai = pm.getApplicationInfo(componentName.getPackageName(), 0); + if (checkLoader && !ai.isPrivilegedApp()) { + Slog.w(TAG, + "Data loader: " + componentName.getPackageName() + + " is not a privileged app, skipping."); + continue; + } + return componentName; + } catch (PackageManager.NameNotFoundException ex) { + Slog.w(TAG, + "Privileged data loader: " + componentName.getPackageName() + + " not found, skipping."); + } + + } + Slog.e(TAG, "Didn't find any matching data loader service provider."); + return null; + } + + /** + * Returns the binder object of a data loader, specified by its ID. + */ + @Override + public @Nullable IDataLoader getDataLoader(int dataLoaderId) { + synchronized (mLock) { + if (mServiceConnections == null) { + return null; + } + DataLoaderServiceConnection serviceConnection = mServiceConnections.get( + dataLoaderId, null); + if (serviceConnection == null) { + return null; + } + return serviceConnection.getDataLoader(); + } + } + + /** + * Destroys a data loader binder service, specified by its ID. + */ + @Override + public void destroyDataLoader(int dataLoaderId) { + synchronized (mLock) { + if (mServiceConnections == null) { + return; + } + DataLoaderServiceConnection serviceConnection = mServiceConnections.get( + dataLoaderId, null); + + if (serviceConnection == null) { + return; + } + serviceConnection.destroy(); + } + } + } + + class DataLoaderServiceConnection implements ServiceConnection { + final int mId; + final Bundle mParams; + final IDataLoaderStatusListener mListener; + IDataLoader mDataLoader; + + DataLoaderServiceConnection(int id, Bundle params, IDataLoaderStatusListener listener) { + mId = id; + mParams = params; + mListener = listener; + mDataLoader = null; + } + + @Override + public void onServiceConnected(ComponentName className, IBinder service) { + mDataLoader = IDataLoader.Stub.asInterface(service); + synchronized (mLock) { + mServiceConnections.append(mId, this); + } + try { + mDataLoader.create(mId, mParams, mListener); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to create data loader service.", e); + } + } + + @Override + public void onServiceDisconnected(ComponentName arg0) { + remove(); + } + + IDataLoader getDataLoader() { + return mDataLoader; + } + + void destroy() { + try { + mDataLoader.destroy(); + } catch (RemoteException ignored) { + } + mContext.unbindService(this); + } + + private void remove() { + synchronized (mLock) { + mServiceConnections.remove(mId); + if (mServiceConnections.size() == 0) { + mServiceConnections = null; + } + } + mParams.clear(); + } + } +} diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 4852947619f9..883baf7886af 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -54,6 +54,7 @@ import android.content.IntentSender; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageInstallObserver2; import android.content.pm.IPackageInstallerSession; +import android.content.pm.InstallationFile; import android.content.pm.PackageInfo; import android.content.pm.PackageInstaller; import android.content.pm.PackageInstaller.SessionInfo; @@ -81,6 +82,8 @@ import android.os.Process; import android.os.RevocableFileDescriptor; import android.os.SystemProperties; import android.os.UserHandle; +import android.os.incremental.IncrementalFileStorages; +import android.os.incremental.IncrementalManager; import android.os.storage.StorageManager; import android.stats.devicepolicy.DevicePolicyEnums; import android.system.ErrnoException; @@ -309,6 +312,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { @GuardedBy("mLock") private boolean mVerityFound; + private IncrementalFileStorages mIncrementalFileStorages; + private static final FileFilter sAddedFilter = new FileFilter() { @Override public boolean accept(File file) { @@ -471,6 +476,18 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { mStagedSessionErrorCode = stagedSessionErrorCode; mStagedSessionErrorMessage = stagedSessionErrorMessage != null ? stagedSessionErrorMessage : ""; + + // TODO(b/136132412): sanity check if session should not be incremental + if (!params.isStaged && params.incrementalParams != null + && !params.incrementalParams.getPackageName().isEmpty()) { + IncrementalManager incrementalManager = (IncrementalManager) mContext.getSystemService( + Context.INCREMENTAL_SERVICE); + if (incrementalManager != null) { + mIncrementalFileStorages = + new IncrementalFileStorages(mPackageName, stageDir, incrementalManager, + params.incrementalParams); + } + } } public SessionInfo generateInfo() { @@ -874,10 +891,18 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } + if (mIncrementalFileStorages != null) { + mIncrementalFileStorages.finishSetUp(); + } + mHandler.obtainMessage(MSG_SEAL, statusReceiver).sendToTarget(); } private void handleSeal(@NonNull IntentSender statusReceiver) { + // TODO(b/136132412): update with new APIs + if (mIncrementalFileStorages != null) { + mIncrementalFileStorages.startLoading(); + } if (!markAsCommitted(statusReceiver)) { return; } @@ -1492,6 +1517,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { computeProgressLocked(true); // Unpack native libraries + // TODO(b/136132412): skip for incremental installation extractNativeLibraries(stageDir, params.abiOverride, mayInheritNativeLibs()); } @@ -2182,7 +2208,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } destroyInternal(); } - dispatchSessionFinished(INSTALL_FAILED_ABORTED, "Session was abandoned", null); } @@ -2268,6 +2293,20 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { return mParentSessionId; } + @Override + public void addFile(@NonNull String name, long size, @NonNull byte[] metadata) { + if (mIncrementalFileStorages == null) { + throw new IllegalStateException( + "Cannot add Incremental File to a non-Incremental session."); + } + try { + mIncrementalFileStorages.addFile(new InstallationFile(name, size, metadata)); + } catch (IOException ex) { + throw new IllegalStateException( + "Failed to add and configure Incremental File: " + name, ex); + } + } + private void dispatchSessionFinished(int returnCode, String msg, Bundle extras) { final IntentSender statusReceiver; final String packageName; @@ -2390,6 +2429,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { // since these packages are supposed to be read on reboot. // Those dirs are deleted when the staged session has reached a final state. if (stageDir != null && !params.isStaged) { + if (mIncrementalFileStorages != null) { + mIncrementalFileStorages.cleanUp(); + } try { mPm.mInstaller.rmPackageDir(stageDir.getAbsolutePath()); } catch (InstallerException ignored) { @@ -2403,6 +2445,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { mSessionProvider.getSession(childSessionId).cleanStageDir(); } } else { + if (mIncrementalFileStorages != null) { + mIncrementalFileStorages.cleanUp(); + } try { mPm.mInstaller.rmPackageDir(stageDir.getAbsolutePath()); } catch (InstallerException ignored) { diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 4e0e4ffb3a13..c5e79426a421 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -153,6 +153,7 @@ import android.content.pm.IPackageManager; import android.content.pm.IPackageManagerNative; import android.content.pm.IPackageMoveObserver; import android.content.pm.IPackageStatsObserver; +import android.content.pm.InstallSourceInfo; import android.content.pm.InstantAppInfo; import android.content.pm.InstantAppRequest; import android.content.pm.InstrumentationInfo; @@ -20128,17 +20129,93 @@ public class PackageManagerService extends IPackageManager.Stub public String getInstallerPackageName(String packageName) { final int callingUid = Binder.getCallingUid(); synchronized (mLock) { - final PackageSetting ps = mSettings.mPackages.get(packageName); - if (shouldFilterApplicationLocked( - ps, callingUid, UserHandle.getUserId(callingUid))) { - return null; + final InstallSource installSource = getInstallSourceLocked(packageName, callingUid); + if (installSource == null) { + throw new IllegalArgumentException("Unknown package: " + packageName); } - // InstallerPackageName for Apex is not stored in PackageManager - if (ps == null && mApexManager.isApexPackage(packageName)) { + String installerPackageName = installSource.installerPackageName; + if (installerPackageName != null) { + final PackageSetting ps = mSettings.mPackages.get(installerPackageName); + if (ps == null || shouldFilterApplicationLocked(ps, callingUid, + UserHandle.getUserId(callingUid))) { + installerPackageName = null; + } + } + return installerPackageName; + } + } + + @Override + @Nullable + public InstallSourceInfo getInstallSourceInfo(String packageName) { + final int callingUid = Binder.getCallingUid(); + final int userId = UserHandle.getUserId(callingUid); + + String installerPackageName; + String initiatingPackageName; + String originatingPackageName; + + synchronized (mLock) { + final InstallSource installSource = getInstallSourceLocked(packageName, callingUid); + if (installSource == null) { return null; } - return mSettings.getInstallerPackageNameLPr(packageName); + + installerPackageName = installSource.installerPackageName; + if (installerPackageName != null) { + final PackageSetting ps = mSettings.mPackages.get(installerPackageName); + if (ps == null || shouldFilterApplicationLocked(ps, callingUid, userId)) { + installerPackageName = null; + } + } + + // All installSource strings are interned, so == is ok here + if (installSource.initiatingPackageName == installSource.installerPackageName) { + // The installer and initiator will often be the same, and when they are + // we can skip doing the same check again. + initiatingPackageName = installerPackageName; + } else { + initiatingPackageName = installSource.initiatingPackageName; + final PackageSetting ps = mSettings.mPackages.get(initiatingPackageName); + if (ps == null || shouldFilterApplicationLocked(ps, callingUid, userId)) { + initiatingPackageName = null; + } + + } + originatingPackageName = installSource.originatingPackageName; + if (originatingPackageName != null) { + final PackageSetting ps = mSettings.mPackages.get(originatingPackageName); + if (ps == null || shouldFilterApplicationLocked(ps, callingUid, userId)) { + originatingPackageName = null; + } + } + } + + if (originatingPackageName != null && mContext.checkCallingOrSelfPermission( + Manifest.permission.INSTALL_PACKAGES) != PackageManager.PERMISSION_GRANTED) { + originatingPackageName = null; } + + return new InstallSourceInfo(initiatingPackageName, originatingPackageName, + installerPackageName); + } + + @GuardedBy("mLock") + @Nullable + private InstallSource getInstallSourceLocked(String packageName, int callingUid) { + final PackageSetting ps = mSettings.mPackages.get(packageName); + + // Installer info for Apex is not stored in PackageManager + if (ps == null && mApexManager.isApexPackage(packageName)) { + return InstallSource.EMPTY; + } + + if (ps == null || shouldFilterApplicationLocked(ps, callingUid, + UserHandle.getUserId(callingUid))) { + return null; + } + + return ps.installSource; } public boolean isOrphaned(String packageName) { diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index 1b73602a4790..b96109683115 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -1854,22 +1854,23 @@ class PackageManagerShellCommand extends ShellCommand { if (internal.isApexPackage(packageName)) { internal.uninstallApex( packageName, versionCode, translatedUserId, receiver.getIntentSender(), flags); - } else if ((flags & PackageManager.DELETE_ALL_USERS) != 0) { - final PackageInfo info = mInterface.getPackageInfo(packageName, - PackageManager.MATCH_STATIC_SHARED_LIBRARIES, translatedUserId); - if (info == null) { - pw.println("Failure [not installed for " + translatedUserId + "]"); - return 1; - } - final boolean isSystem = - (info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0; - // If we are being asked to delete a system app for just one - // user set flag so it disables rather than reverting to system - // version of the app. - if (isSystem) { - flags |= PackageManager.DELETE_SYSTEM_APP; + } else { + if ((flags & PackageManager.DELETE_ALL_USERS) == 0) { + final PackageInfo info = mInterface.getPackageInfo(packageName, + PackageManager.MATCH_STATIC_SHARED_LIBRARIES, translatedUserId); + if (info == null) { + pw.println("Failure [not installed for " + translatedUserId + "]"); + return 1; + } + final boolean isSystem = + (info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0; + // If we are being asked to delete a system app for just one + // user set flag so it disables rather than reverting to system + // version of the app. + if (isSystem) { + flags |= PackageManager.DELETE_SYSTEM_APP; + } } - mInterface.getPackageInstaller().uninstall(new VersionedPackage(packageName, versionCode), null /*callerPackageName*/, flags, receiver.getIntentSender(), translatedUserId); diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index f67d3c0f7523..6e67687ae6d7 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -4257,14 +4257,6 @@ public final class Settings { return userState.isMatch(componentInfo, flags); } - String getInstallerPackageNameLPr(String packageName) { - final PackageSetting pkg = mPackages.get(packageName); - if (pkg == null) { - throw new IllegalArgumentException("Unknown package: " + packageName); - } - return pkg.installSource.installerPackageName; - } - boolean isOrphaned(String packageName) { final PackageSetting pkg = mPackages.get(packageName); if (pkg == null) { diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 14cbe758cd6a..ad4411c0da4d 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -3452,8 +3452,13 @@ public class UserManagerService extends IUserManager.Stub { /** * Find the current guest user. If the Guest user is partial, * then do not include it in the results as it is about to die. + * + * @return The current guest user. Null if it doesn't exist. + * @hide */ - private UserInfo findCurrentGuestUser() { + @Override + public UserInfo findCurrentGuestUser() { + checkManageUsersPermission("findCurrentGuestUser"); synchronized (mUsersLock) { final int size = mUsers.size(); for (int i = 0; i < size; i++) { diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 114a16a96f07..14617d38739e 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -203,8 +203,11 @@ public final class PowerManagerService extends SystemService // System Property indicating that retail demo mode is currently enabled. private static final String SYSTEM_PROPERTY_RETAIL_DEMO_ENABLED = "sys.retaildemo.enabled"; - // Possible reasons for shutting down or reboot for use in REBOOT_PROPERTY(sys.boot.reason) - // which is set by bootstat + // System property for last reboot reason + private static final String SYSTEM_PROPERTY_REBOOT_REASON = "sys.boot.reason"; + + // Possible reasons for shutting down or reboot for use in + // SYSTEM_PROPERTY_REBOOT_REASON(sys.boot.reason) which is set by bootstat private static final String REASON_SHUTDOWN = "shutdown"; private static final String REASON_REBOOT = "reboot"; private static final String REASON_USERREQUESTED = "shutdown,userrequested"; @@ -225,9 +228,6 @@ public final class PowerManagerService extends SystemService private static final int HALT_MODE_REBOOT = 1; private static final int HALT_MODE_REBOOT_SAFE_MODE = 2; - // property for last reboot reason - private static final String REBOOT_PROPERTY = "sys.boot.reason"; - private final Context mContext; private final ServiceThread mHandlerThread; private final PowerManagerHandler mHandler; @@ -240,6 +240,7 @@ public final class PowerManagerService extends SystemService private final BinderService mBinderService; private final LocalService mLocalService; private final NativeWrapper mNativeWrapper; + private final SystemPropertiesWrapper mSystemProperties; private final Injector mInjector; private LightsManager mLightsManager; @@ -756,6 +757,20 @@ public final class PowerManagerService extends SystemService InattentiveSleepWarningController createInattentiveSleepWarningController() { return new InattentiveSleepWarningController(); } + + public SystemPropertiesWrapper createSystemPropertiesWrapper() { + return new SystemPropertiesWrapper() { + @Override + public String get(String key, String def) { + return SystemProperties.get(key, def); + } + + @Override + public void set(String key, String val) { + SystemProperties.set(key, val); + } + }; + } } final Constants mConstants; @@ -781,6 +796,7 @@ public final class PowerManagerService extends SystemService mBinderService = new BinderService(); mLocalService = new LocalService(); mNativeWrapper = injector.createNativeWrapper(); + mSystemProperties = injector.createSystemPropertiesWrapper(); mInjector = injector; mHandlerThread = new ServiceThread(TAG, @@ -816,7 +832,7 @@ public final class PowerManagerService extends SystemService mHalInteractiveModeEnabled = true; mWakefulness = WAKEFULNESS_AWAKE; - sQuiescent = SystemProperties.get(SYSTEM_PROPERTY_QUIESCENT, "0").equals("1"); + sQuiescent = mSystemProperties.get(SYSTEM_PROPERTY_QUIESCENT, "0").equals("1"); mNativeWrapper.nativeInit(this); mNativeWrapper.nativeSetAutoSuspend(false); @@ -1067,8 +1083,9 @@ public final class PowerManagerService extends SystemService } final String retailDemoValue = UserManager.isDeviceInDemoMode(mContext) ? "1" : "0"; - if (!retailDemoValue.equals(SystemProperties.get(SYSTEM_PROPERTY_RETAIL_DEMO_ENABLED))) { - SystemProperties.set(SYSTEM_PROPERTY_RETAIL_DEMO_ENABLED, retailDemoValue); + if (!retailDemoValue.equals( + mSystemProperties.get(SYSTEM_PROPERTY_RETAIL_DEMO_ENABLED, null))) { + mSystemProperties.set(SYSTEM_PROPERTY_RETAIL_DEMO_ENABLED, retailDemoValue); } mScreenBrightnessModeSetting = Settings.System.getIntForUser(resolver, @@ -4820,7 +4837,7 @@ public final class PowerManagerService extends SystemService final long ident = Binder.clearCallingIdentity(); try { - return getLastShutdownReasonInternal(REBOOT_PROPERTY); + return getLastShutdownReasonInternal(); } finally { Binder.restoreCallingIdentity(ident); } @@ -5054,12 +5071,8 @@ public final class PowerManagerService extends SystemService } @VisibleForTesting - // lastRebootReasonProperty argument to permit testing - int getLastShutdownReasonInternal(String lastRebootReasonProperty) { - String line = SystemProperties.get(lastRebootReasonProperty); - if (line == null) { - return PowerManager.SHUTDOWN_REASON_UNKNOWN; - } + int getLastShutdownReasonInternal() { + String line = mSystemProperties.get(SYSTEM_PROPERTY_REBOOT_REASON, null); switch (line) { case REASON_SHUTDOWN: return PowerManager.SHUTDOWN_REASON_SHUTDOWN; diff --git a/services/core/java/com/android/server/power/SystemPropertiesWrapper.java b/services/core/java/com/android/server/power/SystemPropertiesWrapper.java new file mode 100644 index 000000000000..1acf798eb099 --- /dev/null +++ b/services/core/java/com/android/server/power/SystemPropertiesWrapper.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.power; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.SystemProperties; + +import com.android.internal.annotations.VisibleForTesting; + +/** + * Wrapper interface to access {@link SystemProperties}. + * + * @hide + */ +@VisibleForTesting +interface SystemPropertiesWrapper { + /** + * Get the String value for the given {@code key}. + * + * @param key the key to lookup + * @param def the default value in case the property is not set or empty + * @return if the {@code key} isn't found, return {@code def} if it isn't null, or an empty + * string otherwise + */ + @NonNull + String get(@NonNull String key, @Nullable String def); + + /** + * Set the value for the given {@code key} to {@code val}. + * + * @throws IllegalArgumentException if the {@code val} exceeds 91 characters + * @throws RuntimeException if the property cannot be set, for example, if it was blocked by + * SELinux. libc will log the underlying reason. + */ + void set(@NonNull String key, @Nullable String val); +} diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java index 00779ec6e45c..3f5e2a447d28 100644 --- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java +++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java @@ -57,6 +57,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.util.DumpUtils; import com.android.internal.util.IndentingPrintWriter; import com.android.server.LocalServices; +import com.android.server.PackageWatchdog; import com.android.server.Watchdog; import com.android.server.pm.Installer; @@ -1200,6 +1201,8 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { for (Rollback rollback : mRollbacks) { rollback.dump(ipw); } + ipw.println(); + PackageWatchdog.getInstance(mContext).dump(ipw); } } diff --git a/services/core/java/com/android/server/utils/TimingsTraceAndSlog.java b/services/core/java/com/android/server/utils/TimingsTraceAndSlog.java index 03c96e99a5ad..6bdb5cea7f9c 100644 --- a/services/core/java/com/android/server/utils/TimingsTraceAndSlog.java +++ b/services/core/java/com/android/server/utils/TimingsTraceAndSlog.java @@ -55,7 +55,16 @@ public final class TimingsTraceAndSlog extends TimingsTraceLog { * Default constructor using {@code system_server} tags. */ public TimingsTraceAndSlog() { - this(SYSTEM_SERVER_TIMING_TAG, Trace.TRACE_TAG_SYSTEM_SERVER); + this(SYSTEM_SERVER_TIMING_TAG); + } + + /** + * Custom constructor using {@code system_server} trace tag. + * + * @param tag {@code logcat} tag + */ + public TimingsTraceAndSlog(@NonNull String tag) { + this(tag, Trace.TRACE_TAG_SYSTEM_SERVER); } /** diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index d66aa18950d0..03139d2e5e03 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -104,6 +104,7 @@ import com.android.server.EventLogTags; import com.android.server.FgThread; import com.android.server.LocalServices; import com.android.server.SystemService; +import com.android.server.utils.TimingsTraceAndSlog; import com.android.server.wm.WindowManagerInternal; import libcore.io.IoUtils; @@ -1785,25 +1786,26 @@ public class WallpaperManagerService extends IWallpaperManager.Stub @Override public void onUnlockUser(final int userId) { - synchronized (mLock) { - if (mCurrentUserId == userId) { - if (mWaitingForUnlock) { - // the desired wallpaper is not direct-boot aware, load it now - final WallpaperData systemWallpaper = - getWallpaperSafeLocked(userId, FLAG_SYSTEM); - switchWallpaper(systemWallpaper, null); - notifyCallbacksLocked(systemWallpaper); - } - - // Make sure that the SELinux labeling of all the relevant files is correct. - // This corrects for mislabeling bugs that might have arisen from move-to - // operations involving the wallpaper files. This isn't timing-critical, - // so we do it in the background to avoid holding up the user unlock operation. - if (!mUserRestorecon.get(userId)) { - mUserRestorecon.put(userId, true); - Runnable relabeler = new Runnable() { - @Override - public void run() { + TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG); + t.traceBegin("on-unlock-user-" + userId); + try { + synchronized (mLock) { + if (mCurrentUserId == userId) { + if (mWaitingForUnlock) { + // the desired wallpaper is not direct-boot aware, load it now + final WallpaperData systemWallpaper = + getWallpaperSafeLocked(userId, FLAG_SYSTEM); + switchWallpaper(systemWallpaper, null); + notifyCallbacksLocked(systemWallpaper); + } + + // Make sure that the SELinux labeling of all the relevant files is correct. + // This corrects for mislabeling bugs that might have arisen from move-to + // operations involving the wallpaper files. This isn't timing-critical, + // so we do it in the background to avoid holding up the user unlock operation. + if (!mUserRestorecon.get(userId)) { + mUserRestorecon.put(userId, true); + Runnable relabeler = () -> { final File wallpaperDir = getWallpaperDir(userId); for (String filename : sPerUserFiles) { File f = new File(wallpaperDir, filename); @@ -1811,11 +1813,13 @@ public class WallpaperManagerService extends IWallpaperManager.Stub SELinux.restorecon(f); } } - } - }; - BackgroundThread.getHandler().post(relabeler); + }; + BackgroundThread.getHandler().post(relabeler); + } } } + } finally { + t.traceEnd(); } } @@ -1833,31 +1837,37 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } void switchUser(int userId, IRemoteCallback reply) { - final WallpaperData systemWallpaper; - final WallpaperData lockWallpaper; - synchronized (mLock) { - if (mCurrentUserId == userId) { - return; - } - mCurrentUserId = userId; - systemWallpaper = getWallpaperSafeLocked(userId, FLAG_SYSTEM); - final WallpaperData tmpLockWallpaper = mLockWallpaperMap.get(userId); - lockWallpaper = tmpLockWallpaper == null ? systemWallpaper : tmpLockWallpaper; - // Not started watching yet, in case wallpaper data was loaded for other reasons. - if (systemWallpaper.wallpaperObserver == null) { - systemWallpaper.wallpaperObserver = new WallpaperObserver(systemWallpaper); - systemWallpaper.wallpaperObserver.startWatching(); + TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG); + t.traceBegin("switch-user-" + userId); + try { + final WallpaperData systemWallpaper; + final WallpaperData lockWallpaper; + synchronized (mLock) { + if (mCurrentUserId == userId) { + return; + } + mCurrentUserId = userId; + systemWallpaper = getWallpaperSafeLocked(userId, FLAG_SYSTEM); + final WallpaperData tmpLockWallpaper = mLockWallpaperMap.get(userId); + lockWallpaper = tmpLockWallpaper == null ? systemWallpaper : tmpLockWallpaper; + // Not started watching yet, in case wallpaper data was loaded for other reasons. + if (systemWallpaper.wallpaperObserver == null) { + systemWallpaper.wallpaperObserver = new WallpaperObserver(systemWallpaper); + systemWallpaper.wallpaperObserver.startWatching(); + } + switchWallpaper(systemWallpaper, reply); } - switchWallpaper(systemWallpaper, reply); - } - // Offload color extraction to another thread since switchUser will be called - // from the main thread. - FgThread.getHandler().post(() -> { - notifyWallpaperColorsChanged(systemWallpaper, FLAG_SYSTEM); - notifyWallpaperColorsChanged(lockWallpaper, FLAG_LOCK); - notifyWallpaperColorsChanged(mFallbackWallpaper, FLAG_SYSTEM); - }); + // Offload color extraction to another thread since switchUser will be called + // from the main thread. + FgThread.getHandler().post(() -> { + notifyWallpaperColorsChanged(systemWallpaper, FLAG_SYSTEM); + notifyWallpaperColorsChanged(lockWallpaper, FLAG_LOCK); + notifyWallpaperColorsChanged(mFallbackWallpaper, FLAG_SYSTEM); + }); + } finally { + t.traceEnd(); + } } void switchWallpaper(WallpaperData wallpaper, IRemoteCallback reply) { diff --git a/services/core/java/com/android/server/wm/ActivityDisplay.java b/services/core/java/com/android/server/wm/ActivityDisplay.java index db94cf171ec5..9599bad04df0 100644 --- a/services/core/java/com/android/server/wm/ActivityDisplay.java +++ b/services/core/java/com/android/server/wm/ActivityDisplay.java @@ -17,8 +17,6 @@ package com.android.server.wm; import static android.app.ActivityTaskManager.INVALID_STACK_ID; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.ROTATION_UNDEFINED; @@ -34,7 +32,7 @@ import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.FLAG_PRIVATE; import static android.view.Display.REMOVE_MODE_DESTROY_CONTENT; -import static com.android.server.am.ActivityDisplayProto.CONFIGURATION_CONTAINER; +import static com.android.server.am.ActivityDisplayProto.DISPLAY; import static com.android.server.am.ActivityDisplayProto.FOCUSED_STACK_ID; import static com.android.server.am.ActivityDisplayProto.ID; import static com.android.server.am.ActivityDisplayProto.RESUMED_ACTIVITY; @@ -52,6 +50,7 @@ import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLAS import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT; import static com.android.server.wm.RootActivityContainer.FindTaskResult; import static com.android.server.wm.RootActivityContainer.TAG_STATES; +import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DISPLAY; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL; @@ -85,23 +84,19 @@ import java.util.ArrayList; * Exactly one of these classes per Display in the system. Capable of holding zero or more * attached {@link ActivityStack}s. */ -class ActivityDisplay extends ConfigurationContainer<ActivityStack> { +class ActivityDisplay extends DisplayContent { private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityDisplay" : TAG_ATM; private static final String TAG_STACK = TAG + POSTFIX_STACK; static final int POSITION_TOP = Integer.MAX_VALUE; static final int POSITION_BOTTOM = Integer.MIN_VALUE; - /** * Counter for next free stack ID to use for dynamic activity stacks. Unique across displays. */ private static int sNextFreeStackId = 0; - private ActivityTaskManagerService mService; private RootActivityContainer mRootActivityContainer; - // TODO: Remove once unification is complete. - DisplayContent mDisplayContent; /** Actual Display this object tracks. */ int mDisplayId; Display mDisplay; @@ -111,7 +106,6 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { * stacks, bottommost behind. Accessed directly by ActivityManager package classes. Any calls * changing the list should also call {@link #onStackOrderChanged()}. */ - private final ArrayList<ActivityStack> mStacks = new ArrayList<>(); private ArrayList<OnStackOrderChangedListener> mStackOrderChangedCallbacks = new ArrayList<>(); /** Array of all UIDs that are present on the display. */ @@ -157,13 +151,6 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { */ private ActivityStack mLastFocusedStack; - // Cached reference to some special stacks we tend to get a lot so we don't need to loop - // through the list to find them. - private ActivityStack mHomeStack = null; - private ActivityStack mRecentsStack = null; - private ActivityStack mPinnedStack = null; - private ActivityStack mSplitScreenPrimaryStack = null; - // Used in updating the display size private Point mTmpDisplaySize = new Point(); @@ -173,15 +160,24 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { private final FindTaskResult mTmpFindTaskResult = new FindTaskResult(); ActivityDisplay(RootActivityContainer root, Display display) { + super(display, root.mWindowManager); mRootActivityContainer = root; - mService = root.mService; mDisplayId = display.getDisplayId(); mDisplay = display; - mDisplayContent = mService.mWindowManager.mRoot.createDisplayContent(mDisplay, this); - mDisplayContent.reconfigureDisplayLocked(); - onRequestedOverrideConfigurationChanged( - mDisplayContent.getRequestedOverrideConfiguration()); - mService.mWindowManager.mDisplayNotificationController.dispatchDisplayAdded(this); + + if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Adding display=" + display); + + mWmService.mDisplayWindowSettings.applySettingsToDisplayLocked(this); + + if (mWmService.mDisplayManagerInternal != null) { + mWmService.mDisplayManagerInternal + .setDisplayInfoOverrideFromWindowManager(mDisplayId, getDisplayInfo()); + configureDisplayPolicy(); + } + + reconfigureDisplayLocked(); + onRequestedOverrideConfigurationChanged(getRequestedOverrideConfiguration()); + mWmService.mDisplayNotificationController.dispatchDisplayAdded(this); } void onDisplayChanged() { @@ -190,7 +186,7 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { if (displayId != DEFAULT_DISPLAY) { final int displayState = mDisplay.getState(); if (displayState == Display.STATE_OFF && mOffToken == null) { - mOffToken = mService.acquireSleepToken("Display-off", displayId); + mOffToken = mAtmService.acquireSleepToken("Display-off", displayId); } else if (displayState == Display.STATE_ON && mOffToken != null) { mOffToken.release(); mOffToken = null; @@ -199,98 +195,72 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { mDisplay.getRealSize(mTmpDisplaySize); setBounds(0, 0, mTmpDisplaySize.x, mTmpDisplaySize.y); - if (mDisplayContent != null) { - mDisplayContent.updateDisplayInfo(); - mService.mWindowManager.requestTraversal(); - } - } - - // TODO(display-unify): Merge with addChild below. - void addChild(ActivityStack stack, int position) { - addChild(stack, position, false /*fromDc*/); + updateDisplayInfo(); + mWmService.requestTraversal(); } - void addChild(ActivityStack stack, int position, boolean fromDc) { - boolean toTop = position == POSITION_TOP; - if (position == POSITION_BOTTOM) { - position = 0; - } else if (toTop) { - position = getChildCount(); - } - if (DEBUG_STACK) Slog.v(TAG_STACK, "addChild: attaching " + stack - + " to displayId=" + mDisplayId + " position=" + position); - addStackReferenceIfNeeded(stack); - if (!fromDc) { - mDisplayContent.setStackOnDisplay(stack, position); - } - positionChildAt(stack, position); - mService.updateSleepIfNeededLocked(); + void addStack(ActivityStack stack, int position) { + setStackOnDisplay(stack, position); + positionStackAt(stack, position); + mAtmService.updateSleepIfNeededLocked(); } - // TODO(display-unify): Merge with removeChild below. - void onChildRemoved(ActivityStack stack) { - if (!mStacks.remove(stack)) { - // Stack no longer here! - return; + void onStackRemoved(ActivityStack stack) { + if (DEBUG_STACK) { + Slog.v(TAG_STACK, "removeStack: detaching " + stack + " from displayId=" + mDisplayId); } - - if (DEBUG_STACK) Slog.v(TAG_STACK, "removeChild: detaching " + stack - + " from displayId=" + mDisplayId); if (mPreferredTopFocusableStack == stack) { mPreferredTopFocusableStack = null; } - removeStackReferenceIfNeeded(stack); releaseSelfIfNeeded(); - mService.updateSleepIfNeededLocked(); + mAtmService.updateSleepIfNeededLocked(); onStackOrderChanged(stack); } - void removeChild(ActivityStack stack) { - mDisplayContent.removeStackFromDisplay(stack); - } - - void positionChildAtTop(ActivityStack stack, boolean includingParents) { - positionChildAtTop(stack, includingParents, null /* updateLastFocusedStackReason */); + void positionStackAtTop(ActivityStack stack, boolean includingParents) { + positionStackAtTop(stack, includingParents, null /* updateLastFocusedStackReason */); } - void positionChildAtTop(ActivityStack stack, boolean includingParents, + void positionStackAtTop(ActivityStack stack, boolean includingParents, String updateLastFocusedStackReason) { - positionChildAt(stack, getChildCount(), includingParents, updateLastFocusedStackReason); + positionStackAt(stack, getStackCount(), includingParents, updateLastFocusedStackReason); } - void positionChildAtBottom(ActivityStack stack) { - positionChildAtBottom(stack, null /* updateLastFocusedStackReason */); + void positionStackAtBottom(ActivityStack stack) { + positionStackAtBottom(stack, null /* updateLastFocusedStackReason */); } - void positionChildAtBottom(ActivityStack stack, String updateLastFocusedStackReason) { - positionChildAt(stack, 0, false /* includingParents */, updateLastFocusedStackReason); + void positionStackAtBottom(ActivityStack stack, String updateLastFocusedStackReason) { + positionStackAt(stack, 0, false /* includingParents */, updateLastFocusedStackReason); } - private void positionChildAt(ActivityStack stack, int position) { - positionChildAt(stack, position, false /* includingParents */, + private void positionStackAt(ActivityStack stack, int position) { + positionStackAt(stack, position, false /* includingParents */, null /* updateLastFocusedStackReason */); } - private void positionChildAt(ActivityStack stack, int position, boolean includingParents, + private void positionStackAt(ActivityStack stack, int position, boolean includingParents, String updateLastFocusedStackReason) { // TODO: Keep in sync with WindowContainer.positionChildAt(), once we change that to adjust // the position internally, also update the logic here final ActivityStack prevFocusedStack = updateLastFocusedStackReason != null ? getFocusedStack() : null; - final boolean wasContained = mStacks.remove(stack); - if (mSingleTaskInstance && getChildCount() > 0) { + final boolean wasContained = getIndexOf(stack) >= 0; + if (mSingleTaskInstance && getStackCount() == 1 && !wasContained) { throw new IllegalStateException( - "positionChildAt: Can only have one child on display=" + this); + "positionStackAt: Can only have one task on display=" + this); } - final int insertPosition = getTopInsertPosition(stack, position); - mStacks.add(insertPosition, stack); + + // Since positionChildAt() is called during the creation process of pinned stacks, + // ActivityStack#getStack() can be null. + positionStackAt(position, stack, includingParents); // The insert position may be adjusted to non-top when there is always-on-top stack. Since // the original position is preferred to be top, the stack should have higher priority when // we are looking for top focusable stack. The condition {@code wasContained} restricts the // preferred stack is set only when moving an existing stack to top instead of adding a new // stack that may be too early (e.g. in the middle of launching or reparenting). - if (wasContained && position >= getChildCount() - 1 && stack.isFocusableAndVisible()) { + if (wasContained && position >= getStackCount() - 1 && stack.isFocusableAndVisible()) { mPreferredTopFocusableStack = stack; } else if (mPreferredTopFocusableStack == stack) { mPreferredTopFocusableStack = null; @@ -307,67 +277,14 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { } } - // Since positionChildAt() is called during the creation process of pinned stacks, - // ActivityStack#getStack() can be null. - if (mDisplayContent != null) { - mDisplayContent.positionStackAt(insertPosition, stack, includingParents); - } onStackOrderChanged(stack); } - private int getTopInsertPosition(ActivityStack stack, int candidatePosition) { - int position = getChildCount(); - if (stack.inPinnedWindowingMode()) { - // Stack in pinned windowing mode is z-ordered on-top of all other stacks so okay to - // just return the candidate position. - return Math.min(position, candidatePosition); - } - while (position > 0) { - final ActivityStack targetStack = getChildAt(position - 1); - if (!targetStack.isAlwaysOnTop()) { - // We reached a stack that isn't always-on-top. - break; - } - if (stack.isAlwaysOnTop() && !targetStack.inPinnedWindowingMode()) { - // Always on-top non-pinned windowing mode stacks can go anywhere below pinned stack. - break; - } - position--; - } - return Math.min(position, candidatePosition); - } - - <T extends ActivityStack> T getStack(int stackId) { - for (int i = getChildCount() - 1; i >= 0; --i) { - final ActivityStack stack = getChildAt(i); + ActivityStack getStack(int stackId) { + for (int i = getStackCount() - 1; i >= 0; --i) { + final ActivityStack stack = getStackAt(i); if (stack.mStackId == stackId) { - return (T) stack; - } - } - return null; - } - - /** - * @return the topmost stack on the display that is compatible with the input windowing mode and - * activity type. {@code null} means no compatible stack on the display. - * @see ConfigurationContainer#isCompatible(int, int) - */ - <T extends ActivityStack> T getStack(int windowingMode, int activityType) { - if (activityType == ACTIVITY_TYPE_HOME) { - return (T) mHomeStack; - } else if (activityType == ACTIVITY_TYPE_RECENTS) { - return (T) mRecentsStack; - } - if (windowingMode == WINDOWING_MODE_PINNED) { - return (T) mPinnedStack; - } else if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { - return (T) mSplitScreenPrimaryStack; - } - - for (int i = getChildCount() - 1; i >= 0; --i) { - final ActivityStack stack = getChildAt(i); - if (stack.isCompatible(windowingMode, activityType)) { - return (T) stack; + return stack; } } return null; @@ -388,10 +305,10 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { * @see #getStack(int, int) * @see #createStack(int, int, boolean) */ - <T extends ActivityStack> T getOrCreateStack(int windowingMode, int activityType, + ActivityStack getOrCreateStack(int windowingMode, int activityType, boolean onTop) { if (!alwaysCreateStack(windowingMode, activityType)) { - T stack = getStack(windowingMode, activityType); + ActivityStack stack = getStack(windowingMode, activityType); if (stack != null) { return stack; } @@ -404,7 +321,7 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { * if a compatible stack doesn't exist. * @see #getOrCreateStack(int, int, boolean) */ - <T extends ActivityStack> T getOrCreateStack(@Nullable ActivityRecord r, + ActivityStack getOrCreateStack(@Nullable ActivityRecord r, @Nullable ActivityOptions options, @Nullable Task candidateTask, int activityType, boolean onTop) { // First preference is the windowing mode in the activity options if set. @@ -433,9 +350,9 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { * @param onTop If true the stack will be created at the top of the display, else at the bottom. * @return The newly created stack. */ - <T extends ActivityStack> T createStack(int windowingMode, int activityType, boolean onTop) { + ActivityStack createStack(int windowingMode, int activityType, boolean onTop) { - if (mSingleTaskInstance && getChildCount() > 0) { + if (mSingleTaskInstance && getStackCount() > 0) { // Create stack on default display instead since this display can only contain 1 stack. // TODO: Kinda a hack, but better that having the decision at each call point. Hoping // this goes away once ActivityView is no longer using virtual displays. @@ -452,17 +369,17 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { if (activityType != ACTIVITY_TYPE_STANDARD) { // For now there can be only one stack of a particular non-standard activity type on a // display. So, get that ignoring whatever windowing mode it is currently in. - T stack = getStack(WINDOWING_MODE_UNDEFINED, activityType); + ActivityStack stack = getStack(WINDOWING_MODE_UNDEFINED, activityType); if (stack != null) { throw new IllegalArgumentException("Stack=" + stack + " of activityType=" + activityType + " already on display=" + this + ". Can't have multiple."); } } - if (!isWindowingModeSupported(windowingMode, mService.mSupportsMultiWindow, - mService.mSupportsSplitScreenMultiWindow, - mService.mSupportsFreeformWindowManagement, mService.mSupportsPictureInPicture, - activityType)) { + if (!isWindowingModeSupported(windowingMode, mAtmService.mSupportsMultiWindow, + mAtmService.mSupportsSplitScreenMultiWindow, + mAtmService.mSupportsFreeformWindowManagement, + mAtmService.mSupportsPictureInPicture, activityType)) { throw new IllegalArgumentException("Can't create stack for unsupported windowingMode=" + windowingMode); } @@ -472,13 +389,13 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { } @VisibleForTesting - <T extends ActivityStack> T createStackUnchecked(int windowingMode, int activityType, + ActivityStack createStackUnchecked(int windowingMode, int activityType, int stackId, boolean onTop) { if (windowingMode == WINDOWING_MODE_PINNED && activityType != ACTIVITY_TYPE_STANDARD) { throw new IllegalArgumentException("Stack with windowing mode cannot with non standard " + "activity type."); } - return (T) new ActivityStack(this, stackId, + return new ActivityStack(this, stackId, mRootActivityContainer.mStackSupervisor, windowingMode, activityType, onTop); } @@ -491,8 +408,8 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { return mPreferredTopFocusableStack; } - for (int i = getChildCount() - 1; i >= 0; --i) { - final ActivityStack stack = getChildAt(i); + for (int i = getStackCount() - 1; i >= 0; --i) { + final ActivityStack stack = getStackAt(i); if (stack.isFocusableAndVisible()) { return stack; } @@ -506,8 +423,8 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { ? currentFocus.getWindowingMode() : WINDOWING_MODE_UNDEFINED; ActivityStack candidate = null; - for (int i = getChildCount() - 1; i >= 0; --i) { - final ActivityStack stack = getChildAt(i); + for (int i = getStackCount() - 1; i >= 0; --i) { + final ActivityStack stack = getStackAt(i); if (ignoreCurrent && stack == currentFocus) { continue; } @@ -562,8 +479,8 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { } boolean allResumedActivitiesComplete() { - for (int stackNdx = getChildCount() - 1; stackNdx >= 0; --stackNdx) { - final ActivityRecord r = getChildAt(stackNdx).getResumedActivity(); + for (int stackNdx = getStackCount() - 1; stackNdx >= 0; --stackNdx) { + final ActivityRecord r = getStackAt(stackNdx).getResumedActivity(); if (r != null && !r.isState(RESUMED)) { return false; } @@ -589,8 +506,8 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { */ boolean pauseBackStacks(boolean userLeaving, ActivityRecord resuming) { boolean someActivityPaused = false; - for (int stackNdx = getChildCount() - 1; stackNdx >= 0; --stackNdx) { - final ActivityStack stack = getChildAt(stackNdx); + for (int stackNdx = getStackCount() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = getStackAt(stackNdx); final ActivityRecord resumedActivity = stack.getResumedActivity(); if (resumedActivity != null && (stack.getVisibility(resuming) != STACK_VISIBILITY_VISIBLE @@ -610,8 +527,8 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { void findTaskLocked(final ActivityRecord r, final boolean isPreferredDisplay, FindTaskResult result) { mTmpFindTaskResult.clear(); - for (int stackNdx = getChildCount() - 1; stackNdx >= 0; --stackNdx) { - final ActivityStack stack = getChildAt(stackNdx); + for (int stackNdx = getStackCount() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = getStackAt(stackNdx); if (!r.hasCompatibleActivityType(stack)) { if (DEBUG_TASKS) { Slog.d(TAG_TASKS, "Skipping stack: (mismatch activity/stack) " + stack); @@ -654,8 +571,8 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { final ArrayList<ActivityStack> stacks = new ArrayList<>(); for (int j = windowingModes.length - 1 ; j >= 0; --j) { final int windowingMode = windowingModes[j]; - for (int i = getChildCount() - 1; i >= 0; --i) { - final ActivityStack stack = getChildAt(i); + for (int i = getStackCount() - 1; i >= 0; --i) { + final ActivityStack stack = getStackAt(i); if (!stack.isActivityTypeStandardOrUndefined()) { continue; } @@ -682,8 +599,8 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { final ArrayList<ActivityStack> stacks = new ArrayList<>(); for (int j = activityTypes.length - 1 ; j >= 0; --j) { final int activityType = activityTypes[j]; - for (int i = getChildCount() - 1; i >= 0; --i) { - final ActivityStack stack = getChildAt(i); + for (int i = getStackCount() - 1; i >= 0; --i) { + final ActivityStack stack = getStackAt(i); if (stack.getActivityType() == activityType) { stacks.add(stack); } @@ -695,67 +612,12 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { } } - void onStackWindowingModeChanged(ActivityStack stack) { - removeStackReferenceIfNeeded(stack); - addStackReferenceIfNeeded(stack); - } - - private void addStackReferenceIfNeeded(ActivityStack stack) { - final int activityType = stack.getActivityType(); - final int windowingMode = stack.getWindowingMode(); - - if (activityType == ACTIVITY_TYPE_HOME) { - if (mHomeStack != null && mHomeStack != stack) { - throw new IllegalArgumentException("addStackReferenceIfNeeded: home stack=" - + mHomeStack + " already exist on display=" + this + " stack=" + stack); - } - mHomeStack = stack; - } else if (activityType == ACTIVITY_TYPE_RECENTS) { - if (mRecentsStack != null && mRecentsStack != stack) { - throw new IllegalArgumentException("addStackReferenceIfNeeded: recents stack=" - + mRecentsStack + " already exist on display=" + this + " stack=" + stack); - } - mRecentsStack = stack; - } - if (windowingMode == WINDOWING_MODE_PINNED) { - if (mPinnedStack != null && mPinnedStack != stack) { - throw new IllegalArgumentException("addStackReferenceIfNeeded: pinned stack=" - + mPinnedStack + " already exist on display=" + this - + " stack=" + stack); - } - mPinnedStack = stack; - } else if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { - if (mSplitScreenPrimaryStack != null && mSplitScreenPrimaryStack != stack) { - throw new IllegalArgumentException("addStackReferenceIfNeeded:" - + " split-screen-primary" + " stack=" + mSplitScreenPrimaryStack - + " already exist on display=" + this + " stack=" + stack); - } - mSplitScreenPrimaryStack = stack; - onSplitScreenModeActivated(); - } - } - - private void removeStackReferenceIfNeeded(ActivityStack stack) { - if (stack == mHomeStack) { - mHomeStack = null; - } else if (stack == mRecentsStack) { - mRecentsStack = null; - } else if (stack == mPinnedStack) { - mPinnedStack = null; - } else if (stack == mSplitScreenPrimaryStack) { - mSplitScreenPrimaryStack = null; - // Inform the reset of the system that split-screen mode was dismissed so things like - // resizing all the other stacks can take place. - onSplitScreenModeDismissed(); - } - } - - private void onSplitScreenModeDismissed() { - mService.deferWindowLayout(); + void onSplitScreenModeDismissed() { + mAtmService.deferWindowLayout(); try { // Adjust the windowing mode of any stack in secondary split-screen to fullscreen. - for (int i = getChildCount() - 1; i >= 0; --i) { - final ActivityStack otherStack = getChildAt(i); + for (int i = getStackCount() - 1; i >= 0; --i) { + final ActivityStack otherStack = getStackAt(i); if (!otherStack.inSplitScreenSecondaryWindowingMode()) { continue; } @@ -766,26 +628,28 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { } finally { final ActivityStack topFullscreenStack = getTopStackInWindowingMode(WINDOWING_MODE_FULLSCREEN); - if (topFullscreenStack != null && mHomeStack != null && !isTopStack(mHomeStack)) { + final ActivityStack homeStack = getHomeStack(); + if (topFullscreenStack != null && homeStack != null && !isTopStack(homeStack)) { // Whenever split-screen is dismissed we want the home stack directly behind the // current top fullscreen stack so it shows up when the top stack is finished. // TODO: Would be better to use ActivityDisplay.positionChildAt() for this, however // ActivityDisplay doesn't have a direct controller to WM side yet. We can switch // once we have that. - mHomeStack.moveToFront("onSplitScreenModeDismissed"); + homeStack.moveToFront("onSplitScreenModeDismissed"); topFullscreenStack.moveToFront("onSplitScreenModeDismissed"); } - mService.continueWindowLayout(); + mAtmService.continueWindowLayout(); } } - private void onSplitScreenModeActivated() { - mService.deferWindowLayout(); + void onSplitScreenModeActivated() { + mAtmService.deferWindowLayout(); try { // Adjust the windowing mode of any affected by split-screen to split-screen secondary. - for (int i = getChildCount() - 1; i >= 0; --i) { - final ActivityStack otherStack = getChildAt(i); - if (otherStack == mSplitScreenPrimaryStack + final ActivityStack splitScreenPrimaryStack = getSplitScreenPrimaryStack(); + for (int i = getStackCount() - 1; i >= 0; --i) { + final ActivityStack otherStack = getStackAt(i); + if (otherStack == splitScreenPrimaryStack || !otherStack.affectedBySplitScreenResize()) { continue; } @@ -795,7 +659,7 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { false /* creating */); } } finally { - mService.continueWindowLayout(); + mAtmService.continueWindowLayout(); } } @@ -890,10 +754,10 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { int validateWindowingMode(int windowingMode, @Nullable ActivityRecord r, @Nullable Task task, int activityType) { // Make sure the windowing mode we are trying to use makes sense for what is supported. - boolean supportsMultiWindow = mService.mSupportsMultiWindow; - boolean supportsSplitScreen = mService.mSupportsSplitScreenMultiWindow; - boolean supportsFreeform = mService.mSupportsFreeformWindowManagement; - boolean supportsPip = mService.mSupportsPictureInPicture; + boolean supportsMultiWindow = mAtmService.mSupportsMultiWindow; + boolean supportsSplitScreen = mAtmService.mSupportsSplitScreenMultiWindow; + boolean supportsFreeform = mAtmService.mSupportsFreeformWindowManagement; + boolean supportsPip = mAtmService.mSupportsPictureInPicture; if (supportsMultiWindow) { if (task != null) { supportsMultiWindow = task.isResizeable(); @@ -927,21 +791,13 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { return WINDOWING_MODE_UNDEFINED; } - /** - * Get the topmost stack on the display. It may be different from focused stack, because - * some stacks are not focusable (e.g. PiP). - */ - ActivityStack getTopStack() { - return mStacks.isEmpty() ? null : getChildAt(getChildCount() - 1); - } - boolean isTopStack(ActivityStack stack) { return stack == getTopStack(); } boolean isTopNotPinnedStack(ActivityStack stack) { - for (int i = getChildCount() - 1; i >= 0; --i) { - final ActivityStack current = getChildAt(i); + for (int i = getStackCount() - 1; i >= 0; --i) { + final ActivityStack current = getStackAt(i); if (!current.inPinnedWindowingMode()) { return current == stack; } @@ -950,8 +806,8 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { } ActivityStack getTopStackInWindowingMode(int windowingMode) { - for (int i = getChildCount() - 1; i >= 0; --i) { - final ActivityStack current = getChildAt(i); + for (int i = getStackCount() - 1; i >= 0; --i) { + final ActivityStack current = getStackAt(i); if (windowingMode == current.getWindowingMode()) { return current; } @@ -981,8 +837,8 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { // Look in other focusable stacks. if (topRunning == null) { - for (int i = getChildCount() - 1; i >= 0; --i) { - final ActivityStack stack = getChildAt(i); + for (int i = getStackCount() - 1; i >= 0; --i) { + final ActivityStack stack = getStackAt(i); // Only consider focusable stacks other than the current focused one. if (stack == focusedStack || !stack.isFocusable()) { continue; @@ -1005,22 +861,18 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { return topRunning; } - int getIndexOf(ActivityStack stack) { - return mStacks.indexOf(stack); - } - boolean updateDisplayOverrideConfigurationLocked() { Configuration values = new Configuration(); - mDisplayContent.computeScreenConfiguration(values); + computeScreenConfiguration(values); - mService.mH.sendMessage(PooledLambda.obtainMessage( - ActivityManagerInternal::updateOomLevelsForDisplay, mService.mAmInternal, + mAtmService.mH.sendMessage(PooledLambda.obtainMessage( + ActivityManagerInternal::updateOomLevelsForDisplay, mAtmService.mAmInternal, mDisplayId)); Settings.System.clearConfiguration(values); updateDisplayOverrideConfigurationLocked(values, null /* starting */, - false /* deferResume */, mService.mTmpUpdateConfigurationResult); - return mService.mTmpUpdateConfigurationResult.changes != 0; + false /* deferResume */, mAtmService.mTmpUpdateConfigurationResult); + return mAtmService.mTmpUpdateConfigurationResult.changes != 0; } /** @@ -1034,14 +886,14 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { int changes = 0; boolean kept = true; - mService.deferWindowLayout(); + mAtmService.deferWindowLayout(); try { if (values != null) { if (mDisplayId == DEFAULT_DISPLAY) { // Override configuration of the default display duplicates global config, so // we're calling global config update instead for default display. It will also // apply the correct override config. - changes = mService.updateGlobalConfigurationLocked(values, + changes = mAtmService.updateGlobalConfigurationLocked(values, false /* initLocale */, false /* persistent */, UserHandle.USER_NULL /* userId */, deferResume); } else { @@ -1049,9 +901,9 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { } } - kept = mService.ensureConfigAndVisibilityAfterUpdate(starting, changes); + kept = mAtmService.ensureConfigAndVisibilityAfterUpdate(starting, changes); } finally { - mService.continueWindowLayout(); + mAtmService.continueWindowLayout(); } if (result != null) { @@ -1071,17 +923,17 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { final boolean isDensityChange = (changes & ActivityInfo.CONFIG_DENSITY) != 0; if (isDensityChange && mDisplayId == DEFAULT_DISPLAY) { - mService.mAppWarnings.onDensityChanged(); + mAtmService.mAppWarnings.onDensityChanged(); // Post message to start process to avoid possible deadlock of calling into AMS with // the ATMS lock held. final Message msg = PooledLambda.obtainMessage( ActivityManagerInternal::killAllBackgroundProcessesExcept, - mService.mAmInternal, N, + mAtmService.mAmInternal, N, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND); - mService.mH.sendMessage(msg); + mAtmService.mH.sendMessage(msg); } - mService.mWindowManager.mDisplayNotificationController.dispatchDisplayChanged( + mWmService.mDisplayNotificationController.dispatchDisplayChanged( this, getConfiguration()); } return changes; @@ -1092,43 +944,29 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { final int currRotation = getRequestedOverrideConfiguration().windowConfiguration.getRotation(); if (currRotation != ROTATION_UNDEFINED - && currRotation != overrideConfiguration.windowConfiguration.getRotation() - && mDisplayContent != null) { - mDisplayContent.applyRotationLocked(currRotation, + && currRotation != overrideConfiguration.windowConfiguration.getRotation()) { + applyRotationLocked(currRotation, overrideConfiguration.windowConfiguration.getRotation()); } super.onRequestedOverrideConfigurationChanged(overrideConfiguration); - if (mDisplayContent != null) { - mService.mWindowManager.setNewDisplayOverrideConfiguration( - overrideConfiguration, mDisplayContent); - } - mService.addWindowLayoutReasons( + mWmService.setNewDisplayOverrideConfiguration(overrideConfiguration, this); + mAtmService.addWindowLayoutReasons( ActivityTaskManagerService.LAYOUT_REASON_CONFIG_CHANGED); } @Override public void onConfigurationChanged(Configuration newParentConfig) { // update resources before cascade so that docked/pinned stacks use the correct info - if (mDisplayContent != null) { - mDisplayContent.preOnConfigurationChanged(); - } + preOnConfigurationChanged(); super.onConfigurationChanged(newParentConfig); } void onLockTaskPackagesUpdated() { - for (int i = getChildCount() - 1; i >= 0; --i) { - getChildAt(i).onLockTaskPackagesUpdated(); + for (int i = getStackCount() - 1; i >= 0; --i) { + getStackAt(i).onLockTaskPackagesUpdated(); } } - /** We are in the process of exiting split-screen mode. */ - void onExitingSplitScreenMode() { - // Remove reference to the primary-split-screen stack so it no longer has any effect on the - // display. For example, we want to be able to create fullscreen stack for standard activity - // types when exiting split-screen mode. - mSplitScreenPrimaryStack = null; - } - /** Checks whether the given activity is in size compatibility mode and notifies the change. */ void handleActivitySizeCompatModeIfNeeded(ActivityRecord r) { if (!r.isState(RESUMED) || r.getWindowingMode() != WINDOWING_MODE_FULLSCREEN) { @@ -1137,7 +975,7 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { } if (!r.inSizeCompatMode()) { if (mLastCompatModeActivity != null) { - mService.getTaskChangeNotificationController() + mAtmService.getTaskChangeNotificationController() .notifySizeCompatModeActivityChanged(mDisplayId, null /* activityToken */); } mLastCompatModeActivity = null; @@ -1147,44 +985,13 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { return; } mLastCompatModeActivity = r; - mService.getTaskChangeNotificationController() + mAtmService.getTaskChangeNotificationController() .notifySizeCompatModeActivityChanged(mDisplayId, r.appToken); } - ActivityStack getSplitScreenPrimaryStack() { - return mSplitScreenPrimaryStack; - } - - boolean hasSplitScreenPrimaryStack() { - return mSplitScreenPrimaryStack != null; - } - - ActivityStack getPinnedStack() { - return mPinnedStack; - } - - boolean hasPinnedStack() { - return mPinnedStack != null; - } - @Override public String toString() { - return "ActivityDisplay={" + mDisplayId + " numStacks=" + getChildCount() + "}"; - } - - @Override - protected int getChildCount() { - return mStacks.size(); - } - - @Override - protected ActivityStack getChildAt(int index) { - return mStacks.get(index); - } - - @Override - protected ConfigurationContainer getParent() { - return mRootActivityContainer; + return "ActivityDisplay={" + mDisplayId + " numStacks=" + getStackCount() + "}"; } boolean isPrivate() { @@ -1227,10 +1034,10 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { final ActivityDisplay toDisplay = mRootActivityContainer.getDefaultDisplay(); mRootActivityContainer.mStackSupervisor.beginDeferResume(); try { - int numStacks = getChildCount(); + int numStacks = getStackCount(); // Keep the order from bottom to top. for (int stackNdx = 0; stackNdx < numStacks; stackNdx++) { - final ActivityStack stack = getChildAt(stackNdx); + final ActivityStack stack = getStackAt(stackNdx); // Always finish non-standard type stacks. if (destroyContentOnRemoval || !stack.isActivityTypeStandardOrUndefined()) { stack.finishAllActivitiesImmediately(); @@ -1241,14 +1048,14 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { final int windowingMode = toDisplay.hasSplitScreenPrimaryStack() ? WINDOWING_MODE_SPLIT_SCREEN_SECONDARY : WINDOWING_MODE_UNDEFINED; - stack.reparent(toDisplay.mDisplayContent, true /* onTop */); + stack.reparent(toDisplay, true /* onTop */); stack.setWindowingMode(windowingMode); lastReparentedStack = stack; } // Stacks may be removed from this display. Ensure each stack will be processed and // the loop will end. - stackNdx -= numStacks - getChildCount(); - numStacks = getChildCount(); + stackNdx -= numStacks - getStackCount(); + numStacks = getStackCount(); } } finally { mRootActivityContainer.mStackSupervisor.endDeferResume(); @@ -1265,24 +1072,23 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { if (!mAllSleepTokens.isEmpty()) { mRootActivityContainer.mSleepTokens.removeAll(mAllSleepTokens); mAllSleepTokens.clear(); - mService.updateSleepIfNeededLocked(); + mAtmService.updateSleepIfNeededLocked(); } } private void releaseSelfIfNeeded() { - if (!mRemoved || mDisplayContent == null) { + if (!mRemoved) { return; } - final ActivityStack stack = getChildCount() == 1 ? getChildAt(0) : null; + final ActivityStack stack = getStackCount() == 1 ? getStackAt(0) : null; if (stack != null && stack.isActivityTypeHome() && stack.getAllTasks().isEmpty()) { // Release this display if an empty home stack is the only thing left. // Since it is the last stack, this display will be released along with the stack // removal. stack.removeIfPossible(); - } else if (mStacks.isEmpty()) { - mDisplayContent.removeIfPossible(); - mDisplayContent = null; + } else if (getTopStack() == null) { + removeIfPossible(); mRootActivityContainer.removeChild(this); mRootActivityContainer.mStackSupervisor .getKeyguardController().onDisplayRemoved(mDisplayId); @@ -1302,14 +1108,6 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { private static void addActivityUid(ActivityRecord r, IntArray uids) { uids.add(r.getUid()); } - /** - * Checks if system decorations should be shown on this display. - * - * @see Display#FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS - */ - boolean supportsSystemDecorations() { - return mDisplayContent.supportsSystemDecorations(); - } @VisibleForTesting boolean shouldDestroyContentOnRemove() { @@ -1317,14 +1115,11 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { } boolean shouldSleep() { - return (mStacks.isEmpty() || !mAllSleepTokens.isEmpty()) - && (mService.mRunningVoice == null); + return (getStackCount() == 0 || !mAllSleepTokens.isEmpty()) + && (mAtmService.mRunningVoice == null); } void setFocusedApp(ActivityRecord r, boolean moveFocusNow) { - if (mDisplayContent == null) { - return; - } final ActivityRecord newFocus; final IBinder token = r.appToken; if (token == null) { @@ -1332,7 +1127,7 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { mDisplayId); newFocus = null; } else { - newFocus = mService.mWindowManager.mRoot.getActivityRecord(token); + newFocus = mWmService.mRoot.getActivityRecord(token); if (newFocus == null) { Slog.w(TAG_WM, "Attempted to set focus to non-existing app token: " + token + ", displayId=" + mDisplayId); @@ -1342,9 +1137,9 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { moveFocusNow, mDisplayId); } - final boolean changed = mDisplayContent.setFocusedApp(newFocus); + final boolean changed = setFocusedApp(newFocus); if (moveFocusNow && changed) { - mService.mWindowManager.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, + mWmService.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, true /*updateInputWindows*/); } } @@ -1354,8 +1149,8 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { * already top-most. */ ActivityStack getStackAbove(ActivityStack stack) { - final int stackIndex = mStacks.indexOf(stack) + 1; - return (stackIndex < getChildCount()) ? getChildAt(stackIndex) : null; + final int stackIndex = getIndexOf(stack) + 1; + return (stackIndex < getStackCount()) ? getStackAt(stackIndex) : null; } /** @@ -1369,12 +1164,12 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { } // Move the stack to the bottom to not affect the following visibility checks - positionChildAtBottom(stack); + positionStackAtBottom(stack); // Find the next position where the stack should be placed - final int numStacks = getChildCount(); + final int numStacks = getStackCount(); for (int stackNdx = 0; stackNdx < numStacks; stackNdx++) { - final ActivityStack s = getChildAt(stackNdx); + final ActivityStack s = getStackAt(stackNdx); if (s == stack) { continue; } @@ -1383,7 +1178,7 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { winMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; if (s.shouldBeVisible(null) && isValidWindowingMode) { // Move the provided stack to behind this stack - positionChildAt(stack, Math.max(0, stackNdx - 1)); + positionStackAt(stack, Math.max(0, stackNdx - 1)); break; } } @@ -1403,25 +1198,26 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { // list, so we need to adjust the insertion index to account for the removed index // TODO: Remove this logic when WindowContainer.positionChildAt() is updated to adjust the // position internally - final int stackIndex = mStacks.indexOf(stack); - final int behindStackIndex = mStacks.indexOf(behindStack); + final int stackIndex = getIndexOf(stack); + final int behindStackIndex = getIndexOf(behindStack); final int insertIndex = stackIndex <= behindStackIndex ? behindStackIndex - 1 : behindStackIndex; - positionChildAt(stack, Math.max(0, insertIndex)); + positionStackAt(stack, Math.max(0, insertIndex)); } void ensureActivitiesVisible(ActivityRecord starting, int configChanges, boolean preserveWindows, boolean notifyClients) { - for (int stackNdx = getChildCount() - 1; stackNdx >= 0; --stackNdx) { - final ActivityStack stack = getChildAt(stackNdx); + for (int stackNdx = getStackCount() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = getStackAt(stackNdx); stack.ensureActivitiesVisible(starting, configChanges, preserveWindows, notifyClients); } } void moveHomeStackToFront(String reason) { - if (mHomeStack != null) { - mHomeStack.moveToFront(reason); + final ActivityStack homeStack = getHomeStack(); + if (homeStack != null) { + homeStack.moveToFront(reason); } } @@ -1439,25 +1235,21 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { } @Nullable - ActivityStack getHomeStack() { - return mHomeStack; - } - - @Nullable ActivityRecord getHomeActivity() { return getHomeActivityForUser(mRootActivityContainer.mCurrentUser); } @Nullable ActivityRecord getHomeActivityForUser(int userId) { - if (mHomeStack == null) { + final ActivityStack homeStack = getHomeStack(); + if (homeStack == null) { return null; } final PooledPredicate p = PooledLambda.obtainPredicate( ActivityDisplay::isHomeActivityForUser, PooledLambda.__(ActivityRecord.class), userId); - final ActivityRecord r = mHomeStack.getActivity(p); + final ActivityRecord r = homeStack.getActivity(p); p.recycle(); return r; } @@ -1502,32 +1294,14 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { } } - /** - * See {@link DisplayContent#deferUpdateImeTarget()} - */ - public void deferUpdateImeTarget() { - if (mDisplayContent != null) { - mDisplayContent.deferUpdateImeTarget(); - } - } - - /** - * See {@link DisplayContent#deferUpdateImeTarget()} - */ - public void continueUpdateImeTarget() { - if (mDisplayContent != null) { - mDisplayContent.continueUpdateImeTarget(); - } - } - void setDisplayToSingleTaskInstance() { - final int childCount = getChildCount(); + final int childCount = getStackCount(); if (childCount > 1) { throw new IllegalArgumentException("Display already has multiple stacks. display=" + this); } if (childCount > 0) { - final ActivityStack stack = getChildAt(0); + final ActivityStack stack = getStackAt(0); if (stack.getChildCount() > 1) { throw new IllegalArgumentException("Display stack already has multiple tasks." + " display=" + this + " stack=" + stack); @@ -1544,8 +1318,8 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { @VisibleForTesting void removeAllTasks() { - for (int i = getChildCount() - 1; i >= 0; --i) { - final ActivityStack stack = getChildAt(i); + for (int i = getStackCount() - 1; i >= 0; --i) { + final ActivityStack stack = getStackAt(i); final ArrayList<Task> tasks = stack.getAllTasks(); for (int j = tasks.size() - 1; j >= 0; --j) { stack.removeChild(tasks.get(j), "removeAllTasks"); @@ -1554,20 +1328,24 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { } public void dump(PrintWriter pw, String prefix) { - pw.println(prefix + "displayId=" + mDisplayId + " stacks=" + getChildCount() + pw.println(prefix + "displayId=" + mDisplayId + " stacks=" + getStackCount() + (mSingleTaskInstance ? " mSingleTaskInstance" : "")); final String myPrefix = prefix + " "; - if (mHomeStack != null) { - pw.println(myPrefix + "mHomeStack=" + mHomeStack); + ActivityStack stack = getHomeStack(); + if (stack != null) { + pw.println(myPrefix + "mHomeStack=" + stack); } - if (mRecentsStack != null) { - pw.println(myPrefix + "mRecentsStack=" + mRecentsStack); + stack = getRecentsStack(); + if (stack != null) { + pw.println(myPrefix + "mRecentsStack=" + stack); } - if (mPinnedStack != null) { - pw.println(myPrefix + "mPinnedStack=" + mPinnedStack); + stack = getPinnedStack(); + if (stack != null) { + pw.println(myPrefix + "mPinnedStack=" + stack); } - if (mSplitScreenPrimaryStack != null) { - pw.println(myPrefix + "mSplitScreenPrimaryStack=" + mSplitScreenPrimaryStack); + stack = getSplitScreenPrimaryStack(); + if (stack != null) { + pw.println(myPrefix + "mSplitScreenPrimaryStack=" + stack); } if (mPreferredTopFocusableStack != null) { pw.println(myPrefix + "mPreferredTopFocusableStack=" + mPreferredTopFocusableStack); @@ -1578,8 +1356,8 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { } public void dumpStacks(PrintWriter pw) { - for (int i = getChildCount() - 1; i >= 0; --i) { - pw.print(getChildAt(i).mStackId); + for (int i = getStackCount() - 1; i >= 0; --i) { + pw.print(getStackAt(i).mStackId); if (i > 0) { pw.print(","); } @@ -1589,7 +1367,7 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { public void writeToProto(ProtoOutputStream proto, long fieldId, @WindowTraceLogLevel int logLevel) { final long token = proto.start(fieldId); - super.writeToProto(proto, CONFIGURATION_CONTAINER, logLevel); + writeToProtoInner(proto, DISPLAY, logLevel); proto.write(ID, mDisplayId); proto.write(SINGLE_TASK_INSTANCE, mSingleTaskInstance); final ActivityStack focusedStack = getFocusedStack(); @@ -1602,8 +1380,8 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { } else { proto.write(FOCUSED_STACK_ID, INVALID_STACK_ID); } - for (int stackNdx = getChildCount() - 1; stackNdx >= 0; --stackNdx) { - final ActivityStack stack = getChildAt(stackNdx); + for (int stackNdx = getStackCount() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = getStackAt(stackNdx); stack.writeToProto(proto, STACKS, logLevel); } proto.end(token); diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 963e090d2a1e..5377db4402a0 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -2237,7 +2237,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } } return (getWindowConfiguration().canReceiveKeys() || isAlwaysFocusable()) - && getParent() != null; + && getDisplay() != null; } /** Move activity with its stack to front and make the stack focused. */ @@ -2256,7 +2256,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return false; } - if (mRootActivityContainer.getTopResumedActivity() == this) { + if (mRootActivityContainer.getTopResumedActivity() == this + && getDisplayContent().mFocusedApp == this) { if (DEBUG_FOCUS) { Slog.d(TAG_FOCUS, "moveActivityStackToFront: already on top, activity=" + this); } @@ -2420,7 +2421,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final ActivityDisplay display = stack.getDisplay(); next = display.topRunningActivity(); if (next != null) { - display.positionChildAtTop(next.getActivityStack(), + display.positionStackAtTop(next.getActivityStack(), false /* includingParents */, "finish-display-top"); } } @@ -5688,8 +5689,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A @Override boolean isWaitingForTransitionStart() { final DisplayContent dc = getDisplayContent(); - // TODO(display-unify): Test for null can be removed once unification is done. - if (dc == null) return false; return dc.mAppTransition.isTransitionSet() && (dc.mOpeningApps.contains(this) || dc.mClosingApps.contains(this) diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java index bb3126b99696..918d40bf5c8b 100644 --- a/services/core/java/com/android/server/wm/ActivityStack.java +++ b/services/core/java/com/android/server/wm/ActivityStack.java @@ -750,7 +750,7 @@ class ActivityStack extends WindowContainer<Task> implements BoundsAnimationTarg // stacks on a wrong display. mDisplayId = display.mDisplayId; setActivityType(activityType); - display.addChild(this, onTop ? POSITION_TOP : POSITION_BOTTOM); + display.addStack(this, onTop ? POSITION_TOP : POSITION_BOTTOM); setWindowingMode(windowingMode, false /* animate */, false /* showRecents */, false /* enteringSplitScreenMode */, false /* deferEnsuringVisibility */, true /* creating */); @@ -879,7 +879,6 @@ class ActivityStack extends WindowContainer<Task> implements BoundsAnimationTarg newBounds /* outStackBounds */, mTmpRect2 /* outTempTaskBounds */); hasNewOverrideBounds = true; } - display.onStackWindowingModeChanged(this); } if (hasNewOverrideBounds) { if (inSplitScreenPrimaryWindowingMode()) { @@ -896,7 +895,7 @@ class ActivityStack extends WindowContainer<Task> implements BoundsAnimationTarg // Since always on top is only on when the stack is freeform or pinned, the state // can be toggled when the windowing mode changes. We must make sure the stack is // placed properly when always on top state changes. - display.positionChildAtTop(this, false /* includingParents */); + display.positionStackAtTop(this, false /* includingParents */); } } @@ -1337,7 +1336,7 @@ class ActivityStack extends WindowContainer<Task> implements BoundsAnimationTarg } final boolean movingTask = task != null; - display.positionChildAtTop(this, !movingTask /* includingParents */, reason); + display.positionStackAtTop(this, !movingTask /* includingParents */, reason); if (movingTask) { // This also moves the entire hierarchy branch to top, including parents positionChildAtTop(task); @@ -1353,7 +1352,7 @@ class ActivityStack extends WindowContainer<Task> implements BoundsAnimationTarg return; } - getDisplay().positionChildAtBottom(this, reason); + getDisplay().positionStackAtBottom(this, reason); if (task != null) { positionChildAtBottom(task); } @@ -1848,8 +1847,8 @@ class ActivityStack extends WindowContainer<Task> implements BoundsAnimationTarg boolean shouldBeVisible = true; final int windowingMode = getWindowingMode(); final boolean isAssistantType = isActivityTypeAssistant(); - for (int i = display.getChildCount() - 1; i >= 0; --i) { - final ActivityStack other = display.getChildAt(i); + for (int i = display.getStackCount() - 1; i >= 0; --i) { + final ActivityStack other = display.getStackAt(i); final boolean hasRunningActivities = other.topRunningActivityLocked() != null; if (other == this) { // Should be visible if there is no other stack occluding it, unless it doesn't @@ -3010,7 +3009,7 @@ class ActivityStack extends WindowContainer<Task> implements BoundsAnimationTarg if (index == 0) { return false; } - final ActivityStack stackBehind = display.getChildAt(index - 1); + final ActivityStack stackBehind = display.getStackAt(index - 1); return stackBehind.isActivityTypeStandard(); } @@ -3768,7 +3767,7 @@ class ActivityStack extends WindowContainer<Task> implements BoundsAnimationTarg if (!hasChild()) { // Stack is now empty... - removeIfPossible(); + removeIfPossible(); } moveHomeStackToFrontIfNeeded(topFocused, display, reason); @@ -3887,7 +3886,7 @@ class ActivityStack extends WindowContainer<Task> implements BoundsAnimationTarg // always on top windows. Since the position the stack should be inserted into is calculated // properly in {@link ActivityDisplay#getTopInsertPosition()} in both cases, we can just // request that the stack is put at top here. - display.positionChildAtTop(this, false /* includingParents */); + display.positionStackAtTop(this, false /* includingParents */); } /** NOTE: Should only be called from {@link Task#reparent}. */ @@ -4019,7 +4018,7 @@ class ActivityStack extends WindowContainer<Task> implements BoundsAnimationTarg final Task task = mChildren.get(0); setWindowingMode(WINDOWING_MODE_UNDEFINED); - getDisplay().positionChildAtTop(this, false /* includingParents */); + getDisplay().positionStackAtTop(this, false /* includingParents */); mStackSupervisor.scheduleUpdatePictureInPictureModeIfNeeded(task, this); MetricsLoggerWrapper.logPictureInPictureFullScreen(mService.mContext, @@ -4456,20 +4455,14 @@ class ActivityStack extends WindowContainer<Task> implements BoundsAnimationTarg } } - // TODO(display-unify): Remove after display unification. - protected void onParentChanged(ActivityDisplay newParent, ActivityDisplay oldParent) { - onParentChanged( - newParent != null ? newParent.mDisplayContent : null, - oldParent != null ? oldParent.mDisplayContent : null); - } - @Override protected void onParentChanged( ConfigurationContainer newParent, ConfigurationContainer oldParent) { + // TODO(display-merge): Remove cast final ActivityDisplay display = newParent != null - ? ((WindowContainer) newParent).getDisplayContent().mActivityDisplay : null; + ? (ActivityDisplay) ((WindowContainer) newParent).getDisplayContent() : null; final ActivityDisplay oldDisplay = oldParent != null - ? ((WindowContainer) oldParent).getDisplayContent().mActivityDisplay : null; + ? (ActivityDisplay) ((WindowContainer) oldParent).getDisplayContent() : null; mDisplayId = (display != null) ? display.mDisplayId : INVALID_DISPLAY; mPrevDisplayId = (oldDisplay != null) ? oldDisplay.mDisplayId : INVALID_DISPLAY; diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java index d98aa6fe5ebd..735636867d09 100644 --- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java @@ -1500,13 +1500,11 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { mRootActivityContainer.getActivityDisplay(toDisplayId); if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { - // Tell the display we are exiting split-screen mode. - toDisplay.onExitingSplitScreenMode(); // We are moving all tasks from the docked stack to the fullscreen stack, // which is dismissing the docked stack, so resize all other stacks to // fullscreen here already so we don't end up with resize trashing. - for (int i = toDisplay.getChildCount() - 1; i >= 0; --i) { - final ActivityStack otherStack = toDisplay.getChildAt(i); + for (int i = toDisplay.getStackCount() - 1; i >= 0; --i) { + final ActivityStack otherStack = toDisplay.getStackAt(i); if (!otherStack.inSplitScreenSecondaryWindowingMode()) { continue; } @@ -1654,8 +1652,8 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { // screen controls and is also the same for all stacks. final ActivityDisplay display = mRootActivityContainer.getDefaultDisplay(); final Rect otherTaskRect = new Rect(); - for (int i = display.getChildCount() - 1; i >= 0; --i) { - final ActivityStack current = display.getChildAt(i); + for (int i = display.getStackCount() - 1; i >= 0; --i) { + final ActivityStack current = display.getStackAt(i); if (!current.inSplitScreenSecondaryWindowingMode()) { continue; } @@ -1740,7 +1738,7 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { * to the fullscreen stack. This is to guarantee that when we are removing a stack, * that the client receives onStop() before it is reparented. We do this by detaching * the stack from the display so that it will be considered invisible when - * ensureActivitiesVisible() is called, and all of its activitys will be marked + * ensureActivitiesVisible() is called, and all of its activities will be marked * invisible as well and added to the stopping list. After which we process the * stopping list by handling the idle. */ diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 8164bf48627d..baa295518c2d 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -1132,7 +1132,7 @@ class ActivityStarter { request.outActivity[0] = mLastStartActivityRecord; } - return getExternalResult(mLastStartActivityResult); + return mLastStartActivityResult; } /** diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index b046b08247a0..2aa9ea5b385f 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -138,7 +138,6 @@ import static com.android.server.wm.utils.RegionUtils.forEachRectReverse; import static com.android.server.wm.utils.RegionUtils.rectListToRegion; import android.animation.AnimationHandler; -import android.annotation.CallSuper; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -221,7 +220,7 @@ import java.util.function.Predicate; * particular Display. */ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowContainer> - implements WindowManagerPolicy.DisplayContentInfo, ConfigurationContainerListener { + implements WindowManagerPolicy.DisplayContentInfo { private static final String TAG = TAG_WITH_CLASS_NAME ? "DisplayContent" : TAG_WM; /** The default scaling mode that scales content automatically. */ @@ -236,11 +235,10 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo @Retention(RetentionPolicy.SOURCE) @interface ForceScalingMode {} - /** Unique identifier of this stack. */ - private final int mDisplayId; + ActivityTaskManagerService mAtmService; - // TODO: Remove once unification is complete. - ActivityDisplay mActivityDisplay; + /** Unique identifier of this display. */ + private final int mDisplayId; /** The containers below are the only child containers the display can have. */ // Contains all window containers that are related to apps (Activities) @@ -671,10 +669,12 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo getDisplayPolicy().layoutWindowLw(w, null, mDisplayFrames); w.mLayoutSeq = mLayoutSeq; - // If this is the first layout, we need to initialize the last inset values as - // otherwise we'd immediately cause an unnecessary resize. + // If this is the first layout, we need to initialize the last frames and inset values, + // as otherwise we'd immediately cause an unnecessary resize. if (firstLayout) { + w.updateLastFrames(); w.updateLastInsetValues(); + w.updateLocationInParentDisplayIfNeeded(); } if (w.mActivityRecord != null) { @@ -842,16 +842,15 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo * @param service You know. * @param activityDisplay The ActivityDisplay for the display container. */ - DisplayContent(Display display, WindowManagerService service, - ActivityDisplay activityDisplay) { + DisplayContent(Display display, WindowManagerService service) { super(service); - mActivityDisplay = activityDisplay; if (service.mRoot.getDisplayContent(display.getDisplayId()) != null) { throw new IllegalArgumentException("Display with ID=" + display.getDisplayId() + " already exists=" + service.mRoot.getDisplayContent(display.getDisplayId()) + " new=" + display); } + mAtmService = mWmService.mAtmService; mDisplay = display; mDisplayId = display.getDisplayId(); mWallpaperController = new WallpaperController(mWmService, this); @@ -1125,20 +1124,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo mAppTransitionController.registerRemoteAnimations(definition); } - /** - * The display content may have configuration set from {@link #DisplayWindowSettings}. This - * callback let the owner of container know there is existing configuration to prevent the - * values from being replaced by the initializing {@link #ActivityDisplay}. - */ - void initializeDisplayOverrideConfiguration() { - if (mActivityDisplay == null) { - return; - } - mActivityDisplay.onRequestedOverrideConfigurationChanged( - getResolvedOverrideConfiguration()); - mActivityDisplay.registerConfigurationChangeListener(this); - } - void reconfigureDisplayLocked() { if (!isReady()) { return; @@ -1162,13 +1147,15 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } void sendNewConfiguration() { - if (!isReady() || mActivityDisplay == null) { + if (!isReady()) { return; } if (mDisplayRotation.isWaitingForRemoteRotation()) { return; } - final boolean configUpdated = mActivityDisplay.updateDisplayOverrideConfigurationLocked(); + // TODO(display-merge): Remove cast + final boolean configUpdated = + ((ActivityDisplay) this).updateDisplayOverrideConfigurationLocked(); if (configUpdated) { return; } @@ -1199,7 +1186,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo if (handled && requestingContainer instanceof ActivityRecord) { final ActivityRecord activityRecord = (ActivityRecord) requestingContainer; - final boolean kept = mActivityDisplay.updateDisplayOverrideConfigurationLocked( + // TODO(display-merge): Remove cast + final boolean kept = ((ActivityDisplay) this).updateDisplayOverrideConfigurationLocked( config, activityRecord, false /* deferResume */, null /* result */); activityRecord.frozenBeforeDestroy = true; if (!kept) { @@ -1208,7 +1196,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } else { // We have a new configuration to push so we need to update ATMS for now. // TODO: Clean up display configuration push between ATMS and WMS after unification. - mActivityDisplay.updateDisplayOverrideConfigurationLocked( + // TODO(display-merge): Remove cast + ((ActivityDisplay) this.mDisplayContent).updateDisplayOverrideConfigurationLocked( config, null /* starting */, false /* deferResume */, null); } return handled; @@ -1797,12 +1786,15 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo return mTaskStackContainers.getHomeStack(); } + ActivityStack getRecentsStack() { + return mTaskStackContainers.getRecentsStack(); + } + /** - * @return The primary split-screen stack, but only if it is visible, and {@code null} otherwise. + * @return The primary split-screen stack, and {@code null} otherwise. */ ActivityStack getSplitScreenPrimaryStack() { - ActivityStack stack = mTaskStackContainers.getSplitScreenPrimaryStack(); - return (stack != null && stack.isVisible()) ? stack : null; + return mTaskStackContainers.getSplitScreenPrimaryStack(); } boolean hasSplitScreenPrimaryStack() { @@ -1841,6 +1833,22 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo return mTaskStackContainers.getStack(windowingMode, activityType); } + protected int getStackCount() { + return mTaskStackContainers.mChildren.size(); + } + + protected ActivityStack getStackAt(int index) { + return mTaskStackContainers.mChildren.get(index); + } + + int getIndexOf(ActivityStack stack) { + return mTaskStackContainers.getIndexOf(stack); + } + + void removeStack(ActivityStack stack) { + mTaskStackContainers.removeChild(stack); + } + @VisibleForTesting WindowList<ActivityStack> getStacks() { return mTaskStackContainers.mChildren; @@ -2208,11 +2216,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo stack.reparent(mTaskStackContainers, onTop ? POSITION_TOP: POSITION_BOTTOM); } - // TODO(display-unify): No longer needed then. - void removeStackFromDisplay(ActivityStack stack) { - mTaskStackContainers.removeChild(stack); - } - @Override protected void addChild(DisplayChildWindowContainer child, Comparator<DisplayChildWindowContainer> comparator) { @@ -2364,9 +2367,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo void removeImmediately() { mRemovingDisplay = true; try { - if (mActivityDisplay != null) { - mActivityDisplay.unregisterConfigurationChangeListener(this); - } if (mParentWindow != null) { mParentWindow.removeEmbeddedDisplayContent(this); } @@ -2386,7 +2386,9 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo mWindowingLayer.release(); mOverlayLayer.release(); mInputMonitor.onDisplayRemoved(); - mWmService.mDisplayNotificationController.dispatchDisplayRemoved(mActivityDisplay); + // TODO(display-merge): Remove cast + mWmService.mDisplayNotificationController + .dispatchDisplayRemoved((ActivityDisplay) this); } finally { mDisplayReady = false; mRemovingDisplay = false; @@ -2599,9 +2601,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } } - @CallSuper - @Override - public void writeToProto(ProtoOutputStream proto, long fieldId, + // TODO(proto-merge): Remove once protos for ActivityDisplay and DisplayContent are merged. + public void writeToProtoInner(ProtoOutputStream proto, long fieldId, @WindowTraceLogLevel int logLevel) { // Critical log level logs only visible elements to mitigate performance overheard if (logLevel == WindowTraceLogLevel.CRITICAL && !isVisible()) { @@ -3929,6 +3930,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo // Cached reference to some special stacks we tend to get a lot so we don't need to loop // through the list to find them. private ActivityStack mHomeStack = null; + private ActivityStack mRecentsStack = null; private ActivityStack mPinnedStack = null; private ActivityStack mSplitScreenPrimaryStack = null; @@ -3936,12 +3938,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo super(service); } - @Override - public void onConfigurationChanged(Configuration newParentConfig) { - // TODO(display-unify): Remove after unification. - onConfigurationChanged(newParentConfig, mActivityDisplay == null /*forwardToChildren*/); - } - /** * Returns the topmost stack on the display that is compatible with the input windowing mode * and activity type. Null is no compatible stack on the display. @@ -3976,6 +3972,10 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo ? mTaskStackContainers.getChildAt(mTaskStackContainers.getChildCount() - 1) : null; } + int getIndexOf(ActivityStack stack) { + return mTaskStackContainers.mChildren.indexOf(stack); + } + ActivityStack getHomeStack() { if (mHomeStack == null && mDisplayId == DEFAULT_DISPLAY) { Slog.e(TAG_WM, "getHomeStack: Returning null from this=" + this); @@ -3983,6 +3983,10 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo return mHomeStack; } + ActivityStack getRecentsStack() { + return mRecentsStack; + } + ActivityStack getPinnedStack() { return mPinnedStack; } @@ -4018,6 +4022,13 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } mHomeStack = stack; + } else if (stack.isActivityTypeRecents()) { + if (mRecentsStack != null && mRecentsStack != stack) { + throw new IllegalArgumentException( + "addStackReferenceIfNeeded: recents stack=" + mRecentsStack + + " already exist on display=" + this + " stack=" + stack); + } + mRecentsStack = stack; } final int windowingMode = stack.getWindowingMode(); if (windowingMode == WINDOWING_MODE_PINNED) { @@ -4034,17 +4045,23 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo + " already exist on display=" + this + " stack=" + stack); } mSplitScreenPrimaryStack = stack; + // TODO(display-merge): Remove cast + ((ActivityDisplay) this.mDisplayContent).onSplitScreenModeActivated(); mDividerControllerLocked.notifyDockedStackExistsChanged(true); } } - private void removeStackReferenceIfNeeded(ActivityStack stack) { + void removeStackReferenceIfNeeded(ActivityStack stack) { if (stack == mHomeStack) { mHomeStack = null; + } else if (stack == mRecentsStack) { + mRecentsStack = null; } else if (stack == mPinnedStack) { mPinnedStack = null; } else if (stack == mSplitScreenPrimaryStack) { mSplitScreenPrimaryStack = null; + // TODO(display-merge): Remove cast + ((ActivityDisplay) this.mDisplayContent).onSplitScreenModeDismissed(); // Re-set the split-screen create mode whenever the split-screen stack is removed. mWmService.setDockedStackCreateStateLocked( SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, null /* initialBounds */); @@ -4058,9 +4075,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo position = findPositionForStack(position, stack, true /* adding */); super.addChild(stack, position); - if (mActivityDisplay != null) { - mActivityDisplay.addChild(stack, position, true /*fromDc*/); - } // The reparenting case is handled in WindowContainer. if (!stack.mReparenting) { @@ -4072,9 +4086,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo @Override protected void removeChild(ActivityStack stack) { super.removeChild(stack); - if (mActivityDisplay != null) { - mActivityDisplay.onChildRemoved(stack); - } + // TODO(display-merge): Remove cast + ((ActivityDisplay) this.mDisplayContent).onStackRemoved(stack); removeStackReferenceIfNeeded(stack); } @@ -4087,7 +4100,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo @Override void positionChildAt(int position, ActivityStack child, boolean includingParents) { if (child.getWindowConfiguration().isAlwaysOnTop() - && position != POSITION_TOP) { + && position != POSITION_TOP && position != mChildren.size()) { // This stack is always-on-top, override the default behavior. Slog.w(TAG_WM, "Ignoring move of always-on-top stack=" + this + " to bottom"); @@ -4097,7 +4110,11 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo super.positionChildAt(currentPosition, child, false /* includingParents */); return; } - + // We don't allow untrusted display to top when task stack moves to top, + // until user tapping this display to change display position as top intentionally. + if (isUntrustedVirtualDisplay() && !getParent().isOnTop()) { + includingParents = false; + } final int targetPosition = findPositionForStack(position, child, false /* adding */); super.positionChildAt(targetPosition, child, includingParents); @@ -4133,7 +4150,12 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo final int topChildPosition = mChildren.size() - 1; int belowAlwaysOnTopPosition = POSITION_BOTTOM; for (int i = topChildPosition; i >= 0; --i) { - if (getStacks().get(i) != stack && !getStacks().get(i).isAlwaysOnTop()) { + // Since a stack could be repositioned while being one of the child, return + // current index if that's the same stack we are positioning and it is always on + // top. + final boolean sameStack = getStacks().get(i) == stack; + if ((sameStack && stack.isAlwaysOnTop()) + || (!sameStack && !getStacks().get(i).isAlwaysOnTop())) { belowAlwaysOnTopPosition = i; break; } @@ -4158,10 +4180,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo POSITION_BOTTOM ? belowAlwaysOnTopPosition : 0; } - int targetPosition = requestedPosition; - targetPosition = Math.min(targetPosition, maxPosition); - targetPosition = Math.max(targetPosition, minPosition); - // Cap the requested position to something reasonable for the previous position check // below. if (requestedPosition == POSITION_TOP) { @@ -4170,6 +4188,10 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo requestedPosition = 0; } + int targetPosition = requestedPosition; + targetPosition = Math.min(targetPosition, maxPosition); + targetPosition = Math.max(targetPosition, minPosition); + int prevPosition = getStacks().indexOf(stack); // The positions we calculated above (maxPosition, minPosition) do not take into // consideration the following edge cases. diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 77e557b87648..2283041367ef 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -2215,9 +2215,22 @@ public class DisplayPolicy { } private void offsetInputMethodWindowLw(WindowState win, DisplayFrames displayFrames) { + final int rotation = displayFrames.mRotation; + final int navBarPosition = navigationBarPosition(displayFrames.mDisplayWidth, + displayFrames.mDisplayHeight, rotation); + int top = Math.max(win.getDisplayFrameLw().top, win.getContentFrameLw().top); top += win.getGivenContentInsetsLw().top; displayFrames.mContent.bottom = Math.min(displayFrames.mContent.bottom, top); + if (navBarPosition == NAV_BAR_BOTTOM) { + // Always account for the nav bar frame height on the bottom since in all navigation + // modes we make room to show the dismiss-ime button, even if the IME does not report + // insets (ie. when floating) + final int uimode = mService.mPolicy.getUiMode(); + final int navFrameHeight = getNavigationBarFrameHeight(rotation, uimode); + displayFrames.mContent.bottom = Math.min(displayFrames.mContent.bottom, + displayFrames.mUnrestricted.bottom - navFrameHeight); + } displayFrames.mVoiceContent.bottom = Math.min(displayFrames.mVoiceContent.bottom, top); top = win.getVisibleFrameLw().top; top += win.getGivenVisibleInsetsLw().top; diff --git a/services/core/java/com/android/server/wm/DisplayWindowListenerController.java b/services/core/java/com/android/server/wm/DisplayWindowListenerController.java index 2da76ea64d85..78fea74da811 100644 --- a/services/core/java/com/android/server/wm/DisplayWindowListenerController.java +++ b/services/core/java/com/android/server/wm/DisplayWindowListenerController.java @@ -63,7 +63,7 @@ class DisplayWindowListenerController { mDisplayListeners.finishBroadcast(); } - void dispatchDisplayChanged(ActivityDisplay display, Configuration newConfig) { + void dispatchDisplayChanged(DisplayContent display, Configuration newConfig) { // Only report changed if this has actually been added to the hierarchy already. boolean isInHierarchy = false; for (int i = 0; i < display.getParent().getChildCount(); ++i) { @@ -78,7 +78,7 @@ class DisplayWindowListenerController { for (int i = 0; i < count; ++i) { try { mDisplayListeners.getBroadcastItem(i).onDisplayConfigurationChanged( - display.mDisplayId, newConfig); + display.getDisplayId(), newConfig); } catch (RemoteException e) { } } diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java index 7645de5dac74..318d88ef1df5 100644 --- a/services/core/java/com/android/server/wm/KeyguardController.java +++ b/services/core/java/com/android/server/wm/KeyguardController.java @@ -531,8 +531,8 @@ class KeyguardController { * occlusion state. */ private ActivityStack getStackForControllingOccluding(ActivityDisplay display) { - for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { - final ActivityStack stack = display.getChildAt(stackNdx); + for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = display.getStackAt(stackNdx); if (stack != null && stack.isFocusableAndVisible() && !stack.inPinnedWindowingMode()) { return stack; diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java index f4c867c4a448..d5bbe6bb8b9c 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimation.java +++ b/services/core/java/com/android/server/wm/RecentsAnimation.java @@ -475,8 +475,8 @@ class RecentsAnimation implements RecentsAnimationCallbacks, * @return The top stack that is not always-on-top. */ private ActivityStack getTopNonAlwaysOnTopStack() { - for (int i = mDefaultDisplay.getChildCount() - 1; i >= 0; i--) { - final ActivityStack s = mDefaultDisplay.getChildAt(i); + for (int i = mDefaultDisplay.getStackCount() - 1; i >= 0; i--) { + final ActivityStack s = mDefaultDisplay.getStackAt(i); if (s.getWindowConfiguration().isAlwaysOnTop()) { continue; } diff --git a/services/core/java/com/android/server/wm/RootActivityContainer.java b/services/core/java/com/android/server/wm/RootActivityContainer.java index 5fd59fa5ab6e..40e6dcc49c1e 100644 --- a/services/core/java/com/android/server/wm/RootActivityContainer.java +++ b/services/core/java/com/android/server/wm/RootActivityContainer.java @@ -52,7 +52,6 @@ import static com.android.server.wm.ActivityStackSupervisor.ON_TOP; import static com.android.server.wm.ActivityStackSupervisor.dumpHistoryList; import static com.android.server.wm.ActivityStackSupervisor.printThisActivity; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RECENTS; -import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RELEASE; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_STACK; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_STATES; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_TASKS; @@ -770,10 +769,12 @@ class RootActivityContainer extends ConfigurationContainer starting.frozenBeforeDestroy = true; } - if (displayContent != null && displayContent.mActivityDisplay != null) { + if (displayContent != null) { // Update the configuration of the activities on the display. - return displayContent.mActivityDisplay.updateDisplayOverrideConfigurationLocked(config, - starting, deferResume, null /* result */); + // TODO(display-merge): Remove cast + return ((ActivityDisplay) displayContent) + .updateDisplayOverrideConfigurationLocked(config, starting, deferResume, + null /* result */); } else { return true; } @@ -790,8 +791,8 @@ class RootActivityContainer extends ConfigurationContainer for (int i = mActivityDisplays.size() - 1; i >= 0; i--) { final ActivityDisplay display = mActivityDisplays.get(i); // Traverse all stacks on a display. - for (int j = display.getChildCount() - 1; j >= 0; --j) { - final ActivityStack stack = display.getChildAt(j); + for (int j = display.getStackCount() - 1; j >= 0; --j) { + final ActivityStack stack = display.getStackAt(j); // Get top activity from a visible stack and add it to the list. if (stack.shouldBeVisible(null /* starting */)) { final ActivityRecord top = stack.getTopNonFinishingActivity(); @@ -861,8 +862,8 @@ class RootActivityContainer extends ConfigurationContainer WindowProcessController fgApp = null; for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { final ActivityDisplay display = mActivityDisplays.get(displayNdx); - for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { - final ActivityStack stack = display.getChildAt(stackNdx); + for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = display.getStackAt(stackNdx); if (isTopDisplayFocusedStack(stack)) { final ActivityRecord resumedActivity = stack.getResumedActivity(); if (resumedActivity != null) { @@ -988,8 +989,8 @@ class RootActivityContainer extends ConfigurationContainer mStackSupervisor.mStartingUsers.add(uss); for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { final ActivityDisplay display = mActivityDisplays.get(displayNdx); - for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { - final ActivityStack stack = display.getChildAt(stackNdx); + for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = display.getStackAt(stackNdx); stack.switchUser(userId); Task task = stack.topTask(); if (task != null) { @@ -1056,7 +1057,7 @@ class RootActivityContainer extends ConfigurationContainer + " to its current displayId=" + displayId); } - if (activityDisplay.isSingleTaskInstance() && activityDisplay.getChildCount() > 0) { + if (activityDisplay.isSingleTaskInstance() && activityDisplay.getStackCount() > 0) { // We don't allow moving stacks to single instance display that already has a child. Slog.e(TAG, "Can not move stack=" + stack + " to single task instance display=" + activityDisplay); @@ -1229,8 +1230,8 @@ class RootActivityContainer extends ConfigurationContainer final ActivityDisplay display = mActivityDisplays.get(displayNdx); // It is possible that request to finish activity might also remove its task and stack, // so we need to be careful with indexes in the loop and check child count every time. - for (int stackNdx = 0; stackNdx < display.getChildCount(); ++stackNdx) { - final ActivityStack stack = display.getChildAt(stackNdx); + for (int stackNdx = 0; stackNdx < display.getStackCount(); ++stackNdx) { + final ActivityStack stack = display.getStackAt(stackNdx); final Task t = stack.finishTopCrashedActivityLocked(app, reason); if (stack == focusedStack || finishedTask == null) { finishedTask = t; @@ -1260,8 +1261,8 @@ class RootActivityContainer extends ConfigurationContainer for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { boolean resumedOnDisplay = false; final ActivityDisplay display = mActivityDisplays.get(displayNdx); - for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { - final ActivityStack stack = display.getChildAt(stackNdx); + for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = display.getStackAt(stackNdx); final ActivityRecord topRunningActivity = stack.topRunningActivityLocked(); if (!stack.isFocusableAndVisible() || topRunningActivity == null) { continue; @@ -1314,8 +1315,8 @@ class RootActivityContainer extends ConfigurationContainer } // Set the sleeping state of the stacks on the display. - for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { - final ActivityStack stack = display.getChildAt(stackNdx); + for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = display.getStackAt(stackNdx); if (displayShouldSleep) { stack.goToSleepIfPossible(false /* shuttingDown */); } else { @@ -1355,9 +1356,9 @@ class RootActivityContainer extends ConfigurationContainer } } - protected <T extends ActivityStack> T getStack(int stackId) { + protected ActivityStack getStack(int stackId) { for (int i = mActivityDisplays.size() - 1; i >= 0; --i) { - final T stack = mActivityDisplays.get(i).getStack(stackId); + final ActivityStack stack = mActivityDisplays.get(i).getStack(stackId); if (stack != null) { return stack; } @@ -1366,9 +1367,10 @@ class RootActivityContainer extends ConfigurationContainer } /** @see ActivityDisplay#getStack(int, int) */ - private <T extends ActivityStack> T getStack(int windowingMode, int activityType) { + ActivityStack getStack(int windowingMode, int activityType) { for (int i = mActivityDisplays.size() - 1; i >= 0; --i) { - final T stack = mActivityDisplays.get(i).getStack(windowingMode, activityType); + final ActivityStack stack = + mActivityDisplays.get(i).getStack(windowingMode, activityType); if (stack != null) { return stack; } @@ -1376,7 +1378,7 @@ class RootActivityContainer extends ConfigurationContainer return null; } - private <T extends ActivityStack> T getStack(int windowingMode, int activityType, + private ActivityStack getStack(int windowingMode, int activityType, int displayId) { ActivityDisplay display = getActivityDisplay(displayId); if (display == null) { @@ -1449,8 +1451,8 @@ class RootActivityContainer extends ConfigurationContainer if (displayId == INVALID_DISPLAY) { for (int displayNdx = 0; displayNdx < mActivityDisplays.size(); ++displayNdx) { final ActivityDisplay display = mActivityDisplays.get(displayNdx); - for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { - final ActivityStack stack = display.getChildAt(stackNdx); + for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = display.getStackAt(stackNdx); list.add(getStackInfo(stack)); } } @@ -1460,8 +1462,8 @@ class RootActivityContainer extends ConfigurationContainer if (display == null) { return list; } - for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { - final ActivityStack stack = display.getChildAt(stackNdx); + for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = display.getStackAt(stackNdx); list.add(getStackInfo(stack)); } return list; @@ -1549,9 +1551,9 @@ class RootActivityContainer extends ConfigurationContainer ActivityStack findStackBehind(ActivityStack stack) { final ActivityDisplay display = getActivityDisplay(stack.mDisplayId); if (display != null) { - for (int i = display.getChildCount() - 1; i >= 0; i--) { - if (display.getChildAt(i) == stack && i > 0) { - return display.getChildAt(i - 1); + for (int i = display.getStackCount() - 1; i >= 0; i--) { + if (display.getStackAt(i) == stack && i > 0) { + return display.getStackAt(i - 1); } } } @@ -1575,7 +1577,7 @@ class RootActivityContainer extends ConfigurationContainer } // TODO: remove after object merge with RootWindowContainer - void onChildPositionChanged(ActivityDisplay display, int position) { + void onChildPositionChanged(DisplayContent display, int position) { // Assume AM lock is held from positionChildAt of controller in each hierarchy. if (display != null) { positionChildAt(display, position); @@ -1583,18 +1585,20 @@ class RootActivityContainer extends ConfigurationContainer } /** Change the z-order of the given display. */ - private void positionChildAt(ActivityDisplay display, int position) { + private void positionChildAt(DisplayContent display, int position) { if (position >= mActivityDisplays.size()) { position = mActivityDisplays.size() - 1; } else if (position < 0) { position = 0; } + // TODO(display-merge): Remove cast + final ActivityDisplay activityDisplay = (ActivityDisplay) display; if (mActivityDisplays.isEmpty()) { - mActivityDisplays.add(display); + mActivityDisplays.add(activityDisplay); } else if (mActivityDisplays.get(position) != display) { mActivityDisplays.remove(display); - mActivityDisplays.add(position, display); + mActivityDisplays.add(position, activityDisplay); } mStackSupervisor.updateTopResumedActivityIfNeeded(); } @@ -1709,8 +1713,8 @@ class RootActivityContainer extends ConfigurationContainer void scheduleDestroyAllActivities(WindowProcessController app, String reason) { for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { final ActivityDisplay display = mActivityDisplays.get(displayNdx); - for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { - final ActivityStack stack = display.getChildAt(stackNdx); + for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = display.getStackAt(stackNdx); stack.scheduleDestroyActivities(app, reason); } } @@ -1722,15 +1726,15 @@ class RootActivityContainer extends ConfigurationContainer boolean allSleep = true; for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { final ActivityDisplay display = mActivityDisplays.get(displayNdx); - for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { + for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) { // Stacks and activities could be removed while putting activities to sleep if // the app process was gone. This prevents us getting exception by accessing an // invalid stack index. - if (stackNdx >= display.getChildCount()) { + if (stackNdx >= display.getStackCount()) { continue; } - final ActivityStack stack = display.getChildAt(stackNdx); + final ActivityStack stack = display.getStackAt(stackNdx); if (allowDelay) { allSleep &= stack.goToSleepIfPossible(shuttingDown); } else { @@ -1972,8 +1976,8 @@ class RootActivityContainer extends ConfigurationContainer r.getActivityType()); // Return the topmost valid stack on the display. - for (int i = activityDisplay.getChildCount() - 1; i >= 0; --i) { - final ActivityStack stack = activityDisplay.getChildAt(i); + for (int i = activityDisplay.getStackCount() - 1; i >= 0; --i) { + final ActivityStack stack = activityDisplay.getStackAt(i); if (isValidLaunchStack(stack, r, windowingMode)) { return stack; } @@ -2110,8 +2114,8 @@ class RootActivityContainer extends ConfigurationContainer boolean hasVisibleActivities = false; for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { final ActivityDisplay display = mActivityDisplays.get(displayNdx); - for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { - final ActivityStack stack = display.getChildAt(stackNdx); + for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = display.getStackAt(stackNdx); hasVisibleActivities |= stack.handleAppDiedLocked(app); } } @@ -2223,9 +2227,9 @@ class RootActivityContainer extends ConfigurationContainer void finishVoiceTask(IVoiceInteractionSession session) { for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { final ActivityDisplay display = mActivityDisplays.get(displayNdx); - final int numStacks = display.getChildCount(); + final int numStacks = display.getStackCount(); for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) { - final ActivityStack stack = display.getChildAt(stackNdx); + final ActivityStack stack = display.getStackAt(stackNdx); stack.finishVoiceTask(session); } } @@ -2290,8 +2294,8 @@ class RootActivityContainer extends ConfigurationContainer boolean foundResumed = false; for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { final ActivityDisplay display = mActivityDisplays.get(displayNdx); - for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { - final ActivityStack stack = display.getChildAt(stackNdx); + for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = display.getStackAt(stackNdx); final ActivityRecord r = stack.getResumedActivity(); if (r != null) { if (!r.nowVisible) { @@ -2308,8 +2312,8 @@ class RootActivityContainer extends ConfigurationContainer boolean pausing = true; for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { final ActivityDisplay display = mActivityDisplays.get(displayNdx); - for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { - final ActivityStack stack = display.getChildAt(stackNdx); + for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = display.getStackAt(stackNdx); final ActivityRecord r = stack.mPausingActivity; if (r != null && !r.isState(PAUSED, STOPPED, STOPPING)) { if (DEBUG_STATES) { @@ -2336,8 +2340,8 @@ class RootActivityContainer extends ConfigurationContainer try { for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { final ActivityDisplay display = mActivityDisplays.get(displayNdx); - for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { - final ActivityStack stack = display.getChildAt(stackNdx); + for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = display.getStackAt(stackNdx); final List<Task> tasks = stack.getAllTasks(); for (int taskNdx = tasks.size() - 1; taskNdx >= 0; taskNdx--) { final Task task = tasks.get(taskNdx); @@ -2380,8 +2384,8 @@ class RootActivityContainer extends ConfigurationContainer void cancelInitializingActivities() { for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { final ActivityDisplay display = mActivityDisplays.get(displayNdx); - for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { - final ActivityStack stack = display.getChildAt(stackNdx); + for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = display.getStackAt(stackNdx); stack.cancelInitializingActivities(); } } @@ -2413,8 +2417,8 @@ class RootActivityContainer extends ConfigurationContainer int numDisplays = mActivityDisplays.size(); for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) { final ActivityDisplay display = mActivityDisplays.get(displayNdx); - for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { - final ActivityStack stack = display.getChildAt(stackNdx); + for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = display.getStackAt(stackNdx); final Task task = stack.taskForIdLocked(id); if (task == null) { continue; @@ -2471,8 +2475,8 @@ class RootActivityContainer extends ConfigurationContainer int numDisplays = mActivityDisplays.size(); for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) { final ActivityDisplay display = mActivityDisplays.get(displayNdx); - for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { - final ActivityStack stack = display.getChildAt(stackNdx); + for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = display.getStackAt(stackNdx); final ActivityRecord r = stack.isInStackLocked(token); if (r != null) { return r; @@ -2554,8 +2558,8 @@ class RootActivityContainer extends ConfigurationContainer int numDisplays = mActivityDisplays.size(); for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) { final ActivityDisplay display = mActivityDisplays.get(displayNdx); - for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { - final ActivityStack stack = display.getChildAt(stackNdx); + for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = display.getStackAt(stackNdx); if (!dumpVisibleStacksOnly || stack.shouldBeVisible(null)) { activities.addAll(stack.getDumpActivitiesLocked(name)); } @@ -2606,8 +2610,8 @@ class RootActivityContainer extends ConfigurationContainer pw.print("Display #"); pw.print(activityDisplay.mDisplayId); pw.println(" (activities from top to bottom):"); final ActivityDisplay display = mActivityDisplays.get(displayNdx); - for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { - final ActivityStack stack = display.getChildAt(stackNdx); + for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = display.getStackAt(stackNdx); pw.println(); printed = stack.dump(fd, pw, dumpAll, dumpClient, dumpPackage, needSep); needSep = printed; diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 5ec9599cf7b1..5a10428964e7 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -37,7 +37,6 @@ import static com.android.server.wm.ProtoLogGroup.WM_SHOW_TRANSACTIONS; import static com.android.server.wm.RootWindowContainerProto.DISPLAYS; import static com.android.server.wm.RootWindowContainerProto.WINDOWS; import static com.android.server.wm.RootWindowContainerProto.WINDOW_CONTAINER; -import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DISPLAY; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_TRACE; @@ -71,7 +70,6 @@ import android.util.ArraySet; import android.util.Slog; import android.util.SparseIntArray; import android.util.proto.ProtoOutputStream; -import android.view.Display; import android.view.DisplayInfo; import android.view.SurfaceControl; import android.view.WindowManager; @@ -204,6 +202,12 @@ class RootWindowContainer extends WindowContainer<DisplayContent> } @Override + boolean isOnTop() { + // Considered always on top + return true; + } + + @Override void onChildPositionChanged(WindowContainer child) { mWmService.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, !mWmService.mPerDisplayFocusEnabled /* updateInputWindows */); @@ -219,36 +223,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent> return null; } - DisplayContent createDisplayContent(final Display display, ActivityDisplay activityDisplay) { - final int displayId = display.getDisplayId(); - - // In select scenarios, it is possible that a DisplayContent will be created on demand - // rather than waiting for the controller. In this case, associate the controller and return - // the existing display. - final DisplayContent existing = getDisplayContent(displayId); - - if (existing != null) { - existing.mActivityDisplay = activityDisplay; - existing.initializeDisplayOverrideConfiguration(); - return existing; - } - - final DisplayContent dc = new DisplayContent(display, mWmService, activityDisplay); - - if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Adding display=" + display); - - mWmService.mDisplayWindowSettings.applySettingsToDisplayLocked(dc); - dc.initializeDisplayOverrideConfiguration(); - - if (mWmService.mDisplayManagerInternal != null) { - mWmService.mDisplayManagerInternal.setDisplayInfoOverrideFromWindowManager( - displayId, dc.getDisplayInfo()); - dc.configureDisplayPolicy(); - } - - return dc; - } - /** * Called when DisplayWindowSettings values may change. */ @@ -262,7 +236,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent> continue; } - displayContent.initializeDisplayOverrideConfiguration(); displayContent.reconfigureDisplayLocked(); // We need to update global configuration as well if config of default display has @@ -1039,7 +1012,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> final int count = mChildren.size(); for (int i = 0; i < count; ++i) { final DisplayContent displayContent = mChildren.get(i); - displayContent.writeToProto(proto, DISPLAYS, logLevel); + displayContent.writeToProtoInner(proto, DISPLAYS, logLevel); } } if (logLevel == WindowTraceLogLevel.ALL) { @@ -1059,7 +1032,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> void positionChildAt(int position, DisplayContent child, boolean includingParents) { super.positionChildAt(position, child, includingParents); if (mRootActivityContainer != null) { - mRootActivityContainer.onChildPositionChanged(child.mActivityDisplay, position); + mRootActivityContainer.onChildPositionChanged(child, position); } } diff --git a/services/core/java/com/android/server/wm/RunningTasks.java b/services/core/java/com/android/server/wm/RunningTasks.java index f2678bbf0c9f..ca9d91e9bcab 100644 --- a/services/core/java/com/android/server/wm/RunningTasks.java +++ b/services/core/java/com/android/server/wm/RunningTasks.java @@ -52,8 +52,8 @@ class RunningTasks { final int numDisplays = activityDisplays.size(); for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) { final ActivityDisplay display = activityDisplays.get(displayNdx); - for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { - final ActivityStack stack = display.getChildAt(stackNdx); + for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = display.getStackAt(stackNdx); mTmpStackTasks.clear(); stack.getRunningTasks(mTmpStackTasks, ignoreActivityType, ignoreWindowingMode, callingUid, allowed, crossUser, profileIds); diff --git a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java index 8ad8972937cd..9d19cfe95f26 100644 --- a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java +++ b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java @@ -705,8 +705,8 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { private void adjustBoundsToAvoidConflictInDisplay(@NonNull ActivityDisplay display, @NonNull Rect inOutBounds) { final List<Rect> taskBoundsToCheck = new ArrayList<>(); - for (int i = 0; i < display.getChildCount(); ++i) { - final ActivityStack stack = display.getChildAt(i); + for (int i = 0; i < display.getStackCount(); ++i) { + final ActivityStack stack = display.getStackAt(i); if (!stack.inFreeformWindowingMode()) { continue; } diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 5c1491e3ac38..1b4ca4127bc6 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -288,10 +288,8 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< final DisplayContent dc = newParent.getDisplayContent(); mReparenting = true; - // Oddly enough we add to the new parent before removing from the old parent to avoid - // issues... - newParent.addChild(this, position); oldParent.removeChild(this); + newParent.addChild(this, position); mReparenting = false; // Relayout display(s) diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 4ec50f0a15b1..0a87fdaed3a1 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -1732,20 +1732,7 @@ public class WindowManagerService extends IWindowManager.Stub } } - DisplayContent displayContent = mRoot.getDisplayContent(displayId); - - // Create an instance if possible instead of waiting for the ActivityManagerService to drive - // the creation. - if (displayContent == null) { - final Display display = mDisplayManager.getDisplay(displayId); - - if (display != null) { - displayContent = mRoot.createDisplayContent(display, null /* activityDisplay */); - displayContent.reconfigureDisplayLocked(); - } - } - - return displayContent; + return mAtmService.mRootActivityContainer.getActivityDisplayOrCreate(displayId); } private boolean doesAddToastWindowRequireToken(String packageName, int callingUid, @@ -2350,9 +2337,8 @@ public class WindowManagerService extends IWindowManager.Stub win.setLastReportedMergedConfiguration(mergedConfiguration); - // Update the last frames and inset values here because the values are sent back to the - // client. The last values represent the last client state. - win.updateLastFrames(); + // Update the last inset values here because the values are sent back to the client. + // The last inset values represent the last client state win.updateLastInsetValues(); win.getCompatFrame(outFrame); @@ -7626,12 +7612,15 @@ public class WindowManagerService extends IWindowManager.Stub // it as if the host window was tapped. touchedWindow = mEmbeddedWindowController.getHostWindow(touchedToken); } - if (touchedWindow == null || !touchedWindow.canReceiveKeys()) { + + if (touchedWindow == null || !touchedWindow.canReceiveKeys(true /* fromUserTouch */)) { + // If the window that received the input event cannot receive keys, don't move the + // display it's on to the top since that window won't be able to get focus anyway. return; } - handleTaskFocusChange(touchedWindow.getTask()); handleDisplayFocusChange(touchedWindow); + handleTaskFocusChange(touchedWindow.getTask()); } private void handleTaskFocusChange(Task task) { @@ -7659,12 +7648,6 @@ public class WindowManagerService extends IWindowManager.Stub return; } - if (!window.canReceiveKeys()) { - // If the window that received the input event cannot receive keys, don't move the - // display it's on to the top since that window won't be able to get focus anyway. - return; - } - final WindowContainer parent = displayContent.getParent(); if (parent != null && parent.getTopChild() != displayContent) { parent.positionChildAt(WindowContainer.POSITION_TOP, displayContent, @@ -7676,7 +7659,8 @@ public class WindowManagerService extends IWindowManager.Stub // to do so because it seems possible to resume activities as part of a larger // transaction and it's too early to resume based on current order when performing // updateTopResumedActivityIfNeeded(). - displayContent.mActivityDisplay.ensureActivitiesVisible(null /* starting */, + // TODO(display-merge): Remove cast + ((ActivityDisplay) displayContent).ensureActivitiesVisible(null /* starting */, 0 /* configChanges */, !PRESERVE_WINDOWS, true /* notifyClients */); } } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index ce26e5f4bdc1..8cc0736b7537 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -2616,11 +2616,22 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP @Override public boolean canReceiveKeys() { - return isVisibleOrAdding() + return canReceiveKeys(false /* fromUserTouch */); + } + + public boolean canReceiveKeys(boolean fromUserTouch) { + final boolean canReceiveKeys = isVisibleOrAdding() && (mViewVisibility == View.VISIBLE) && !mRemoveOnExit && ((mAttrs.flags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) == 0) && (mActivityRecord == null || mActivityRecord.windowsAreFocusable()) && !cantReceiveTouchInput(); + if (!canReceiveKeys) { + return false; + } + // Do not allow untrusted virtual display to receive keys unless user intentionally + // touches the display. + return fromUserTouch || getDisplayContent().isOnTop() + || !getDisplayContent().isUntrustedVirtualDisplay(); } @Override diff --git a/services/core/xsd/platform-compat-config.xsd b/services/core/xsd/platform-compat-config.xsd index ee39e507aff1..a70568f5911a 100644 --- a/services/core/xsd/platform-compat-config.xsd +++ b/services/core/xsd/platform-compat-config.xsd @@ -28,6 +28,7 @@ <xs:attribute type="xs:string" name="name" use="required"/> <xs:attribute type="xs:boolean" name="disabled"/> <xs:attribute type="xs:int" name="enableAfterTargetSdk"/> + <xs:attribute type="xs:string" name="description"/> </xs:extension> </xs:simpleContent> </xs:complexType> diff --git a/services/core/xsd/platform-compat-schema/current.txt b/services/core/xsd/platform-compat-schema/current.txt index 84567851da2c..3a33f63361a5 100644 --- a/services/core/xsd/platform-compat-schema/current.txt +++ b/services/core/xsd/platform-compat-schema/current.txt @@ -3,11 +3,13 @@ package com.android.server.compat.config { public class Change { ctor public Change(); + method public String getDescription(); method public boolean getDisabled(); method public int getEnableAfterTargetSdk(); method public long getId(); method public String getName(); method public String getValue(); + method public void setDescription(String); method public void setDisabled(boolean); method public void setEnableAfterTargetSdk(int); method public void setId(long); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 2499ad816b2d..cb599be82aa6 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -139,6 +139,8 @@ import android.app.admin.SystemUpdatePolicy; import android.app.backup.IBackupManager; import android.app.timedetector.ManualTimeSuggestion; import android.app.timedetector.TimeDetector; +import android.app.timezonedetector.ManualTimeZoneSuggestion; +import android.app.timezonedetector.TimeZoneDetector; import android.app.trust.TrustManager; import android.app.usage.UsageStatsManagerInternal; import android.compat.annotation.ChangeId; @@ -1962,6 +1964,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return mContext.getSystemService(TimeDetector.class); } + TimeZoneDetector getTimeZoneDetector() { + return mContext.getSystemService(TimeZoneDetector.class); + } + ConnectivityManager getConnectivityManager() { return mContext.getSystemService(ConnectivityManager.class); } @@ -2748,6 +2754,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return null; } + // Code for handling failure from getActiveAdminWithPolicyForUidLocked to find an admin + // that satisfies the required policy. + // Throws a security exception with the right error message. if (who != null) { final int userId = UserHandle.getUserId(callingUid); final DevicePolicyData policy = getUserData(userId); @@ -2763,6 +2772,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { throw new SecurityException("Admin " + admin.info.getComponent() + " does not own the profile"); } + if (reqPolicy == DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER) { + throw new SecurityException("Admin " + admin.info.getComponent() + + " is not the profile owner on organization-owned device"); + } if (DA_DISALLOWED_POLICIES.contains(reqPolicy) && !isDeviceOwner && !isProfileOwner) { throw new SecurityException("Admin " + admin.info.getComponent() + " is not a device owner or profile owner, so may not use policy: " @@ -2869,12 +2882,16 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { ensureLocked(); final boolean ownsDevice = isDeviceOwner(admin.info.getComponent(), userId); final boolean ownsProfile = isProfileOwner(admin.info.getComponent(), userId); + final boolean ownsProfileOnOrganizationOwnedDevice = + isProfileOwnerOfOrganizationOwnedDevice(admin.info.getComponent(), userId); if (reqPolicy == DeviceAdminInfo.USES_POLICY_DEVICE_OWNER) { return ownsDevice; + } else if (reqPolicy == DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER) { + return ownsDevice || ownsProfileOnOrganizationOwnedDevice; } else if (reqPolicy == DeviceAdminInfo.USES_POLICY_PROFILE_OWNER) { // DO always has the PO power. - return ownsDevice || ownsProfile; + return ownsDevice || ownsProfileOnOrganizationOwnedDevice || ownsProfile; } else { boolean allowedToUsePolicy = ownsDevice || ownsProfile || !DA_DISALLOWED_POLICIES.contains(reqPolicy) @@ -5574,6 +5591,13 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } + private void enforceDeviceOwnerOrProfileOwnerOnOrganizationOwnedDevice(ComponentName who) { + synchronized (getLockObject()) { + getActiveAdminForCallerLocked( + who, DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER); + } + } + private void enforceProfileOwnerOfOrganizationOwnedDevice(ActiveAdmin admin) { if (!isProfileOwnerOfOrganizationOwnedDevice(admin)) { throw new SecurityException(String.format("Provided admin %s is either not a profile " @@ -8071,21 +8095,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return false; } - final int adminUserId = admin.getUserHandle().getIdentifier(); - - if (!isProfileOwner(admin.info.getComponent(), adminUserId)) { - Slog.w(LOG_TAG, String.format("%s is not profile owner of user %d", - admin.info.getComponent(), adminUserId)); - return false; - } - - if (!canProfileOwnerAccessDeviceIds(adminUserId)) { - Slog.w(LOG_TAG, String.format("Profile owner of user %d does not own the device.", - adminUserId)); - return false; - } + return isProfileOwnerOfOrganizationOwnedDevice( + admin.info.getComponent(), admin.getUserHandle().getIdentifier()); + } - return true; + private boolean isProfileOwnerOfOrganizationOwnedDevice(ComponentName who, int userId) { + return isProfileOwner(who, userId) && canProfileOwnerAccessDeviceIds(userId); } @Override @@ -11123,8 +11138,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (mInjector.settingsGlobalGetInt(Global.AUTO_TIME_ZONE, 0) == 1) { return false; } + ManualTimeZoneSuggestion manualTimeZoneSuggestion = + TimeZoneDetector.createManualTimeZoneSuggestion( + timeZone, "DevicePolicyManagerService: setTimeZone"); mInjector.binderWithCleanCallingIdentity(() -> - mInjector.getAlarmManager().setTimeZone(timeZone)); + mInjector.getTimeZoneDetector().suggestManualTimeZone(manualTimeZoneSuggestion)); return true; } @@ -12377,7 +12395,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public String getWifiMacAddress(ComponentName admin) { // Make sure caller has DO. - enforceDeviceOwner(admin); + enforceDeviceOwnerOrProfileOwnerOnOrganizationOwnedDevice(admin); final long ident = mInjector.binderClearCallingIdentity(); try { diff --git a/services/net/java/android/net/ip/IpClientCallbacks.java b/services/net/java/android/net/ip/IpClientCallbacks.java index db01ae4d4d9c..61cd88aac921 100644 --- a/services/net/java/android/net/ip/IpClientCallbacks.java +++ b/services/net/java/android/net/ip/IpClientCallbacks.java @@ -17,8 +17,11 @@ package android.net.ip; import android.net.DhcpResults; +import android.net.Layer2PacketParcelable; import android.net.LinkProperties; +import java.util.List; + /** * Callbacks for handling IpClient events. * @@ -116,4 +119,9 @@ public class IpClientCallbacks { * whenever 464xlat is being started or stopped. */ public void setNeighborDiscoveryOffload(boolean enable) {} + + /** + * Invoked on starting preconnection process. + */ + public void onPreconnectionStart(List<Layer2PacketParcelable> packets) {} } diff --git a/services/net/java/android/net/ip/IpClientManager.java b/services/net/java/android/net/ip/IpClientManager.java index 1e653cd29aed..4b7ed3c7b72f 100644 --- a/services/net/java/android/net/ip/IpClientManager.java +++ b/services/net/java/android/net/ip/IpClientManager.java @@ -234,7 +234,7 @@ public class IpClientManager { slot, KeepalivePacketDataUtil.toStableParcelable(pkt)); return true; } catch (RemoteException e) { - log("Error adding Keepalive Packet Filter ", e); + log("Error adding NAT-T Keepalive Packet Filter ", e); return false; } finally { Binder.restoreCallingIdentity(token); @@ -272,4 +272,22 @@ public class IpClientManager { Binder.restoreCallingIdentity(token); } } + + /** + * Notify IpClient that preconnection is complete and that the link is ready for use. + * The success parameter indicates whether the packets passed in by 'onPreconnectionStart' + * were successfully sent to the network or not. + */ + public boolean notifyPreconnectionComplete(boolean success) { + final long token = Binder.clearCallingIdentity(); + try { + mIpClient.notifyPreconnectionComplete(success); + return true; + } catch (RemoteException e) { + log("Error notifying IpClient Preconnection completed", e); + return false; + } finally { + Binder.restoreCallingIdentity(token); + } + } } diff --git a/services/net/java/android/net/ip/IpClientUtil.java b/services/net/java/android/net/ip/IpClientUtil.java index 714ade12435b..4d60e6239376 100644 --- a/services/net/java/android/net/ip/IpClientUtil.java +++ b/services/net/java/android/net/ip/IpClientUtil.java @@ -20,12 +20,14 @@ import static android.net.shared.IpConfigurationParcelableUtil.fromStableParcela import android.content.Context; import android.net.DhcpResultsParcelable; +import android.net.Layer2PacketParcelable; import android.net.LinkProperties; import android.net.NetworkStackClient; import android.os.ConditionVariable; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.util.List; /** @@ -176,6 +178,12 @@ public class IpClientUtil { mCb.setNeighborDiscoveryOffload(enable); } + // Invoked on starting preconnection process. + @Override + public void onPreconnectionStart(List<Layer2PacketParcelable> packets) { + mCb.onPreconnectionStart(packets); + } + @Override public int getInterfaceVersion() { return this.VERSION; diff --git a/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java index 37f5b87ac115..335217719cc9 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java @@ -16,18 +16,39 @@ package com.android.server.accessibility; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; import android.accessibilityservice.AccessibilityService; +import android.app.PendingIntent; +import android.app.PendingIntent.CanceledException; +import android.app.RemoteAction; import android.app.StatusBarManager; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.graphics.drawable.Icon; import android.os.Handler; +import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; + +import androidx.test.InstrumentationRegistry; import com.android.internal.util.ScreenshotHelper; +import com.android.server.LocalServices; +import com.android.server.statusbar.StatusBarManagerInternal; import com.android.server.wm.WindowManagerInternal; import org.junit.Before; @@ -35,55 +56,290 @@ import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + /** * Tests for SystemActionPerformer */ public class SystemActionPerformerTest { - SystemActionPerformer mSystemActionPerformer; + private static final int LATCH_TIMEOUT_MS = 500; + private static final int LEGACY_SYSTEM_ACTION_COUNT = 9; + private static final int NEW_ACTION_ID = 20; + private static final String LABEL_1 = "label1"; + private static final String LABEL_2 = "label2"; + private static final String INTENT_ACTION1 = "TESTACTION1"; + private static final String INTENT_ACTION2 = "TESTACTION2"; + private static final String DESCRIPTION1 = "description1"; + private static final String DESCRIPTION2 = "description2"; + private static final PendingIntent TEST_PENDING_INTENT_1 = PendingIntent.getBroadcast( + InstrumentationRegistry.getTargetContext(), 0, new Intent(INTENT_ACTION1), 0); + private static final RemoteAction NEW_TEST_ACTION_1 = new RemoteAction( + Icon.createWithContentUri("content://test"), + LABEL_1, + DESCRIPTION1, + TEST_PENDING_INTENT_1); + private static final PendingIntent TEST_PENDING_INTENT_2 = PendingIntent.getBroadcast( + InstrumentationRegistry.getTargetContext(), 0, new Intent(INTENT_ACTION2), 0); + private static final RemoteAction NEW_TEST_ACTION_2 = new RemoteAction( + Icon.createWithContentUri("content://test"), + LABEL_2, + DESCRIPTION2, + TEST_PENDING_INTENT_2); + + private static final AccessibilityAction NEW_ACCESSIBILITY_ACTION = + new AccessibilityAction(NEW_ACTION_ID, LABEL_1); + private static final AccessibilityAction LEGACY_NOTIFICATIONS_ACCESSIBILITY_ACTION = + new AccessibilityAction(AccessibilityService.GLOBAL_ACTION_NOTIFICATIONS, LABEL_1); + private static final AccessibilityAction LEGACY_HOME_ACCESSIBILITY_ACTION = + new AccessibilityAction(AccessibilityService.GLOBAL_ACTION_HOME, LABEL_2); - @Mock Context mMockContext; - @Mock WindowManagerInternal mMockWindowManagerInternal; - @Mock StatusBarManager mMockStatusBarManager; - @Mock ScreenshotHelper mMockScreenshotHelper; + private SystemActionPerformer mSystemActionPerformer; + + @Mock private Context mMockContext; + @Mock private StatusBarManagerInternal mMockStatusBarManagerInternal; + @Mock private WindowManagerInternal mMockWindowManagerInternal; + @Mock private StatusBarManager mMockStatusBarManager; + @Mock private ScreenshotHelper mMockScreenshotHelper; + @Mock private SystemActionPerformer.SystemActionsChangedListener mMockListener; @Before public void setup() { MockitoAnnotations.initMocks(this); + LocalServices.removeServiceForTest(StatusBarManagerInternal.class); + LocalServices.addService(StatusBarManagerInternal.class, mMockStatusBarManagerInternal); + } + + private void setupWithMockContext() { + doReturn(mMockStatusBarManager).when( + mMockContext).getSystemService(android.app.Service.STATUS_BAR_SERVICE); + doReturn(InstrumentationRegistry.getContext().getResources()).when( + mMockContext).getResources(); + mSystemActionPerformer = new SystemActionPerformer( + mMockContext, + mMockWindowManagerInternal, + () -> mMockScreenshotHelper, + mMockListener); + } + + private void setupWithRealContext() { + mSystemActionPerformer = new SystemActionPerformer( + InstrumentationRegistry.getContext(), + mMockWindowManagerInternal, + () -> mMockScreenshotHelper, + mMockListener); + } + + // We need below two help functions because AccessbilityAction.equals function only compares + // action ids. To verify the test result here, we are also looking at action labels. + private void assertHasLegacyAccessibilityAction( + List<AccessibilityAction> actions, AccessibilityAction action) { + boolean foundAction = false; + for (AccessibilityAction a : actions) { + if ((a.getId() == action.getId()) && (a.getLabel().equals(action.getLabel()))) { + foundAction = true; + break; + } + } + assertTrue(foundAction); + } + + private void assertHasNoLegacyAccessibilityAction( + List<AccessibilityAction> actions, AccessibilityAction action) { + boolean foundAction = false; + for (AccessibilityAction a : actions) { + if ((a.getId() == action.getId()) && (a.getLabel().equals(action.getLabel()))) { + foundAction = true; + break; + } + } + assertFalse(foundAction); + } + + @Test + public void testRegisterSystemAction_addedIntoAvailableSystemActions() { + setupWithRealContext(); + // Before any new system action is registered, getSystemActions returns all legacy actions + List<AccessibilityAction> actions = mSystemActionPerformer.getSystemActions(); + assertEquals(LEGACY_SYSTEM_ACTION_COUNT, actions.size()); + // Register a new system action + mSystemActionPerformer.registerSystemAction(NEW_ACTION_ID, NEW_TEST_ACTION_1); + actions = mSystemActionPerformer.getSystemActions(); + assertEquals(LEGACY_SYSTEM_ACTION_COUNT + 1, actions.size()); + assertThat(actions, hasItem(NEW_ACCESSIBILITY_ACTION)); + } + + @Test + public void testRegisterSystemAction_overrideLegacyAction() { + setupWithRealContext(); + // Before any new system action is registered, getSystemActions returns all legacy actions + List<AccessibilityAction> actions = mSystemActionPerformer.getSystemActions(); + assertEquals(LEGACY_SYSTEM_ACTION_COUNT, actions.size()); + // Overriding a legacy system action using legacy notification action id + mSystemActionPerformer.registerSystemAction( + AccessibilityService.GLOBAL_ACTION_NOTIFICATIONS, NEW_TEST_ACTION_1); + actions = mSystemActionPerformer.getSystemActions(); + assertEquals(LEGACY_SYSTEM_ACTION_COUNT, actions.size()); + assertHasLegacyAccessibilityAction(actions, LEGACY_NOTIFICATIONS_ACCESSIBILITY_ACTION); + } + + @Test + public void testUnregisterSystemAction_removeFromAvailableSystemActions() { + setupWithRealContext(); + // Before any new system action is registered, getSystemActions returns all legacy actions + List<AccessibilityAction> actions = mSystemActionPerformer.getSystemActions(); + assertEquals(LEGACY_SYSTEM_ACTION_COUNT, actions.size()); + // Register a new system action + mSystemActionPerformer.registerSystemAction(NEW_ACTION_ID, NEW_TEST_ACTION_1); + actions = mSystemActionPerformer.getSystemActions(); + assertEquals(LEGACY_SYSTEM_ACTION_COUNT + 1, actions.size()); + + mSystemActionPerformer.unregisterSystemAction(NEW_ACTION_ID); + actions = mSystemActionPerformer.getSystemActions(); + assertEquals(LEGACY_SYSTEM_ACTION_COUNT, actions.size()); + assertThat(actions, is(not(hasItem(NEW_ACCESSIBILITY_ACTION)))); + } - when(mMockContext.getSystemService(android.app.Service.STATUS_BAR_SERVICE)) - .thenReturn(mMockStatusBarManager); + @Test + public void testUnregisterSystemAction_removeOverrideForLegacyAction() { + setupWithRealContext(); + + // Overriding a legacy system action + mSystemActionPerformer.registerSystemAction( + AccessibilityService.GLOBAL_ACTION_NOTIFICATIONS, NEW_TEST_ACTION_1); + List<AccessibilityAction> actions = mSystemActionPerformer.getSystemActions(); + assertEquals(LEGACY_SYSTEM_ACTION_COUNT, actions.size()); + assertHasLegacyAccessibilityAction(actions, LEGACY_NOTIFICATIONS_ACCESSIBILITY_ACTION); + + // Remove the overriding action using legacy action id + mSystemActionPerformer.unregisterSystemAction( + AccessibilityService.GLOBAL_ACTION_NOTIFICATIONS); + actions = mSystemActionPerformer.getSystemActions(); + assertEquals(LEGACY_SYSTEM_ACTION_COUNT, actions.size()); + assertHasNoLegacyAccessibilityAction(actions, LEGACY_NOTIFICATIONS_ACCESSIBILITY_ACTION); + } + + @Test + public void testPerformSystemActionNewAction() throws CanceledException { + setupWithRealContext(); - mSystemActionPerformer = - new SystemActionPerformer(mMockContext, mMockWindowManagerInternal, - () -> mMockScreenshotHelper); + final CountDownLatch latch = new CountDownLatch(1); + mSystemActionPerformer.registerSystemAction(NEW_ACTION_ID, NEW_TEST_ACTION_1); + TestBroadcastReceiver br = new TestBroadcastReceiver(latch); + br.register(InstrumentationRegistry.getTargetContext()); + mSystemActionPerformer.performSystemAction(NEW_ACTION_ID); + try { + latch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + fail("RemoteAction should be triggered."); + } finally { + br.unregister(InstrumentationRegistry.getTargetContext()); + } } @Test - public void testNotifications_expandsNotificationPanel() { + public void testPerformSystemActionOverrideLegacyActionUsingLegacyActionId() + throws CanceledException { + setupWithRealContext(); + + final CountDownLatch latch = new CountDownLatch(1); + mSystemActionPerformer.registerSystemAction( + AccessibilityService.GLOBAL_ACTION_RECENTS, NEW_TEST_ACTION_1); + TestBroadcastReceiver br = new TestBroadcastReceiver(latch); + br.register(InstrumentationRegistry.getTargetContext()); + mSystemActionPerformer.performSystemAction(AccessibilityService.GLOBAL_ACTION_RECENTS); + try { + latch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + fail("RemoteAction should be triggered."); + } finally { + br.unregister(InstrumentationRegistry.getTargetContext()); + } + verify(mMockStatusBarManagerInternal, never()).toggleRecentApps(); + // Now revert to legacy action + mSystemActionPerformer.unregisterSystemAction(AccessibilityService.GLOBAL_ACTION_RECENTS); + mSystemActionPerformer.performSystemAction(AccessibilityService.GLOBAL_ACTION_RECENTS); + verify(mMockStatusBarManagerInternal).toggleRecentApps(); + } + + @Test + public void testNotifications_expandsNotificationPanel_legacy() { + setupWithMockContext(); mSystemActionPerformer .performSystemAction(AccessibilityService.GLOBAL_ACTION_NOTIFICATIONS); verify(mMockStatusBarManager).expandNotificationsPanel(); } @Test - public void testQuickSettings_requestsQuickSettingsPanel() { + public void testQuickSettings_requestsQuickSettingsPanel_legacy() { + setupWithMockContext(); mSystemActionPerformer .performSystemAction(AccessibilityService.GLOBAL_ACTION_QUICK_SETTINGS); verify(mMockStatusBarManager).expandSettingsPanel(); } @Test - public void testPowerDialog_requestsFromWindowManager() { + public void testRecentApps_legacy() { + setupWithRealContext(); + mSystemActionPerformer.performSystemAction(AccessibilityService.GLOBAL_ACTION_RECENTS); + verify(mMockStatusBarManagerInternal).toggleRecentApps(); + } + + @Test + public void testPowerDialog_requestsFromWindowManager_legacy() { + setupWithMockContext(); mSystemActionPerformer.performSystemAction(AccessibilityService.GLOBAL_ACTION_POWER_DIALOG); verify(mMockWindowManagerInternal).showGlobalActions(); } @Test - public void testScreenshot_requestsFromScreenshotHelper() { + public void testToggleSplitScreen_legacy() { + setupWithRealContext(); + mSystemActionPerformer.performSystemAction( + AccessibilityService.GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN); + verify(mMockStatusBarManagerInternal).toggleSplitScreen(); + } + + @Test + public void testScreenshot_requestsFromScreenshotHelper_legacy() { + setupWithMockContext(); mSystemActionPerformer.performSystemAction( AccessibilityService.GLOBAL_ACTION_TAKE_SCREENSHOT); verify(mMockScreenshotHelper).takeScreenshot( eq(android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN), anyBoolean(), anyBoolean(), any(Handler.class), any()); } + + // PendingIntent is a final class and cannot be mocked. So we are using this + // Broadcast receiver to verify the registered remote action is called correctly. + private static final class TestBroadcastReceiver extends BroadcastReceiver { + private CountDownLatch mLatch; + private boolean mRegistered; + private final IntentFilter mFilter; + + TestBroadcastReceiver(CountDownLatch latch) { + mLatch = latch; + mRegistered = false; + mFilter = new IntentFilter(INTENT_ACTION1); + } + + @Override + public void onReceive(Context context, Intent intent) { + mLatch.countDown(); + } + + void register(Context context) { + if (!mRegistered) { + context.registerReceiver(this, mFilter); + mRegistered = true; + } + } + + void unregister(Context context) { + if (mRegistered) { + context.unregisterReceiver(this); + } + } + } } diff --git a/services/tests/servicestests/src/com/android/server/appsearch/impl/FakeIcingTest.java b/services/tests/servicestests/src/com/android/server/appsearch/impl/FakeIcingTest.java index adef02ee55d7..6f2de7f50379 100644 --- a/services/tests/servicestests/src/com/android/server/appsearch/impl/FakeIcingTest.java +++ b/services/tests/servicestests/src/com/android/server/appsearch/impl/FakeIcingTest.java @@ -21,6 +21,7 @@ import androidx.test.runner.AndroidJUnit4; import com.google.android.icing.proto.DocumentProto; import com.google.android.icing.proto.PropertyProto; +import com.google.android.icing.proto.SearchResultProto; import org.junit.Test; import org.junit.runner.RunWith; @@ -115,8 +116,9 @@ public class FakeIcingTest { private static List<String> queryGetUris(FakeIcing icing, String term) { List<String> uris = new ArrayList<>(); - for (DocumentProto result : icing.query(term)) { - uris.add(result.getUri()); + SearchResultProto results = icing.query(term); + for (SearchResultProto.ResultProto result : results.getResultsList()) { + uris.add(result.getDocument().getUri()); } return uris; } diff --git a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java index f8c87fcb4ef6..72679769be8e 100644 --- a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java +++ b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java @@ -73,36 +73,36 @@ public class CompatConfigTest { @Test public void testDisabledChangeDisabled() { CompatConfig pc = new CompatConfig(); - pc.addChange(new CompatChange(1234L, "MY_CHANGE", -1, true)); + pc.addChange(new CompatChange(1234L, "MY_CHANGE", -1, true, "")); assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 1))).isFalse(); } @Test public void testTargetSdkChangeDisabled() { CompatConfig pc = new CompatConfig(); - pc.addChange(new CompatChange(1234L, "MY_CHANGE", 2, false)); + pc.addChange(new CompatChange(1234L, "MY_CHANGE", 2, false, null)); assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 2))).isFalse(); } @Test public void testTargetSdkChangeEnabled() { CompatConfig pc = new CompatConfig(); - pc.addChange(new CompatChange(1234L, "MY_CHANGE", 2, false)); + pc.addChange(new CompatChange(1234L, "MY_CHANGE", 2, false, "")); assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 3))).isTrue(); } @Test public void testDisabledOverrideTargetSdkChange() { CompatConfig pc = new CompatConfig(); - pc.addChange(new CompatChange(1234L, "MY_CHANGE", 2, true)); + pc.addChange(new CompatChange(1234L, "MY_CHANGE", 2, true, null)); assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 3))).isFalse(); } @Test public void testGetDisabledChanges() { CompatConfig pc = new CompatConfig(); - pc.addChange(new CompatChange(1234L, "MY_CHANGE", -1, true)); - pc.addChange(new CompatChange(2345L, "OTHER_CHANGE", -1, false)); + pc.addChange(new CompatChange(1234L, "MY_CHANGE", -1, true, null)); + pc.addChange(new CompatChange(2345L, "OTHER_CHANGE", -1, false, null)); assertThat(pc.getDisabledChanges( makeAppInfo("com.some.package", 2))).asList().containsExactly(1234L); } @@ -110,9 +110,9 @@ public class CompatConfigTest { @Test public void testGetDisabledChangesSorted() { CompatConfig pc = new CompatConfig(); - pc.addChange(new CompatChange(1234L, "MY_CHANGE", 2, true)); - pc.addChange(new CompatChange(123L, "OTHER_CHANGE", 2, true)); - pc.addChange(new CompatChange(12L, "THIRD_CHANGE", 2, true)); + pc.addChange(new CompatChange(1234L, "MY_CHANGE", 2, true, null)); + pc.addChange(new CompatChange(123L, "OTHER_CHANGE", 2, true, null)); + pc.addChange(new CompatChange(12L, "THIRD_CHANGE", 2, true, null)); assertThat(pc.getDisabledChanges( makeAppInfo("com.some.package", 2))).asList().containsExactly(12L, 123L, 1234L); } @@ -120,7 +120,7 @@ public class CompatConfigTest { @Test public void testPackageOverrideEnabled() { CompatConfig pc = new CompatConfig(); - pc.addChange(new CompatChange(1234L, "MY_CHANGE", -1, true)); // disabled + pc.addChange(new CompatChange(1234L, "MY_CHANGE", -1, true, null)); // disabled pc.addOverride(1234L, "com.some.package", true); assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 2))).isTrue(); assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.other.package", 2))).isFalse(); @@ -129,7 +129,7 @@ public class CompatConfigTest { @Test public void testPackageOverrideDisabled() { CompatConfig pc = new CompatConfig(); - pc.addChange(new CompatChange(1234L, "MY_CHANGE", -1, false)); + pc.addChange(new CompatChange(1234L, "MY_CHANGE", -1, false, null)); pc.addOverride(1234L, "com.some.package", false); assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 2))).isFalse(); assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.other.package", 2))).isTrue(); @@ -152,7 +152,7 @@ public class CompatConfigTest { @Test public void testRemovePackageOverride() { CompatConfig pc = new CompatConfig(); - pc.addChange(new CompatChange(1234L, "MY_CHANGE", -1, false)); + pc.addChange(new CompatChange(1234L, "MY_CHANGE", -1, false, null)); pc.addOverride(1234L, "com.some.package", false); pc.removeOverride(1234L, "com.some.package"); assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 2))).isTrue(); @@ -161,8 +161,8 @@ public class CompatConfigTest { @Test public void testLookupChangeId() { CompatConfig pc = new CompatConfig(); - pc.addChange(new CompatChange(1234L, "MY_CHANGE", -1, false)); - pc.addChange(new CompatChange(2345L, "ANOTHER_CHANGE", -1, false)); + pc.addChange(new CompatChange(1234L, "MY_CHANGE", -1, false, null)); + pc.addChange(new CompatChange(2345L, "ANOTHER_CHANGE", -1, false, null)); assertThat(pc.lookupChangeId("MY_CHANGE")).isEqualTo(1234L); } @@ -174,8 +174,9 @@ public class CompatConfigTest { @Test public void testReadConfig() { - Change[] changes = {new Change(1234L, "MY_CHANGE1", false, 2), new Change(1235L, - "MY_CHANGE2", true, null), new Change(1236L, "MY_CHANGE3", false, null)}; + Change[] changes = {new Change(1234L, "MY_CHANGE1", false, 2, null), new Change(1235L, + "MY_CHANGE2", true, null, "description"), new Change(1236L, "MY_CHANGE3", false, + null, "")}; File dir = createTempDir(); writeChangesToFile(changes, new File(dir.getPath() + "/platform_compat_config.xml")); @@ -191,9 +192,9 @@ public class CompatConfigTest { @Test public void testReadConfigMultipleFiles() { - Change[] changes1 = {new Change(1234L, "MY_CHANGE1", false, 2)}; - Change[] changes2 = {new Change(1235L, "MY_CHANGE2", true, null), new Change(1236L, - "MY_CHANGE3", false, null)}; + Change[] changes1 = {new Change(1234L, "MY_CHANGE1", false, 2, null)}; + Change[] changes2 = {new Change(1235L, "MY_CHANGE2", true, null, ""), new Change(1236L, + "MY_CHANGE3", false, null, null)}; File dir = createTempDir(); writeChangesToFile(changes1, diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java index f86bacf67901..ac555fda2204 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java @@ -23,6 +23,7 @@ import android.app.NotificationManager; import android.app.PendingIntent; import android.app.backup.IBackupManager; import android.app.timedetector.TimeDetector; +import android.app.timezonedetector.TimeZoneDetector; import android.app.usage.UsageStatsManagerInternal; import android.content.Context; import android.content.Intent; @@ -235,6 +236,11 @@ public class DevicePolicyManagerServiceTestable extends DevicePolicyManagerServi } @Override + TimeZoneDetector getTimeZoneDetector() { + return services.timeZoneDetector; + } + + @Override LockPatternUtils newLockPatternUtils() { return services.lockPatternUtils; } diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index 059002094bc0..06b8716c0926 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -64,6 +64,8 @@ import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManagerInternal; import android.app.admin.PasswordMetrics; import android.app.timedetector.ManualTimeSuggestion; +import android.app.timezonedetector.ManualTimeZoneSuggestion; +import android.app.timezonedetector.TimeZoneDetector; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Intent; @@ -138,6 +140,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { permission.MANAGE_USERS, permission.INTERACT_ACROSS_USERS_FULL); public static final String NOT_DEVICE_OWNER_MSG = "does not own the device"; public static final String NOT_PROFILE_OWNER_MSG = "does not own the profile"; + public static final String NOT_ORG_OWNED_PROFILE_OWNER_MSG = + "not the profile owner on organization-owned device"; public static final String ONGOING_CALL_MSG = "ongoing call on the device"; // TODO replace all instances of this with explicit {@link #mServiceContext}. @@ -2114,12 +2118,14 @@ public class DevicePolicyManagerTest extends DpmTestBase { assertTrue(dpm.isAdminActive(admin1)); // Test 2. Caller has DA, but not DO. - assertExpectException(SecurityException.class, /* messageRegex= */ NOT_DEVICE_OWNER_MSG, + assertExpectException(SecurityException.class, + /* messageRegex= */ NOT_ORG_OWNED_PROFILE_OWNER_MSG, () -> dpm.getWifiMacAddress(admin1)); // Test 3. Caller has PO, but not DO. assertTrue(dpm.setProfileOwner(admin1, null, UserHandle.USER_SYSTEM)); - assertExpectException(SecurityException.class, /* messageRegex= */ NOT_DEVICE_OWNER_MSG, + assertExpectException(SecurityException.class, + /* messageRegex= */ NOT_ORG_OWNED_PROFILE_OWNER_MSG, () -> dpm.getWifiMacAddress(admin1)); // Remove PO. @@ -2141,6 +2147,15 @@ public class DevicePolicyManagerTest extends DpmTestBase { assertEquals("11:22:33:44:55:66", dpm.getWifiMacAddress(admin1)); } + public void testGetMacAddressByOrgOwnedPO() throws Exception { + setupProfileOwner(); + configureProfileOwnerOfOrgOwnedDevice(admin1, DpmMockContext.CALLER_USER_HANDLE); + + final String[] macAddresses = new String[]{"11:22:33:44:55:66"}; + when(getServices().wifiManager.getFactoryMacAddresses()).thenReturn(macAddresses); + assertEquals("11:22:33:44:55:66", dpm.getWifiMacAddress(admin1)); + } + public void testReboot() throws Exception { mContext.callerPermissions.add(permission.MANAGE_DEVICE_ADMINS); mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS); @@ -3694,7 +3709,9 @@ public class DevicePolicyManagerTest extends DpmTestBase { mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; setupDeviceOwner(); dpm.setTimeZone(admin1, "Asia/Shanghai"); - verify(getServices().alarmManager).setTimeZone("Asia/Shanghai"); + ManualTimeZoneSuggestion suggestion = + TimeZoneDetector.createManualTimeZoneSuggestion("Asia/Shanghai", "Test debug info"); + verify(getServices().timeZoneDetector).suggestManualTimeZone(suggestion); } public void testSetTimeZoneFailWithPO() throws Exception { diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java index c9273642635e..6a0d9265f594 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java @@ -32,6 +32,7 @@ import android.app.IActivityTaskManager; import android.app.NotificationManager; import android.app.backup.IBackupManager; import android.app.timedetector.TimeDetector; +import android.app.timezonedetector.TimeZoneDetector; import android.app.usage.UsageStatsManagerInternal; import android.content.BroadcastReceiver; import android.content.ContentValues; @@ -113,6 +114,7 @@ public class MockSystemServices { public final AccountManager accountManager; public final AlarmManager alarmManager; public final TimeDetector timeDetector; + public final TimeZoneDetector timeZoneDetector; public final KeyChain.KeyChainConnection keyChainConnection; /** Note this is a partial mock, not a real mock. */ public final PackageManager packageManager; @@ -155,6 +157,7 @@ public class MockSystemServices { accountManager = mock(AccountManager.class); alarmManager = mock(AlarmManager.class); timeDetector = mock(TimeDetector.class); + timeZoneDetector = mock(TimeZoneDetector.class); keyChainConnection = mock(KeyChain.KeyChainConnection.class, RETURNS_DEEP_STUBS); // Package manager is huge, so we use a partial mock instead. diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java index 313afbb4ca4c..dee79bb7b501 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java @@ -263,6 +263,19 @@ public class UserManagerTest extends AndroidTestCase { } @MediumTest + public void testFindExistingGuest_guestExists() throws Exception { + UserInfo userInfo1 = createUser("Guest", UserInfo.FLAG_GUEST); + UserInfo foundGuest = mUserManager.findCurrentGuestUser(); + assertNotNull(foundGuest); + } + + @SmallTest + public void testFindExistingGuest_guestDoesNotExist() throws Exception { + UserInfo foundGuest = mUserManager.findCurrentGuestUser(); + assertNull(foundGuest); + } + + @MediumTest public void testSetUserAdmin() throws Exception { UserInfo userInfo = createUser("SecondaryUser", /*flags=*/ 0); diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java index 0ca62e2dcdff..81fb0ec3bf53 100644 --- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java @@ -61,7 +61,6 @@ import android.os.Looper; import android.os.PowerManager; import android.os.PowerSaveState; import android.os.SystemClock; -import android.os.SystemProperties; import android.os.UserHandle; import android.provider.Settings; import android.test.mock.MockContentResolver; @@ -97,10 +96,12 @@ import java.util.Map; * Tests for {@link com.android.server.power.PowerManagerService} */ public class PowerManagerServiceTest { + private static final String SYSTEM_PROPERTY_QUIESCENT = "ro.boot.quiescent"; + private static final String SYSTEM_PROPERTY_REBOOT_REASON = "sys.boot.reason"; + private static final float PRECISION = 0.001f; private static final float BRIGHTNESS_FACTOR = 0.7f; private static final boolean BATTERY_SAVER_ENABLED = true; - private static final String TEST_LAST_REBOOT_PROPERTY = "test.sys.boot.reason"; @Mock private BatterySaverPolicy mBatterySaverPolicyMock; @Mock private LightsManager mLightsManagerMock; @@ -112,6 +113,7 @@ public class PowerManagerServiceTest { @Mock private Notifier mNotifierMock; @Mock private WirelessChargerDetector mWirelessChargerDetectorMock; @Mock private AmbientDisplayConfiguration mAmbientDisplayConfigurationMock; + @Mock private SystemPropertiesWrapper mSystemPropertiesMock; @Mock private InattentiveSleepWarningController mInattentiveSleepWarningControllerMock; @@ -159,6 +161,7 @@ public class PowerManagerServiceTest { when(mBatteryManagerInternalMock.isPowered(anyInt())).thenReturn(false); when(mInattentiveSleepWarningControllerMock.isShown()).thenReturn(false); when(mDisplayManagerInternalMock.requestPowerState(any(), anyBoolean())).thenReturn(true); + when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), anyString())).thenReturn(""); mDisplayPowerRequest = new DisplayPowerRequest(); addLocalServiceMock(LightsManager.class, mLightsManagerMock); @@ -218,6 +221,11 @@ public class PowerManagerServiceTest { InattentiveSleepWarningController createInattentiveSleepWarningController() { return mInattentiveSleepWarningControllerMock; } + + @Override + public SystemPropertiesWrapper createSystemPropertiesWrapper() { + return mSystemPropertiesMock; + } }); return mService; } @@ -228,12 +236,6 @@ public class PowerManagerServiceTest { LocalServices.removeServiceForTest(DisplayManagerInternal.class); LocalServices.removeServiceForTest(BatteryManagerInternal.class); LocalServices.removeServiceForTest(ActivityManagerInternal.class); - - Settings.Global.putInt( - mContextSpy.getContentResolver(), Settings.Global.THEATER_MODE_ON, 0); - setAttentiveTimeout(-1); - Settings.Global.putInt(mContextSpy.getContentResolver(), - Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0); } /** @@ -322,10 +324,10 @@ public class PowerManagerServiceTest { @Test public void testGetLastShutdownReasonInternal() { + when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_REBOOT_REASON), any())).thenReturn( + "shutdown,thermal"); createService(); - SystemProperties.set(TEST_LAST_REBOOT_PROPERTY, "shutdown,thermal"); - int reason = mService.getLastShutdownReasonInternal(TEST_LAST_REBOOT_PROPERTY); - SystemProperties.set(TEST_LAST_REBOOT_PROPERTY, ""); + int reason = mService.getLastShutdownReasonInternal(); assertThat(reason).isEqualTo(PowerManager.SHUTDOWN_REASON_THERMAL_SHUTDOWN); } diff --git a/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java b/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java index e32103fe6bff..e6bb244ef05b 100644 --- a/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java +++ b/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java @@ -45,6 +45,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.util.List; import java.util.Locale; +import java.util.Set; @RunWith(AndroidJUnit4.class) @SmallTest @@ -93,6 +94,8 @@ public class UsageStatsDatabaseTest { for (File f : usageFiles) { f.delete(); } + } else { + intervalDir.delete(); } } } @@ -587,6 +590,7 @@ public class UsageStatsDatabaseTest { db.readMappingsLocked(); db.init(1); db.putUsageStats(interval, mIntervalStats); + db.writeMappingsLocked(); final String removedPackage = "fake.package.name0"; // invoke handler call directly from test to remove package @@ -594,19 +598,19 @@ public class UsageStatsDatabaseTest { List<IntervalStats> stats = db.queryUsageStats(interval, 0, mEndTime, mIntervalStatsVerifier); - for (int i = 0; i < stats.size(); i++) { - final IntervalStats stat = stats.get(i); - if (stat.packageStats.containsKey(removedPackage)) { - fail("Found removed package " + removedPackage + " in package stats."); + assertEquals(1, stats.size(), + "Only one interval stats object should exist for the given time range."); + final IntervalStats stat = stats.get(0); + if (stat.packageStats.containsKey(removedPackage)) { + fail("Found removed package " + removedPackage + " in package stats."); + return; + } + for (int i = 0; i < stat.events.size(); i++) { + final Event event = stat.events.get(i); + if (removedPackage.equals(event.mPackage)) { + fail("Found an event from removed package " + removedPackage); return; } - for (int j = 0; j < stat.events.size(); j++) { - final Event event = stat.events.get(j); - if (removedPackage.equals(event.mPackage)) { - fail("Found an event from removed package " + removedPackage); - return; - } - } } } @@ -617,4 +621,90 @@ public class UsageStatsDatabaseTest { verifyPackageNotRetained(UsageStatsManager.INTERVAL_MONTHLY); verifyPackageNotRetained(UsageStatsManager.INTERVAL_YEARLY); } + + private void verifyPackageDataIsRemoved(UsageStatsDatabase db, int interval, + String removedPackage) { + List<IntervalStats> stats = db.queryUsageStats(interval, 0, mEndTime, + mIntervalStatsVerifier); + assertEquals(1, stats.size(), + "Only one interval stats object should exist for the given time range."); + final IntervalStats stat = stats.get(0); + if (stat.packageStats.containsKey(removedPackage)) { + fail("Found removed package " + removedPackage + " in package stats."); + return; + } + for (int i = 0; i < stat.events.size(); i++) { + final Event event = stat.events.get(i); + if (removedPackage.equals(event.mPackage)) { + fail("Found an event from removed package " + removedPackage); + return; + } + } + } + + private void verifyPackageDataIsNotRemoved(UsageStatsDatabase db, int interval, + Set<String> installedPackages) { + List<IntervalStats> stats = db.queryUsageStats(interval, 0, mEndTime, + mIntervalStatsVerifier); + assertEquals(1, stats.size(), + "Only one interval stats object should exist for the given time range."); + final IntervalStats stat = stats.get(0); + if (!stat.packageStats.containsAll(installedPackages)) { + fail("Could not find some installed packages in package stats."); + return; + } + // attempt to find an event from each installed package + for (String installedPackage : installedPackages) { + for (int i = 0; i < stat.events.size(); i++) { + if (installedPackage.equals(stat.events.get(i).mPackage)) { + break; + } + if (i == stat.events.size() - 1) { + fail("Could not find any event for: " + installedPackage); + return; + } + } + } + } + + @Test + public void testPackageDataIsRemoved() throws IOException { + UsageStatsDatabase db = new UsageStatsDatabase(mTestDir); + db.readMappingsLocked(); + db.init(1); + + // write stats to disk for each interval + db.putUsageStats(UsageStatsManager.INTERVAL_DAILY, mIntervalStats); + db.putUsageStats(UsageStatsManager.INTERVAL_WEEKLY, mIntervalStats); + db.putUsageStats(UsageStatsManager.INTERVAL_MONTHLY, mIntervalStats); + db.putUsageStats(UsageStatsManager.INTERVAL_YEARLY, mIntervalStats); + db.writeMappingsLocked(); + + final Set<String> installedPackages = mIntervalStats.packageStats.keySet(); + final String removedPackage = installedPackages.iterator().next(); + installedPackages.remove(removedPackage); + + // mimic a package uninstall + db.onPackageRemoved(removedPackage, System.currentTimeMillis()); + + // mimic the idle prune job being triggered + db.pruneUninstalledPackagesData(); + + // read data from disk into a new db instance + UsageStatsDatabase newDB = new UsageStatsDatabase(mTestDir); + newDB.readMappingsLocked(); + newDB.init(mEndTime); + + // query data for each interval and ensure data for package doesn't exist + verifyPackageDataIsRemoved(newDB, UsageStatsManager.INTERVAL_DAILY, removedPackage); + verifyPackageDataIsRemoved(newDB, UsageStatsManager.INTERVAL_WEEKLY, removedPackage); + verifyPackageDataIsRemoved(newDB, UsageStatsManager.INTERVAL_MONTHLY, removedPackage); + verifyPackageDataIsRemoved(newDB, UsageStatsManager.INTERVAL_YEARLY, removedPackage); + + // query data for each interval and ensure some data for installed packages exists + verifyPackageDataIsNotRemoved(newDB, UsageStatsManager.INTERVAL_DAILY, installedPackages); + verifyPackageDataIsNotRemoved(newDB, UsageStatsManager.INTERVAL_WEEKLY, installedPackages); + verifyPackageDataIsNotRemoved(newDB, UsageStatsManager.INTERVAL_MONTHLY, installedPackages); + verifyPackageDataIsNotRemoved(newDB, UsageStatsManager.INTERVAL_YEARLY, installedPackages); + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java index 53368111d151..de2bba275171 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java @@ -123,7 +123,7 @@ public class ActivityDisplayTests extends ActivityTestsBase { assertTrue(stack1.isFocusedStackOnDisplay()); // Stack2 should be focused after removing stack1. - display.removeChild(stack1); + display.removeStack(stack1); assertTrue(stack2.isFocusedStackOnDisplay()); } @@ -224,7 +224,7 @@ public class ActivityDisplayTests extends ActivityTestsBase { final ActivityRecord activity = new ActivityBuilder(mService).setCreateTask(true) .setStack(alwaysOnTopStack).build(); alwaysOnTopStack.setAlwaysOnTop(true); - display.positionChildAtTop(alwaysOnTopStack, false /* includingParents */); + display.positionStackAtTop(alwaysOnTopStack, false /* includingParents */); assertTrue(alwaysOnTopStack.isAlwaysOnTop()); // Ensure always on top state is synced to the children of the stack. assertTrue(alwaysOnTopStack.getTopNonFinishingActivity().isAlwaysOnTop()); @@ -238,36 +238,36 @@ public class ActivityDisplayTests extends ActivityTestsBase { final ActivityStack anotherAlwaysOnTopStack = display.createStack( WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD, true /* onTop */); anotherAlwaysOnTopStack.setAlwaysOnTop(true); - display.positionChildAtTop(anotherAlwaysOnTopStack, false /* includingParents */); + display.positionStackAtTop(anotherAlwaysOnTopStack, false /* includingParents */); assertTrue(anotherAlwaysOnTopStack.isAlwaysOnTop()); - int topPosition = display.getChildCount() - 1; + int topPosition = display.getStackCount() - 1; // Ensure the new alwaysOnTop stack is put below the pinned stack, but on top of the // existing alwaysOnTop stack. - assertEquals(anotherAlwaysOnTopStack, display.getChildAt(topPosition - 1)); + assertEquals(anotherAlwaysOnTopStack, display.getStackAt(topPosition - 1)); final ActivityStack nonAlwaysOnTopStack = display.createStack( WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD, true /* onTop */); assertEquals(display, nonAlwaysOnTopStack.getDisplay()); - topPosition = display.getChildCount() - 1; + topPosition = display.getStackCount() - 1; // Ensure the non-alwaysOnTop stack is put below the three alwaysOnTop stacks, but above the // existing other non-alwaysOnTop stacks. - assertEquals(nonAlwaysOnTopStack, display.getChildAt(topPosition - 3)); + assertEquals(nonAlwaysOnTopStack, display.getStackAt(topPosition - 3)); anotherAlwaysOnTopStack.setAlwaysOnTop(false); - display.positionChildAtTop(anotherAlwaysOnTopStack, false /* includingParents */); + display.positionStackAtTop(anotherAlwaysOnTopStack, false /* includingParents */); assertFalse(anotherAlwaysOnTopStack.isAlwaysOnTop()); // Ensure, when always on top is turned off for a stack, the stack is put just below all // other always on top stacks. - assertEquals(anotherAlwaysOnTopStack, display.getChildAt(topPosition - 2)); + assertEquals(anotherAlwaysOnTopStack, display.getStackAt(topPosition - 2)); anotherAlwaysOnTopStack.setAlwaysOnTop(true); // Ensure always on top state changes properly when windowing mode changes. anotherAlwaysOnTopStack.setWindowingMode(WINDOWING_MODE_FULLSCREEN); assertFalse(anotherAlwaysOnTopStack.isAlwaysOnTop()); - assertEquals(anotherAlwaysOnTopStack, display.getChildAt(topPosition - 2)); + assertEquals(anotherAlwaysOnTopStack, display.getStackAt(topPosition - 2)); anotherAlwaysOnTopStack.setWindowingMode(WINDOWING_MODE_FREEFORM); assertTrue(anotherAlwaysOnTopStack.isAlwaysOnTop()); - assertEquals(anotherAlwaysOnTopStack, display.getChildAt(topPosition - 1)); + assertEquals(anotherAlwaysOnTopStack, display.getStackAt(topPosition - 1)); } @Test @@ -299,14 +299,14 @@ public class ActivityDisplayTests extends ActivityTestsBase { // Reordering stacks while removing stacks. doAnswer(invocation -> { - display.positionChildAtTop(stack3, false); + display.positionStackAtTop(stack3, false); return true; }).when(mSupervisor).removeTaskByIdLocked(eq(task4.mTaskId), anyBoolean(), anyBoolean(), any()); // Removing stacks from the display while removing stacks. doAnswer(invocation -> { - display.removeChild(stack2); + display.removeStack(stack2); return true; }).when(mSupervisor).removeTaskByIdLocked(eq(task2.mTaskId), anyBoolean(), anyBoolean(), any()); diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index c2b8827df8a3..7166829e01da 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -455,7 +455,7 @@ public class ActivityRecordTests extends ActivityTestsBase { verify(mService.getLifecycleManager()).scheduleTransaction( eq(mActivity.app.getThread()), eq(mActivity.appToken), eq(expected)); } finally { - stack.getDisplay().removeChild(stack); + stack.getDisplay().removeStack(stack); } } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java index c8795cec3508..7806d40c3c0d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java @@ -290,7 +290,7 @@ public class ActivityStackTests extends ActivityTestsBase { verify(stack2).positionChildAtBottom(any(), eq(false) /* includingParents */); // Also move display to back because there is only one stack left. - display.removeChild(stack1); + display.removeStack(stack1); stack2.moveToBack("testMoveStackToBackIncludingParent", stack2.topTask()); verify(stack2).positionChildAtBottom(any(), eq(true) /* includingParents */); } @@ -576,7 +576,7 @@ public class ActivityStackTests extends ActivityTestsBase { @Test public void testMoveHomeStackBehindBottomMostVisibleStack_NoMoveHomeBehindFullscreen() { - mDefaultDisplay.removeChild(mStack); + mDefaultDisplay.removeStack(mStack); final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); @@ -595,7 +595,7 @@ public class ActivityStackTests extends ActivityTestsBase { @Test public void testMoveHomeStackBehindBottomMostVisibleStack_NoMoveHomeBehindTranslucent() { - mDefaultDisplay.removeChild(mStack); + mDefaultDisplay.removeStack(mStack); final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); @@ -614,7 +614,7 @@ public class ActivityStackTests extends ActivityTestsBase { @Test public void testMoveHomeStackBehindBottomMostVisibleStack_NoMoveHomeOnTop() { - mDefaultDisplay.removeChild(mStack); + mDefaultDisplay.removeStack(mStack); final ActivityStack fullscreenStack = createStackForShouldBeVisibleTest(mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); @@ -633,7 +633,7 @@ public class ActivityStackTests extends ActivityTestsBase { @Test public void testMoveHomeStackBehindBottomMostVisibleStack_MoveHomeBehindFullscreen() { - mDefaultDisplay.removeChild(mStack); + mDefaultDisplay.removeStack(mStack); final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); @@ -660,7 +660,7 @@ public class ActivityStackTests extends ActivityTestsBase { @Test public void testMoveHomeStackBehindBottomMostVisibleStack_MoveHomeBehindFullscreenAndTranslucent() { - mDefaultDisplay.removeChild(mStack); + mDefaultDisplay.removeStack(mStack); final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); @@ -684,7 +684,7 @@ public class ActivityStackTests extends ActivityTestsBase { @Test public void testMoveHomeStackBehindStack_BehindHomeStack() { - mDefaultDisplay.removeChild(mStack); + mDefaultDisplay.removeStack(mStack); final ActivityStack fullscreenStack1 = createStackForShouldBeVisibleTest( mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, @@ -707,7 +707,7 @@ public class ActivityStackTests extends ActivityTestsBase { @Test public void testMoveHomeStackBehindStack() { - mDefaultDisplay.removeChild(mStack); + mDefaultDisplay.removeStack(mStack); final ActivityStack fullscreenStack1 = createStackForShouldBeVisibleTest( mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, @@ -814,16 +814,16 @@ public class ActivityStackTests extends ActivityTestsBase { } @SuppressWarnings("TypeParameterUnusedInFormals") - private <T extends ActivityStack> T createStackForShouldBeVisibleTest( + private ActivityStack createStackForShouldBeVisibleTest( ActivityDisplay display, int windowingMode, int activityType, boolean onTop) { final ActivityStack stack; if (activityType == ACTIVITY_TYPE_HOME) { // Home stack and activity are created in ActivityTestsBase#setupActivityManagerService stack = mDefaultDisplay.getStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME); if (onTop) { - mDefaultDisplay.positionChildAtTop(stack, false /* includingParents */); + mDefaultDisplay.positionStackAtTop(stack, false /* includingParents */); } else { - mDefaultDisplay.positionChildAtBottom(stack); + mDefaultDisplay.positionStackAtBottom(stack); } } else { stack = new StackBuilder(mRootActivityContainer) @@ -834,7 +834,7 @@ public class ActivityStackTests extends ActivityTestsBase { .setCreateActivity(true) .build(); } - return (T) stack; + return stack; } @Test @@ -1051,7 +1051,7 @@ public class ActivityStackTests extends ActivityTestsBase { StackOrderChangedListener listener = new StackOrderChangedListener(); mDefaultDisplay.registerStackOrderChangedListener(listener); try { - mDefaultDisplay.removeChild(mStack); + mDefaultDisplay.removeStack(mStack); } finally { mDefaultDisplay.unregisterStackOrderChangedListener(listener); } @@ -1060,12 +1060,12 @@ public class ActivityStackTests extends ActivityTestsBase { @Test public void testStackOrderChangedOnAddPositionStack() { - mDefaultDisplay.removeChild(mStack); + mDefaultDisplay.removeStack(mStack); StackOrderChangedListener listener = new StackOrderChangedListener(); mDefaultDisplay.registerStackOrderChangedListener(listener); try { - mDefaultDisplay.addChild(mStack, 0); + mDefaultDisplay.addStack(mStack, 0); } finally { mDefaultDisplay.unregisterStackOrderChangedListener(listener); } @@ -1080,7 +1080,7 @@ public class ActivityStackTests extends ActivityTestsBase { mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); mDefaultDisplay.registerStackOrderChangedListener(listener); - mDefaultDisplay.positionChildAtBottom(fullscreenStack1); + mDefaultDisplay.positionStackAtBottom(fullscreenStack1); } finally { mDefaultDisplay.unregisterStackOrderChangedListener(listener); } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java index c712d6dedb27..d5fdf9871f97 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java @@ -517,8 +517,8 @@ public class ActivityStarterTests extends ActivityTestsBase { } private void assertNoTasks(ActivityDisplay display) { - for (int i = display.getChildCount() - 1; i >= 0; --i) { - final ActivityStack stack = display.getChildAt(i); + for (int i = display.getStackCount() - 1; i >= 0; --i) { + final ActivityStack stack = display.getStackAt(i); assertThat(stack.getAllTasks()).isEmpty(); } } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java index 7322ac364e5d..0021cc5368e2 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java @@ -203,6 +203,15 @@ class ActivityTestsBase extends SystemServiceTestsBase { } ActivityRecord build() { + try { + mService.deferWindowLayout(); + return buildInner(); + } finally { + mService.continueWindowLayout(); + } + } + + ActivityRecord buildInner() { if (mComponent == null) { final int id = sCurrentActivityId++; mComponent = ComponentName.createRelative(DEFAULT_COMPONENT_PACKAGE_NAME, @@ -236,7 +245,7 @@ class ActivityTestsBase extends SystemServiceTestsBase { ActivityOptions options = null; if (mLaunchTaskBehind) { - options = ActivityOptions.makeTaskLaunchBehind(); + options = ActivityOptions.makeTaskLaunchBehind(); } final ActivityRecord activity = new ActivityRecord(mService, null /* caller */, @@ -399,11 +408,18 @@ class ActivityTestsBase extends SystemServiceTestsBase { return this; } + // TODO(display-merge): Remove StackBuilder setDisplay(ActivityDisplay display) { mDisplay = display; return this; } + StackBuilder setDisplay(DisplayContent display) { + // TODO(display-merge): Remove cast + mDisplay = (ActivityDisplay) display; + return this; + } + StackBuilder setOnTop(boolean onTop) { mOnTop = onTop; return this; diff --git a/services/tests/wmtests/src/com/android/server/wm/BoundsAnimationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BoundsAnimationControllerTests.java index beec1a8b8942..0ad0f95f64c1 100644 --- a/services/tests/wmtests/src/com/android/server/wm/BoundsAnimationControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/BoundsAnimationControllerTests.java @@ -47,6 +47,7 @@ import com.android.server.wm.WindowManagerInternal.AppTransitionListener; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; /** * Test class for {@link BoundsAnimationController} to ensure that it sends the right callbacks @@ -63,6 +64,7 @@ import org.junit.Test; */ @SmallTest @Presubmit +@RunWith(WindowTestRunner.class) public class BoundsAnimationControllerTests extends WindowTestsBase { /** diff --git a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java index 73420a095cad..0aa6961d20b3 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java @@ -36,12 +36,14 @@ import android.view.SurfaceSession; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; /** * Build/Install/Run: * atest FrameworksServicesTests:DimmerTests */ @Presubmit +@RunWith(WindowTestRunner.class) public class DimmerTests extends WindowTestsBase { private static class TestWindowContainer extends WindowContainer<TestWindowContainer> { diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index 3eda980ba60a..716e777ab779 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -709,13 +709,13 @@ public class DisplayContentTests extends WindowTestsBase { final ActivityStack stack = new ActivityTestsBase.StackBuilder(mWm.mAtmService.mRootActivityContainer) - .setDisplay(dc.mActivityDisplay) + .setDisplay(dc) .build(); doReturn(true).when(stack).isVisible(); final ActivityStack freeformStack = new ActivityTestsBase.StackBuilder(mWm.mAtmService.mRootActivityContainer) - .setDisplay(dc.mActivityDisplay) + .setDisplay(dc) .setWindowingMode(WINDOWING_MODE_FREEFORM) .build(); doReturn(true).when(freeformStack).isVisible(); @@ -743,7 +743,7 @@ public class DisplayContentTests extends WindowTestsBase { final ActivityStack stack = new ActivityTestsBase.StackBuilder(mWm.mAtmService.mRootActivityContainer) - .setDisplay(dc.mActivityDisplay).build(); + .setDisplay(dc).build(); final ActivityRecord activity = stack.topTask().getTopNonFinishingActivity(); activity.setRequestedOrientation(newOrientation); @@ -765,12 +765,13 @@ public class DisplayContentTests extends WindowTestsBase { final ActivityStack stack = new ActivityTestsBase.StackBuilder(mWm.mAtmService.mRootActivityContainer) - .setDisplay(dc.mActivityDisplay).build(); + .setDisplay(dc).build(); final ActivityRecord activity = stack.topTask().getTopNonFinishingActivity(); activity.setRequestedOrientation(newOrientation); - verify(dc.mActivityDisplay, never()).updateDisplayOverrideConfigurationLocked(any(), + // TODO(display-merge): Remove cast + verify((ActivityDisplay) dc, never()).updateDisplayOverrideConfigurationLocked(any(), eq(activity), anyBoolean(), same(null)); assertEquals(dc.getDisplayRotation().getUserRotation(), dc.getRotation()); } @@ -940,6 +941,7 @@ public class DisplayContentTests extends WindowTestsBase { final DisplayContent displayContent = createNewDisplay(); Mockito.doReturn(mockLogger).when(displayContent).getMetricsLogger(); Mockito.doReturn(oldConfig).doReturn(newConfig).when(displayContent).getConfiguration(); + doNothing().when(displayContent).preOnConfigurationChanged(); displayContent.onConfigurationChanged(newConfig); @@ -959,12 +961,12 @@ public class DisplayContentTests extends WindowTestsBase { Mockito.doCallRealMethod().when(dr).updateRotationUnchecked(anyBoolean()); Mockito.doReturn(ROTATION_90).when(dr).rotationForOrientation(anyInt(), anyInt()); final boolean[] continued = new boolean[1]; - spyOn(dc.mActivityDisplay); + // TODO(display-merge): Remove cast Mockito.doAnswer( invocation -> { continued[0] = true; return true; - }).when(dc.mActivityDisplay).updateDisplayOverrideConfigurationLocked(); + }).when((ActivityDisplay) dc).updateDisplayOverrideConfigurationLocked(); final boolean[] called = new boolean[1]; mWm.mDisplayRotationController = new IDisplayWindowRotationController.Stub() { diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyInsetsTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyInsetsTests.java index 6767465f838c..f754c5956cd9 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyInsetsTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyInsetsTests.java @@ -32,9 +32,11 @@ import androidx.test.filters.SmallTest; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ErrorCollector; +import org.junit.runner.RunWith; @SmallTest @Presubmit +@RunWith(WindowTestRunner.class) public class DisplayPolicyInsetsTests extends DisplayPolicyTestsBase { @Rule 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 79ad0c45c01f..9e5d9da65b87 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java @@ -46,6 +46,7 @@ import com.android.server.wm.LaunchParamsController.LaunchParams; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import java.io.File; @@ -63,6 +64,7 @@ import java.util.function.Predicate; */ @MediumTest @Presubmit +@RunWith(WindowTestRunner.class) 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/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java index 4f2d5d23f83d..9f97c488cf4d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java @@ -977,9 +977,9 @@ public class RecentTasksTest extends ActivityTestsBase { private void assertNotRestoreTask(Runnable action) { // Verify stack count doesn't change because task with fullscreen mode and standard type // would have its own stack. - final int orignalStackCount = mDisplay.getChildCount(); + final int originalStackCount = mDisplay.getStackCount(); action.run(); - assertEquals(orignalStackCount, mDisplay.getChildCount()); + assertEquals(originalStackCount, mDisplay.getStackCount()); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java index 1abd3662165e..2374847283e0 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java @@ -138,7 +138,8 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { @Test public void testIncludedApps_expectTargetAndVisible() { mWm.setRecentsAnimationController(mController); - final ActivityStack homeStack = mDisplayContent.mActivityDisplay.getOrCreateStack( + // TODO(display-merge): Remove cast + final ActivityStack homeStack = ((ActivityDisplay) mDisplayContent).getOrCreateStack( WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP); final ActivityRecord homeActivity = new ActivityTestsBase.ActivityBuilder(mWm.mAtmService) @@ -163,7 +164,8 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { @Test public void testWallpaperIncluded_expectTarget() throws Exception { mWm.setRecentsAnimationController(mController); - final ActivityStack homeStack = mDisplayContent.mActivityDisplay.getOrCreateStack( + // TODO(display-merge): Remove cast + final ActivityStack homeStack = ((ActivityDisplay) mDisplayContent).getOrCreateStack( WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP); final ActivityRecord homeAppWindow = new ActivityTestsBase.ActivityBuilder(mWm.mAtmService) @@ -192,7 +194,8 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { @Test public void testWallpaperAnimatorCanceled_expectAnimationKeepsRunning() throws Exception { mWm.setRecentsAnimationController(mController); - final ActivityStack homeStack = mDisplayContent.mActivityDisplay.getOrCreateStack( + // TODO(display-merge): Remove cast + final ActivityStack homeStack = ((ActivityDisplay) mDisplayContent).getOrCreateStack( WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP); final ActivityRecord homeActivity = new ActivityTestsBase.ActivityBuilder(mWm.mAtmService) @@ -223,7 +226,8 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { @Test public void testFinish_expectTargetAndWallpaperAdaptersRemoved() { mWm.setRecentsAnimationController(mController); - final ActivityStack homeStack = mDisplayContent.mActivityDisplay.getOrCreateStack( + // TODO(display-merge): Remove cast + final ActivityStack homeStack = ((ActivityDisplay) mDisplayContent).getOrCreateStack( WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP); final ActivityRecord homeActivity = new ActivityTestsBase.ActivityBuilder(mWm.mAtmService) diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java index e67380c23056..4abab63cba81 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java @@ -119,7 +119,7 @@ public class RecentsAnimationTest extends ActivityTestsBase { final ActivityDisplay defaultDisplay = mRootActivityContainer.getDefaultDisplay(); final ActivityStack homeStack = defaultDisplay.getStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME); - defaultDisplay.positionChildAtTop(homeStack, false /* includingParents */); + defaultDisplay.positionStackAtTop(homeStack, false /* includingParents */); ActivityRecord topRunningHomeActivity = homeStack.topRunningActivityLocked(); if (topRunningHomeActivity == null) { topRunningHomeActivity = new ActivityBuilder(mService) diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java index e353903835a6..112479b3b9a0 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java @@ -54,6 +54,7 @@ import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -64,6 +65,7 @@ import org.mockito.MockitoAnnotations; */ @SmallTest @Presubmit +@RunWith(WindowTestRunner.class) public class RemoteAnimationControllerTest extends WindowTestsBase { @Mock SurfaceControl mMockLeash; @@ -84,7 +86,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { when(mMockRunner.asBinder()).thenReturn(new Binder()); mAdapter = new RemoteAnimationAdapter(mMockRunner, 100, 50, true /* changeNeedsSnapshot */); mAdapter.setCallingPidUid(123, 456); - mWm.mH.runWithScissors(() -> mHandler = new TestHandler(null, mClock), 0); + runWithScissors(mWm.mH, () -> mHandler = new TestHandler(null, mClock), 0); mController = new RemoteAnimationController(mWm, mAdapter, mHandler); } 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 425724921ada..59c7c021d71e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java @@ -226,20 +226,20 @@ public class RootActivityContainerTests extends ActivityTestsBase { @Test public void testRemovingStackOnAppCrash() { final ActivityDisplay defaultDisplay = mRootActivityContainer.getDefaultDisplay(); - final int originalStackCount = defaultDisplay.getChildCount(); + final int originalStackCount = defaultDisplay.getStackCount(); final ActivityStack stack = mRootActivityContainer.getDefaultDisplay().createStack( WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */); final ActivityRecord firstActivity = new ActivityBuilder(mService).setCreateTask(true) .setStack(stack).build(); - assertEquals(originalStackCount + 1, defaultDisplay.getChildCount()); + assertEquals(originalStackCount + 1, defaultDisplay.getStackCount()); // Let's pretend that the app has crashed. firstActivity.app.setThread(null); mRootActivityContainer.finishTopCrashedActivities(firstActivity.app, "test"); // Verify that the stack was removed. - assertEquals(originalStackCount, defaultDisplay.getChildCount()); + assertEquals(originalStackCount, defaultDisplay.getStackCount()); } @Test @@ -392,7 +392,7 @@ public class RootActivityContainerTests extends ActivityTestsBase { ACTIVITY_TYPE_STANDARD, false /* onTop */)); final Task task = new TaskBuilder(mSupervisor).setStack(targetStack).build(); final ActivityRecord activity = new ActivityBuilder(mService).setTask(task).build(); - display.positionChildAtBottom(targetStack); + display.positionStackAtBottom(targetStack); // Assume the stack is not at the topmost position (e.g. behind always-on-top stacks) but it // is the current top focused stack. @@ -493,7 +493,7 @@ public class RootActivityContainerTests extends ActivityTestsBase { final Task task = new TaskBuilder(mSupervisor).setStack(targetStack).build(); final ActivityRecord activity = new ActivityBuilder(mService).setTask(task).build(); activity.setState(ActivityState.RESUMED, "test"); - display.positionChildAtBottom(targetStack); + display.positionStackAtBottom(targetStack); // Assume the stack is at the topmost position assertFalse(targetStack.isTopStackOnDisplay()); diff --git a/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java index eba2bc899b57..5c9ccae8b7d1 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java @@ -74,7 +74,7 @@ public class RunningTasksTest extends ActivityTestsBase { final int numTasks = 10; int activeTime = 0; for (int i = 0; i < numTasks; i++) { - createTask(display.getChildAt(i % numStacks), ".Task" + i, i, activeTime++); + createTask(display.getStackAt(i % numStacks), ".Task" + i, i, activeTime++); } // Ensure that the latest tasks were returned in order of decreasing last active time, diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java index c48348992196..3008a75740e8 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java @@ -50,6 +50,7 @@ import com.android.server.wm.LocalAnimationAdapter.AnimationSpec; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -63,6 +64,7 @@ import java.util.concurrent.CountDownLatch; */ @SmallTest @Presubmit +@RunWith(WindowTestRunner.class) public class SurfaceAnimationRunnerTest extends WindowTestsBase { @Mock SurfaceControl mMockSurface; @@ -98,6 +100,7 @@ public class SurfaceAnimationRunnerTest extends WindowTestsBase { verify(mMockTransaction, atLeastOnce()).setMatrix(eq(mMockSurface), eq(m), any()); verify(mMockTransaction, atLeastOnce()).setAlpha(eq(mMockSurface), eq(1.0f)); + waitHandlerIdle(SurfaceAnimationThread.getHandler()); mFinishCallbackLatch.await(1, SECONDS); assertFinishCallbackCalled(); @@ -176,6 +179,7 @@ public class SurfaceAnimationRunnerTest extends WindowTestsBase { assertTrue(mSurfaceAnimationRunner.mRunningAnimations.isEmpty()); mSurfaceAnimationRunner.continueStartingAnimations(); waitUntilNextFrame(); + waitHandlerIdle(SurfaceAnimationThread.getHandler()); assertFalse(mSurfaceAnimationRunner.mRunningAnimations.isEmpty()); mFinishCallbackLatch.await(1, SECONDS); assertFinishCallbackCalled(); diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java index 979aab66be63..2894241356f7 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java @@ -44,6 +44,7 @@ import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback; import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -56,6 +57,7 @@ import org.mockito.MockitoAnnotations; */ @SmallTest @Presubmit +@RunWith(WindowTestRunner.class) public class SurfaceAnimatorTest extends WindowTestsBase { @Mock AnimationAdapter mSpec; 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 27ebd5ad94aa..9a91efe0be62 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java @@ -53,6 +53,7 @@ import com.android.server.wm.LaunchParamsController.LaunchParams; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; import java.util.ArrayList; import java.util.List; @@ -66,6 +67,7 @@ import java.util.Locale; */ @SmallTest @Presubmit +@RunWith(WindowTestRunner.class) public class TaskLaunchParamsModifierTests extends ActivityTestsBase { private static final Rect DISPLAY_BOUNDS = new Rect(/* left */ 0, /* top */ 0, /* right */ 1920, /* bottom */ 1080); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java index 9275512bc719..897057165971 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java @@ -19,7 +19,6 @@ package com.android.server.wm; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.server.wm.TaskPositioner.MIN_ASPECT; import static com.android.server.wm.WindowManagerService.dipToPixel; @@ -33,7 +32,6 @@ import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; -import android.app.IActivityTaskManager; import android.graphics.Rect; import android.platform.test.annotations.Presubmit; import android.util.DisplayMetrics; @@ -46,6 +44,7 @@ import androidx.test.filters.SmallTest; import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; /** * Tests for the {@link TaskPositioner} class. @@ -55,6 +54,7 @@ import org.junit.Test; */ @SmallTest @Presubmit +@RunWith(WindowTestRunner.class) public class TaskPositionerTests extends WindowTestsBase { private static final boolean DEBUGGING = false; diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotCacheTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotCacheTest.java index f595e0525cc2..0274b7d788ff 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotCacheTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotCacheTest.java @@ -18,21 +18,21 @@ package com.android.server.wm; import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW; +import static junit.framework.Assert.assertEquals; + import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; -import static junit.framework.Assert.assertEquals; +import android.app.ActivityManager.TaskSnapshot; import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; -import android.app.ActivityManager.TaskSnapshot; - -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; /** * Test class for {@link TaskSnapshotCache}. @@ -42,6 +42,7 @@ import org.junit.Test; */ @SmallTest @Presubmit +@RunWith(WindowTestRunner.class) public class TaskSnapshotCacheTest extends TaskSnapshotPersisterTestBase { private TaskSnapshotCache mCache; diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java index 3b11003f52e6..6ebaf29d2977 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java @@ -33,6 +33,7 @@ import androidx.test.filters.SmallTest; import com.google.android.collect.Sets; import org.junit.Test; +import org.junit.runner.RunWith; /** * Test class for {@link TaskSnapshotController}. @@ -42,6 +43,7 @@ import org.junit.Test; */ @SmallTest @Presubmit +@RunWith(WindowTestRunner.class) public class TaskSnapshotControllerTest extends WindowTestsBase { @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java index b29453a4fa94..b5a5790a3a8c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java @@ -38,6 +38,7 @@ import androidx.test.filters.MediumTest; import com.android.server.wm.TaskSnapshotPersister.RemoveObsoleteFilesQueueItem; import org.junit.Test; +import org.junit.runner.RunWith; import java.io.File; import java.util.function.Predicate; @@ -50,6 +51,7 @@ import java.util.function.Predicate; */ @MediumTest @Presubmit +@RunWith(WindowTestRunner.class) public class TaskSnapshotPersisterLoaderTest extends TaskSnapshotPersisterTestBase { private static final Rect TEST_INSETS = new Rect(10, 20, 30, 40); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java index 74db8202614d..817344f9e36f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java @@ -47,6 +47,7 @@ import androidx.test.filters.SmallTest; import com.android.server.wm.TaskSnapshotSurface.Window; import org.junit.Test; +import org.junit.runner.RunWith; /** * Test class for {@link TaskSnapshotSurface}. @@ -56,6 +57,7 @@ import org.junit.Test; */ @SmallTest @Presubmit +@RunWith(WindowTestRunner.class) public class TaskSnapshotSurfaceTest extends WindowTestsBase { private TaskSnapshotSurface mSurface; diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskStackContainersTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskStackContainersTests.java index dbf61db088ba..6ad9f740576c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskStackContainersTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskStackContainersTests.java @@ -19,6 +19,9 @@ package com.android.server.wm; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; + import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; @@ -112,6 +115,10 @@ public class TaskStackContainersTests extends WindowTestsBase { @Test public void testDisplayPositionWithPinnedStack() { + // Make sure the display is system owned display which capable to move the stack to top. + spyOn(mDisplayContent); + doReturn(false).when(mDisplayContent).isUntrustedVirtualDisplay(); + // The display contains pinned stack that was added in {@link #setUp}. final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent); final Task task = createTaskInStack(stack, 0 /* userId */); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowConfigurationTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowConfigurationTests.java index 64ac547bd9ee..c359fb1415d7 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowConfigurationTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowConfigurationTests.java @@ -48,6 +48,7 @@ import androidx.test.filters.SmallTest; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; /** * Test class to for {@link android.app.WindowConfiguration}. @@ -57,6 +58,7 @@ import org.junit.Test; */ @SmallTest @Presubmit +@RunWith(WindowTestRunner.class) public class WindowConfigurationTests extends WindowTestsBase { private Rect mParentBounds; diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java index 814004522b28..13cf22d176ec 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java @@ -56,6 +56,7 @@ import android.view.SurfaceSession; import androidx.test.filters.SmallTest; import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.Mockito; import java.util.Comparator; @@ -68,6 +69,7 @@ import java.util.Comparator; */ @SmallTest @Presubmit +@RunWith(WindowTestRunner.class) public class WindowContainerTests extends WindowTestsBase { @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java index 4b666f538ea2..8936aadd5f92 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java @@ -31,6 +31,7 @@ import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; import org.junit.Test; +import org.junit.runner.RunWith; import java.util.function.Consumer; @@ -42,6 +43,7 @@ import java.util.function.Consumer; */ @SmallTest @Presubmit +@RunWith(WindowTestRunner.class) public class WindowContainerTraversalTests extends WindowTestsBase { @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerSettingsTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerSettingsTests.java index 1c9eed2e5622..ce6efdfbffc6 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerSettingsTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerSettingsTests.java @@ -32,6 +32,7 @@ import android.provider.Settings; import androidx.test.filters.SmallTest; import org.junit.Test; +import org.junit.runner.RunWith; /** * Test for {@link WindowManagerService.SettingsObserver}. @@ -40,6 +41,7 @@ import org.junit.Test; * atest WmTests:WindowManagerSettingsTests */ @SmallTest +@RunWith(WindowTestRunner.class) public class WindowManagerSettingsTests extends WindowTestsBase { @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index 437b84b42dae..22ba90b84562 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -320,7 +320,7 @@ class WindowTestsBase extends SystemServiceTestsBase { synchronized (mWm.mGlobalLock) { return new ActivityTestsBase.StackBuilder( dc.mWmService.mAtmService.mRootActivityContainer) - .setDisplay(dc.mActivityDisplay) + .setDisplay(dc) .setWindowingMode(windowingMode) .setActivityType(activityType) .setCreateActivity(false) diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java index 4cdbea0bfb56..e6aed498cf1c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java @@ -31,6 +31,7 @@ import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; import org.junit.Test; +import org.junit.runner.RunWith; /** * Tests for the {@link WindowToken} class. @@ -40,6 +41,7 @@ import org.junit.Test; */ @SmallTest @Presubmit +@RunWith(WindowTestRunner.class) public class WindowTokenTests extends WindowTestsBase { @Test diff --git a/services/usage/java/com/android/server/usage/IntervalStats.java b/services/usage/java/com/android/server/usage/IntervalStats.java index 46b261b64192..8fb283adc740 100644 --- a/services/usage/java/com/android/server/usage/IntervalStats.java +++ b/services/usage/java/com/android/server/usage/IntervalStats.java @@ -448,8 +448,11 @@ public class IntervalStats { /** * Parses all of the tokens to strings in the obfuscated usage stats data. This includes * deobfuscating each of the package tokens and chooser actions and categories. + * + * @return {@code true} if any stats were omitted while deobfuscating, {@code false} otherwise. */ - private void deobfuscateUsageStats(PackagesTokenData packagesTokenData) { + private boolean deobfuscateUsageStats(PackagesTokenData packagesTokenData) { + boolean dataOmitted = false; final int usageStatsSize = packageStatsObfuscated.size(); for (int statsIndex = 0; statsIndex < usageStatsSize; statsIndex++) { final int packageToken = packageStatsObfuscated.keyAt(statsIndex); @@ -457,6 +460,7 @@ public class IntervalStats { usageStats.mPackageName = packagesTokenData.getPackageString(packageToken); if (usageStats.mPackageName == null) { Slog.e(TAG, "Unable to parse usage stats package " + packageToken); + dataOmitted = true; continue; } @@ -489,14 +493,18 @@ public class IntervalStats { } packageStats.put(usageStats.mPackageName, usageStats); } + return dataOmitted; } /** * Parses all of the tokens to strings in the obfuscated events data. This includes * deobfuscating the package token, along with any class, task root package/class tokens, and * shortcut or notification channel tokens. + * + * @return {@code true} if any events were omitted while deobfuscating, {@code false} otherwise. */ - private void deobfuscateEvents(PackagesTokenData packagesTokenData) { + private boolean deobfuscateEvents(PackagesTokenData packagesTokenData) { + boolean dataOmitted = false; for (int i = this.events.size() - 1; i >= 0; i--) { final Event event = this.events.get(i); final int packageToken = event.mPackageToken; @@ -504,6 +512,7 @@ public class IntervalStats { if (event.mPackage == null) { Slog.e(TAG, "Unable to parse event package " + packageToken); this.events.remove(i); + dataOmitted = true; continue; } @@ -543,6 +552,7 @@ public class IntervalStats { Slog.e(TAG, "Unable to parse shortcut " + event.mShortcutIdToken + " for package " + packageToken); this.events.remove(i); + dataOmitted = true; continue; } break; @@ -554,21 +564,25 @@ public class IntervalStats { + event.mNotificationChannelIdToken + " for package " + packageToken); this.events.remove(i); + dataOmitted = true; continue; } break; } } + return dataOmitted; } /** * Parses the obfuscated tokenized data held in this interval stats object. * + * @return {@code true} if any data was omitted while deobfuscating, {@code false} otherwise. * @hide */ - public void deobfuscateData(PackagesTokenData packagesTokenData) { - deobfuscateUsageStats(packagesTokenData); - deobfuscateEvents(packagesTokenData); + public boolean deobfuscateData(PackagesTokenData packagesTokenData) { + final boolean statsOmitted = deobfuscateUsageStats(packagesTokenData); + final boolean eventsOmitted = deobfuscateEvents(packagesTokenData); + return statsOmitted || eventsOmitted; } /** diff --git a/services/usage/java/com/android/server/usage/PackagesTokenData.java b/services/usage/java/com/android/server/usage/PackagesTokenData.java index 4bf08a49af0f..f19abbbac485 100644 --- a/services/usage/java/com/android/server/usage/PackagesTokenData.java +++ b/services/usage/java/com/android/server/usage/PackagesTokenData.java @@ -162,15 +162,18 @@ public final class PackagesTokenData { * * @param packageName the package to be removed * @param timeRemoved the time stamp of when the package was removed + * @return the token mapped to the package removed or {@code PackagesTokenData.UNASSIGNED_TOKEN} + * if not mapped */ - public void removePackage(String packageName, long timeRemoved) { + public int removePackage(String packageName, long timeRemoved) { removedPackagesMap.put(packageName, timeRemoved); if (!packagesToTokensMap.containsKey(packageName)) { - return; + return UNASSIGNED_TOKEN; } final int packageToken = packagesToTokensMap.get(packageName).get(packageName); packagesToTokensMap.remove(packageName); tokensToPackagesMap.delete(packageToken); + return packageToken; } } diff --git a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java index 27d7360313ad..e34824c57fb2 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java +++ b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java @@ -29,6 +29,7 @@ import android.util.SparseArray; import android.util.TimeUtils; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.ArrayUtils; import com.android.internal.util.IndentingPrintWriter; import libcore.io.IoUtils; @@ -52,6 +53,7 @@ import java.io.OutputStream; import java.nio.file.Files; import java.nio.file.StandardCopyOption; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; /** @@ -122,6 +124,7 @@ public class UsageStatsDatabase { private int mCurrentVersion; private boolean mFirstUpdate; private boolean mNewUpdate; + private boolean mUpgradePerformed; // The obfuscated packages to tokens mappings file private final File mPackageMappingsFile; @@ -325,6 +328,13 @@ public class UsageStatsDatabase { return mNewUpdate; } + /** + * Was an upgrade performed when this database was initialized? + */ + boolean wasUpgradePerformed() { + return mUpgradePerformed; + } + private void checkVersionAndBuildLocked() { int version; String buildFingerprint; @@ -397,6 +407,8 @@ public class UsageStatsDatabase { if (mUpdateBreadcrumb.exists()) { // Files should be up to date with current version. Clear the version update breadcrumb mUpdateBreadcrumb.delete(); + // update mUpgradePerformed after breadcrumb is deleted to indicate a successful upgrade + mUpgradePerformed = true; } if (mBackupsDir.exists() && !KEEP_BACKUP_DIR) { @@ -545,12 +557,127 @@ public class UsageStatsDatabase { } } - void onPackageRemoved(String packageName, long timeRemoved) { + /** + * Returns the token mapped to the package removed or {@code PackagesTokenData.UNASSIGNED_TOKEN} + * if not mapped. + */ + int onPackageRemoved(String packageName, long timeRemoved) { + synchronized (mLock) { + final int tokenRemoved = mPackagesTokenData.removePackage(packageName, timeRemoved); + try { + writeMappingsLocked(); + } catch (Exception e) { + Slog.w(TAG, "Unable to update package mappings on disk after removing token " + + tokenRemoved); + } + return tokenRemoved; + } + } + + /** + * Reads all the usage stats data on disk and rewrites it with any data related to uninstalled + * packages omitted. Returns {@code true} on success, {@code false} otherwise. + */ + boolean pruneUninstalledPackagesData() { + synchronized (mLock) { + for (int i = 0; i < mIntervalDirs.length; i++) { + final File[] files = mIntervalDirs[i].listFiles(); + if (files == null) { + continue; + } + for (int j = 0; j < files.length; j++) { + try { + final IntervalStats stats = new IntervalStats(); + final AtomicFile atomicFile = new AtomicFile(files[j]); + if (!readLocked(atomicFile, stats, mCurrentVersion, mPackagesTokenData)) { + continue; // no data was omitted when read so no need to rewrite + } + // Any data related to packages that have been removed would have failed + // the deobfuscation step on read so the IntervalStats object here only + // contains data for packages that are currently installed - all we need + // to do here is write the data back to disk. + writeLocked(atomicFile, stats, mCurrentVersion, mPackagesTokenData); + } catch (Exception e) { + Slog.e(TAG, "Failed to prune data from: " + files[j].toString()); + return false; + } + } + } + + try { + writeMappingsLocked(); + } catch (IOException e) { + Slog.e(TAG, "Failed to write package mappings after pruning data."); + return false; + } + return true; + } + } + + /** + * Iterates through all the files on disk and prunes any data that belongs to packages that have + * been uninstalled (packages that are not in the given list). + * Note: this should only be called once, when there has been a database upgrade. + * + * @param installedPackages map of installed packages (package_name:package_install_time) + */ + void prunePackagesDataOnUpgrade(HashMap<String, Long> installedPackages) { + if (ArrayUtils.isEmpty(installedPackages)) { + return; + } synchronized (mLock) { - mPackagesTokenData.removePackage(packageName, timeRemoved); + for (int i = 0; i < mIntervalDirs.length; i++) { + final File[] files = mIntervalDirs[i].listFiles(); + if (files == null) { + continue; + } + for (int j = 0; j < files.length; j++) { + try { + final IntervalStats stats = new IntervalStats(); + final AtomicFile atomicFile = new AtomicFile(files[j]); + readLocked(atomicFile, stats, mCurrentVersion, mPackagesTokenData); + if (!pruneStats(installedPackages, stats)) { + continue; // no stats were pruned so no need to rewrite + } + writeLocked(atomicFile, stats, mCurrentVersion, mPackagesTokenData); + } catch (Exception e) { + Slog.e(TAG, "Failed to prune data from: " + files[j].toString()); + } + } + } } } + private boolean pruneStats(HashMap<String, Long> installedPackages, IntervalStats stats) { + boolean dataPruned = false; + + // prune old package usage stats + for (int i = stats.packageStats.size() - 1; i >= 0; i--) { + final UsageStats usageStats = stats.packageStats.valueAt(i); + final Long timeInstalled = installedPackages.get(usageStats.mPackageName); + if (timeInstalled == null || timeInstalled > usageStats.mEndTimeStamp) { + stats.packageStats.removeAt(i); + dataPruned = true; + } + } + if (dataPruned) { + // ensure old stats don't linger around during the obfuscation step on write + stats.packageStatsObfuscated.clear(); + } + + // prune old events + for (int i = stats.events.size() - 1; i >= 0; i--) { + final UsageEvents.Event event = stats.events.get(i); + final Long timeInstalled = installedPackages.get(event.mPackage); + if (timeInstalled == null || timeInstalled > event.mTimeStamp) { + stats.events.remove(i); + dataPruned = true; + } + } + + return dataPruned; + } + public void onTimeChanged(long timeDiffMillis) { synchronized (mLock) { StringBuilder logBuilder = new StringBuilder(); @@ -645,7 +772,6 @@ public class UsageStatsDatabase { } // filter out events - final int eventsSize = stats.events.size(); for (int i = stats.events.size() - 1; i >= 0; i--) { final UsageEvents.Event event = stats.events.get(i); final Long timeRemoved = removedPackagesMap.get(event.mPackage); @@ -942,13 +1068,17 @@ public class UsageStatsDatabase { readLocked(file, statsOut, mCurrentVersion, mPackagesTokenData); } - private static void readLocked(AtomicFile file, IntervalStats statsOut, int version, + /** + * Returns {@code true} if any stats were omitted while reading, {@code false} otherwise. + */ + private static boolean readLocked(AtomicFile file, IntervalStats statsOut, int version, PackagesTokenData packagesTokenData) throws IOException { + boolean dataOmitted = false; try { FileInputStream in = file.openRead(); try { statsOut.beginTime = parseBeginTime(file); - readLocked(in, statsOut, version, packagesTokenData); + dataOmitted = readLocked(in, statsOut, version, packagesTokenData); statsOut.lastTimeSaved = file.getLastModifiedTime(); } finally { try { @@ -961,10 +1091,15 @@ public class UsageStatsDatabase { Slog.e(TAG, "UsageStatsDatabase", e); throw e; } + return dataOmitted; } - private static void readLocked(InputStream in, IntervalStats statsOut, int version, + /** + * Returns {@code true} if any stats were omitted while reading, {@code false} otherwise. + */ + private static boolean readLocked(InputStream in, IntervalStats statsOut, int version, PackagesTokenData packagesTokenData) throws IOException { + boolean dataOmitted = false; switch (version) { case 1: case 2: @@ -989,14 +1124,14 @@ public class UsageStatsDatabase { } catch (IOException e) { Slog.e(TAG, "Unable to read interval stats from proto.", e); } - statsOut.deobfuscateData(packagesTokenData); + dataOmitted = statsOut.deobfuscateData(packagesTokenData); break; default: throw new RuntimeException( "Unhandled UsageStatsDatabase version: " + Integer.toString(version) + " on read."); } - + return dataOmitted; } /** diff --git a/services/usage/java/com/android/server/usage/UsageStatsIdleService.java b/services/usage/java/com/android/server/usage/UsageStatsIdleService.java new file mode 100644 index 000000000000..4468871ee0fb --- /dev/null +++ b/services/usage/java/com/android/server/usage/UsageStatsIdleService.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.usage; + +import android.app.job.JobInfo; +import android.app.job.JobParameters; +import android.app.job.JobScheduler; +import android.app.job.JobService; +import android.app.usage.UsageStatsManagerInternal; +import android.content.ComponentName; +import android.content.Context; +import android.os.AsyncTask; +import android.os.PersistableBundle; + +import com.android.server.LocalServices; + +/** + * JobService used to do any work for UsageStats while the device is idle. + */ +public class UsageStatsIdleService extends JobService { + + /** + * Base job ID for the pruning job - must be unique within the system server uid. + */ + private static final int PRUNE_JOB_ID = 546357475; + + private static final String USER_ID_KEY = "user_id"; + + static void scheduleJob(Context context, int userId) { + final int userJobId = PRUNE_JOB_ID + userId; // unique job id per user + final ComponentName component = new ComponentName(context.getPackageName(), + UsageStatsIdleService.class.getName()); + final PersistableBundle bundle = new PersistableBundle(); + bundle.putInt(USER_ID_KEY, userId); + final JobInfo pruneJob = new JobInfo.Builder(userJobId, component) + .setRequiresDeviceIdle(true) + .setExtras(bundle) + .setPersisted(true) + .build(); + + final JobScheduler jobScheduler = context.getSystemService(JobScheduler.class); + final JobInfo pendingPruneJob = jobScheduler.getPendingJob(userJobId); + // only schedule a new prune job if one doesn't exist already for this user + if (!pruneJob.equals(pendingPruneJob)) { + jobScheduler.cancel(userJobId); // cancel any previously scheduled prune job + jobScheduler.schedule(pruneJob); + } + + } + + static void cancelJob(Context context, int userId) { + final int userJobId = PRUNE_JOB_ID + userId; // unique job id per user + final JobScheduler jobScheduler = context.getSystemService(JobScheduler.class); + jobScheduler.cancel(userJobId); + } + + @Override + public boolean onStartJob(JobParameters params) { + final PersistableBundle bundle = params.getExtras(); + final int userId = bundle.getInt(USER_ID_KEY, -1); + if (userId == -1) { + return false; + } + + AsyncTask.execute(() -> { + final UsageStatsManagerInternal usageStatsManagerInternal = LocalServices.getService( + UsageStatsManagerInternal.class); + final boolean pruned = usageStatsManagerInternal.pruneUninstalledPackagesData(userId); + jobFinished(params, !pruned); // reschedule if data was not pruned + }); + return true; + } + + @Override + public boolean onStopJob(JobParameters params) { + // Since the pruning job isn't a heavy job, we don't want to cancel it's execution midway. + return false; + } +} diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index ba4a4484f09b..064922386773 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -52,6 +52,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.ParceledListSlice; @@ -99,6 +100,7 @@ import java.io.PrintWriter; import java.nio.file.Files; import java.nio.file.StandardCopyOption; import java.util.Arrays; +import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Set; @@ -318,6 +320,8 @@ public class UsageStatsService extends SystemService implements } private void onUserUnlocked(int userId) { + // fetch the installed packages outside the lock so it doesn't block package manager. + final HashMap<String, Long> installedPackages = getInstalledPackages(userId); synchronized (mLock) { // Create a user unlocked event to report final Event unlockEvent = new Event(USER_UNLOCKED, SystemClock.elapsedRealtime()); @@ -334,9 +338,10 @@ public class UsageStatsService extends SystemService implements } boolean needToFlush = !pendingEvents.isEmpty(); + initializeUserUsageStatsServiceLocked(userId, System.currentTimeMillis(), + installedPackages); mUserUnlockedStates.put(userId, true); - final UserUsageStatsService userService = getUserDataAndInitializeIfNeededLocked( - userId, System.currentTimeMillis()); + final UserUsageStatsService userService = getUserUsageStatsServiceLocked(userId); if (userService == null) { Slog.i(TAG, "Attempted to unlock stopped or removed user " + userId); return; @@ -361,6 +366,29 @@ public class UsageStatsService extends SystemService implements } } + /** + * Fetches a map (package_name:install_time) of installed packages for the given user. This + * map contains all installed packages, including those packages which have been uninstalled + * with the DONT_DELETE_DATA flag. + * This is a helper method which should only be called when the given user's usage stats service + * is initialized; it performs a heavy query to package manager so do not call it otherwise. + * <br/> + * Note: DO NOT call this while holding the usage stats lock ({@code mLock}). + */ + private HashMap<String, Long> getInstalledPackages(int userId) { + if (mPackageManager == null) { + return null; + } + final List<PackageInfo> installedPackages = mPackageManager.getInstalledPackagesAsUser( + PackageManager.MATCH_UNINSTALLED_PACKAGES, userId); + final HashMap<String, Long> packagesMap = new HashMap<>(); + for (int i = installedPackages.size() - 1; i >= 0; i--) { + final PackageInfo packageInfo = installedPackages.get(i); + packagesMap.put(packageInfo.packageName, packageInfo.firstInstallTime); + } + return packagesMap; + } + private DevicePolicyManagerInternal getDpmInternal() { if (mDpmInternal == null) { mDpmInternal = LocalServices.getService(DevicePolicyManagerInternal.class); @@ -450,31 +478,42 @@ public class UsageStatsService extends SystemService implements } } - private UserUsageStatsService getUserDataAndInitializeIfNeededLocked(int userId, - long currentTimeMillis) { - UserUsageStatsService service = mUserState.get(userId); + /** + * This should the be only way to fetch the usage stats service for a specific user. + */ + private UserUsageStatsService getUserUsageStatsServiceLocked(int userId) { + final UserUsageStatsService service = mUserState.get(userId); if (service == null) { - final File usageStatsDir = new File(Environment.getDataSystemCeDirectory(userId), - "usagestats"); - service = new UserUsageStatsService(getContext(), userId, usageStatsDir, this); - if (mUserUnlockedStates.get(userId)) { - try { - service.init(currentTimeMillis); - mUserState.put(userId, service); - } catch (Exception e) { - if (mUserManager.isUserUnlocked(userId)) { - throw e; // rethrow exception - user is unlocked - } else { - Slog.w(TAG, "Attempted to initialize service for " - + "stopped or removed user " + userId); - return null; - } - } - } + Slog.wtf(TAG, "Failed to fetch usage stats service for user " + userId + ". " + + "The user might not have been initialized yet."); } return service; } + /** + * Initializes the given user's usage stats service - this should ideally only be called once, + * when the user is initially unlocked. + */ + private void initializeUserUsageStatsServiceLocked(int userId, + long currentTimeMillis, HashMap<String, Long> installedPackages) { + final File usageStatsDir = new File(Environment.getDataSystemCeDirectory(userId), + "usagestats"); + final UserUsageStatsService service = new UserUsageStatsService(getContext(), userId, + usageStatsDir, this); + try { + service.init(currentTimeMillis, installedPackages); + mUserState.put(userId, service); + } catch (Exception e) { + if (mUserManager.isUserUnlocked(userId)) { + Slog.w(TAG, "Failed to initialized unlocked user " + userId); + throw e; // rethrow the exception - user is unlocked + } else { + Slog.w(TAG, "Attempted to initialize service for stopped or removed user " + + userId); + } + } + } + private void migrateStatsToSystemCeIfNeededLocked(int userId) { final File usageStatsDir = new File(Environment.getDataSystemCeDirectory(userId), "usagestats"); @@ -694,7 +733,6 @@ public class UsageStatsService extends SystemService implements return; } - final long timeNow = System.currentTimeMillis(); final long elapsedRealtime = SystemClock.elapsedRealtime(); if (event.mPackage != null @@ -789,8 +827,7 @@ public class UsageStatsService extends SystemService implements break; } - final UserUsageStatsService service = - getUserDataAndInitializeIfNeededLocked(userId, timeNow); + final UserUsageStatsService service = getUserUsageStatsServiceLocked(userId); if (service == null) { return; // user was stopped or removed } @@ -841,15 +878,18 @@ public class UsageStatsService extends SystemService implements mAppStandby.onUserRemoved(userId); mAppTimeLimit.onUserRemoved(userId); } + // Cancel any scheduled jobs for this user since the user is being removed. + UsageStatsIdleService.cancelJob(getContext(), userId); } /** * Called by the Handler for message MSG_PACKAGE_REMOVED. */ private void onPackageRemoved(int userId, String packageName) { + final int tokenRemoved; synchronized (mLock) { final long timeRemoved = System.currentTimeMillis(); - if (!mUserUnlockedStates.get(userId, false)) { + if (!mUserUnlockedStates.get(userId)) { // If user is not unlocked and a package is removed for them, we will handle it // when the user service is initialized and package manager is queried. return; @@ -859,7 +899,30 @@ public class UsageStatsService extends SystemService implements return; } - userService.onPackageRemoved(packageName, timeRemoved); + tokenRemoved = userService.onPackageRemoved(packageName, timeRemoved); + } + + // Schedule a job to prune any data related to this package. + if (tokenRemoved != PackagesTokenData.UNASSIGNED_TOKEN) { + UsageStatsIdleService.scheduleJob(getContext(), userId); + } + } + + /** + * Called by the Binder stub. + */ + private boolean pruneUninstalledPackagesData(int userId) { + synchronized (mLock) { + if (!mUserUnlockedStates.get(userId)) { + return false; // user is no longer unlocked + } + + final UserUsageStatsService userService = mUserState.get(userId); + if (userService == null) { + return false; // user was stopped or removed + } + + return userService.pruneUninstalledPackagesData(); } } @@ -874,8 +937,7 @@ public class UsageStatsService extends SystemService implements return null; } - final UserUsageStatsService service = - getUserDataAndInitializeIfNeededLocked(userId, System.currentTimeMillis()); + final UserUsageStatsService service = getUserUsageStatsServiceLocked(userId); if (service == null) { return null; // user was stopped or removed } @@ -909,8 +971,7 @@ public class UsageStatsService extends SystemService implements return null; } - final UserUsageStatsService service = - getUserDataAndInitializeIfNeededLocked(userId, System.currentTimeMillis()); + final UserUsageStatsService service = getUserUsageStatsServiceLocked(userId); if (service == null) { return null; // user was stopped or removed } @@ -929,8 +990,7 @@ public class UsageStatsService extends SystemService implements return null; } - final UserUsageStatsService service = - getUserDataAndInitializeIfNeededLocked(userId, System.currentTimeMillis()); + final UserUsageStatsService service = getUserUsageStatsServiceLocked(userId); if (service == null) { return null; // user was stopped or removed } @@ -949,8 +1009,7 @@ public class UsageStatsService extends SystemService implements return null; } - final UserUsageStatsService service = - getUserDataAndInitializeIfNeededLocked(userId, System.currentTimeMillis()); + final UserUsageStatsService service = getUserUsageStatsServiceLocked(userId); if (service == null) { return null; // user was stopped or removed } @@ -969,8 +1028,7 @@ public class UsageStatsService extends SystemService implements return null; } - final UserUsageStatsService service = - getUserDataAndInitializeIfNeededLocked(userId, System.currentTimeMillis()); + final UserUsageStatsService service = getUserUsageStatsServiceLocked(userId); if (service == null) { return null; // user was stopped or removed } @@ -2025,8 +2083,7 @@ public class UsageStatsService extends SystemService implements // Check to ensure that only user 0's data is b/r for now if (user == UserHandle.USER_SYSTEM) { - final UserUsageStatsService userStats = getUserDataAndInitializeIfNeededLocked( - user, System.currentTimeMillis()); + final UserUsageStatsService userStats = getUserUsageStatsServiceLocked(user); if (userStats == null) { return null; // user was stopped or removed } @@ -2046,8 +2103,7 @@ public class UsageStatsService extends SystemService implements } if (user == UserHandle.USER_SYSTEM) { - final UserUsageStatsService userStats = getUserDataAndInitializeIfNeededLocked( - user, System.currentTimeMillis()); + final UserUsageStatsService userStats = getUserUsageStatsServiceLocked(user); if (userStats == null) { return; // user was stopped or removed } @@ -2108,6 +2164,11 @@ public class UsageStatsService extends SystemService implements public AppUsageLimitData getAppUsageLimit(String packageName, UserHandle user) { return mAppTimeLimit.getAppUsageLimit(packageName, user); } + + @Override + public boolean pruneUninstalledPackagesData(int userId) { + return UsageStatsService.this.pruneUninstalledPackagesData(userId); + } } private class MyPackageMonitor extends PackageMonitor { diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java index c6a5fcfa8d2c..179b6490a7fd 100644 --- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java @@ -34,10 +34,7 @@ import android.app.usage.UsageEvents.Event; import android.app.usage.UsageStats; import android.app.usage.UsageStatsManager; import android.content.Context; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManagerInternal; import android.content.res.Configuration; -import android.os.Process; import android.os.SystemClock; import android.text.format.DateUtils; import android.util.ArrayMap; @@ -46,8 +43,8 @@ import android.util.AtomicFile; import android.util.Slog; import android.util.SparseIntArray; +import com.android.internal.util.ArrayUtils; import com.android.internal.util.IndentingPrintWriter; -import com.android.server.LocalServices; import com.android.server.usage.UsageStatsDatabase.StatCombiner; import java.io.File; @@ -55,7 +52,7 @@ import java.io.IOException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; -import java.util.HashSet; +import java.util.HashMap; import java.util.List; /** @@ -112,9 +109,12 @@ class UserUsageStatsService { mSystemTimeSnapshot = System.currentTimeMillis(); } - void init(final long currentTimeMillis) { - readPackageMappingsLocked(); + void init(final long currentTimeMillis, HashMap<String, Long> installedPackages) { + readPackageMappingsLocked(installedPackages); mDatabase.init(currentTimeMillis); + if (mDatabase.wasUpgradePerformed()) { + mDatabase.prunePackagesDataOnUpgrade(installedPackages); + } int nullCount = 0; for (int i = 0; i < mCurrentStats.length; i++) { @@ -170,52 +170,53 @@ class UserUsageStatsService { persistActiveStats(); } - void onPackageRemoved(String packageName, long timeRemoved) { - mDatabase.onPackageRemoved(packageName, timeRemoved); + int onPackageRemoved(String packageName, long timeRemoved) { + return mDatabase.onPackageRemoved(packageName, timeRemoved); } - private void readPackageMappingsLocked() { + private void readPackageMappingsLocked(HashMap<String, Long> installedPackages) { mDatabase.readMappingsLocked(); - cleanUpPackageMappingsLocked(); + updatePackageMappingsLocked(installedPackages); } /** - * Queries Package Manager for a list of installed packages and removes those packages from - * mPackagesTokenData which are not installed any more. + * Queries Job Scheduler for any pending data prune jobs and if any exist, it updates the + * package mappings in memory by removing those tokens. * This will only happen once per device boot, when the user is unlocked for the first time. + * + * @param installedPackages map of installed packages (package_name:package_install_time) */ - private void cleanUpPackageMappingsLocked() { - final long timeNow = System.currentTimeMillis(); - /* - Note (b/142501248): PackageManagerInternal#getInstalledApplications is not lightweight. - Once its implementation is updated, or it's replaced with a better alternative, update - the call here to use it. For now, using the heavy #getInstalledApplications is okay since - this clean-up is only performed once every boot. - */ - final PackageManagerInternal packageManagerInternal = - LocalServices.getService(PackageManagerInternal.class); - if (packageManagerInternal == null) { + private void updatePackageMappingsLocked(HashMap<String, Long> installedPackages) { + if (ArrayUtils.isEmpty(installedPackages)) { return; } - final List<ApplicationInfo> installedPackages = - packageManagerInternal.getInstalledApplications(0, mUserId, Process.SYSTEM_UID); - // convert the package list to a set for easy look-ups - final HashSet<String> packagesSet = new HashSet<>(installedPackages.size()); - for (int i = installedPackages.size() - 1; i >= 0; i--) { - packagesSet.add(installedPackages.get(i).packageName); - } - final List<String> removedPackages = new ArrayList<>(); + + final long timeNow = System.currentTimeMillis(); + final ArrayList<String> removedPackages = new ArrayList<>(); // populate list of packages that are found in the mappings but not in the installed list for (int i = mDatabase.mPackagesTokenData.packagesToTokensMap.size() - 1; i >= 0; i--) { - if (!packagesSet.contains(mDatabase.mPackagesTokenData.packagesToTokensMap.keyAt(i))) { - removedPackages.add(mDatabase.mPackagesTokenData.packagesToTokensMap.keyAt(i)); + final String packageName = mDatabase.mPackagesTokenData.packagesToTokensMap.keyAt(i); + if (!installedPackages.containsKey(packageName)) { + removedPackages.add(packageName); } } + if (removedPackages.isEmpty()) { + return; + } - // remove packages in the mappings that are no longer installed + // remove packages in the mappings that are no longer installed and persist to disk for (int i = removedPackages.size() - 1; i >= 0; i--) { mDatabase.mPackagesTokenData.removePackage(removedPackages.get(i), timeNow); } + try { + mDatabase.writeMappingsLocked(); + } catch (Exception e) { + Slog.w(TAG, "Unable to write updated package mappings file on service initialization."); + } + } + + boolean pruneUninstalledPackagesData() { + return mDatabase.pruneUninstalledPackagesData(); } private void onTimeChanged(long oldTime, long newTime) { diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java index 4c22ba9715b0..ae20aed2d3c7 100644 --- a/telecomm/java/android/telecom/Connection.java +++ b/telecomm/java/android/telecom/Connection.java @@ -35,9 +35,7 @@ import android.os.Message; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.SystemClock; -import android.telephony.Annotation.RilRadioTechnology; -import android.telephony.ServiceState; -import android.telephony.TelephonyManager; +import android.telephony.ims.ImsStreamMediaProfile; import android.util.ArraySet; import android.view.Surface; @@ -475,6 +473,52 @@ public abstract class Connection extends Conferenceable { //********************************************************************************************** /** + * Define IMS Audio Codec + */ + // Current audio codec is NONE + public static final int AUDIO_CODEC_NONE = ImsStreamMediaProfile.AUDIO_QUALITY_NONE; // 0 + // Current audio codec is AMR + public static final int AUDIO_CODEC_AMR = ImsStreamMediaProfile.AUDIO_QUALITY_AMR; // 1 + // Current audio codec is AMR_WB + public static final int AUDIO_CODEC_AMR_WB = ImsStreamMediaProfile.AUDIO_QUALITY_AMR_WB; // 2 + // Current audio codec is QCELP13K + public static final int AUDIO_CODEC_QCELP13K = ImsStreamMediaProfile.AUDIO_QUALITY_QCELP13K; //3 + // Current audio codec is EVRC + public static final int AUDIO_CODEC_EVRC = ImsStreamMediaProfile.AUDIO_QUALITY_EVRC; // 4 + // Current audio codec is EVRC_B + public static final int AUDIO_CODEC_EVRC_B = ImsStreamMediaProfile.AUDIO_QUALITY_EVRC_B; // 5 + // Current audio codec is EVRC_WB + public static final int AUDIO_CODEC_EVRC_WB = ImsStreamMediaProfile.AUDIO_QUALITY_EVRC_WB; // 6 + // Current audio codec is EVRC_NW + public static final int AUDIO_CODEC_EVRC_NW = ImsStreamMediaProfile.AUDIO_QUALITY_EVRC_NW; // 7 + // Current audio codec is GSM_EFR + public static final int AUDIO_CODEC_GSM_EFR = ImsStreamMediaProfile.AUDIO_QUALITY_GSM_EFR; // 8 + // Current audio codec is GSM_FR + public static final int AUDIO_CODEC_GSM_FR = ImsStreamMediaProfile.AUDIO_QUALITY_GSM_FR; // 9 + // Current audio codec is GSM_HR + public static final int AUDIO_CODEC_GSM_HR = ImsStreamMediaProfile.AUDIO_QUALITY_GSM_HR; // 10 + // Current audio codec is G711U + public static final int AUDIO_CODEC_G711U = ImsStreamMediaProfile.AUDIO_QUALITY_G711U; // 11 + // Current audio codec is G723 + public static final int AUDIO_CODEC_G723 = ImsStreamMediaProfile.AUDIO_QUALITY_G723; // 12 + // Current audio codec is G711A + public static final int AUDIO_CODEC_G711A = ImsStreamMediaProfile.AUDIO_QUALITY_G711A; // 13 + // Current audio codec is G722 + public static final int AUDIO_CODEC_G722 = ImsStreamMediaProfile.AUDIO_QUALITY_G722; // 14 + // Current audio codec is G711AB + public static final int AUDIO_CODEC_G711AB = ImsStreamMediaProfile.AUDIO_QUALITY_G711AB; // 15 + // Current audio codec is G729 + public static final int AUDIO_CODEC_G729 = ImsStreamMediaProfile.AUDIO_QUALITY_G729; // 16 + // Current audio codec is EVS_NB + public static final int AUDIO_CODEC_EVS_NB = ImsStreamMediaProfile.AUDIO_QUALITY_EVS_NB; // 17 + // Current audio codec is EVS_WB + public static final int AUDIO_CODEC_EVS_WB = ImsStreamMediaProfile.AUDIO_QUALITY_EVS_WB; // 18 + // Current audio codec is EVS_SWB + public static final int AUDIO_CODEC_EVS_SWB = ImsStreamMediaProfile.AUDIO_QUALITY_EVS_SWB; // 19 + // Current audio codec is EVS_FB + public static final int AUDIO_CODEC_EVS_FB = ImsStreamMediaProfile.AUDIO_QUALITY_EVS_FB; // 20 + + /** * Connection extra key used to store the last forwarded number associated with the current * connection. Used to communicate to the user interface that the connection was forwarded via * the specified number. @@ -567,6 +611,13 @@ public abstract class Connection extends Conferenceable { "android.telecom.extra.IS_RTT_AUDIO_PRESENT"; /** + * The audio codec in use for the current {@link Connection}, if known. Valid values include + * {@link #AUDIO_CODEC_AMR_WB} and {@link #AUDIO_CODEC_EVS_WB}. + */ + public static final String EXTRA_AUDIO_CODEC = + "android.telecom.extra.AUDIO_CODEC"; + + /** * Connection event used to inform Telecom that it should play the on hold tone. This is used * to play a tone when the peer puts the current call on hold. Sent to Telecom via * {@link #sendConnectionEvent(String, Bundle)}. diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl index de3a816eba03..204c37e9aa38 100644 --- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl +++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl @@ -314,8 +314,6 @@ interface ITelecomService { void addOrRemoveTestCallCompanionApp(String packageName, boolean isAdded); - void setTestAutoModeApp(String packageName); - /** * @see TelecomServiceImpl#setSystemDialer */ diff --git a/telephony/java/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java index 96ddf22b64b4..86630b0eb991 100644 --- a/telephony/java/com/android/internal/telephony/TelephonyPermissions.java +++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java @@ -156,6 +156,27 @@ public final class TelephonyPermissions { return false; } + /** + * Check whether the app with the given pid/uid can read phone state. + * + * <p>This method behaves in one of the following ways: + * <ul> + * <li>return true: if the caller has the READ_PRIVILEGED_PHONE_STATE permission, the + * READ_PHONE_STATE runtime permission, or carrier privileges on the given subId. + * <li>throw SecurityException: if the caller didn't declare any of these permissions, or, for + * apps which support runtime permissions, if the caller does not currently have any of + * these permissions. + * <li>return false: if the caller lacks all of these permissions and doesn't support runtime + * permissions. This implies that the user revoked the ability to read phone state + * manually (via AppOps). In this case we can't throw as it would break app compatibility, + * so we return false to indicate that the calling function should return dummy data. + * </ul> + * + * <p>Note: for simplicity, this method always returns false for callers using legacy + * permissions and who have had READ_PHONE_STATE revoked, even if they are carrier-privileged. + * Such apps should migrate to runtime permissions or stop requiring READ_PHONE_STATE on P+ + * devices. + */ @VisibleForTesting public static boolean checkReadPhoneState( Context context, Supplier<ITelephony> telephonySupplier, int subId, int pid, int uid, @@ -208,6 +229,20 @@ public final class TelephonyPermissions { callingPackage, callingFeatureId, message); } + /** + * Check whether the app with the given pid/uid can read phone state, or has carrier + * privileges on any active subscription. + * + * <p>If the app does not have carrier privilege, this method will return {@code false} instead + * of throwing a SecurityException. Therefore, the callers cannot tell the difference + * between M+ apps which declare the runtime permission but do not have it, and pre-M apps + * which declare the static permission but had access revoked via AppOps. Apps in the former + * category expect SecurityExceptions; apps in the latter don't. So this method is suitable for + * use only if the behavior in both scenarios is meant to be identical. + * + * @return {@code true} if the app can read phone state or has carrier privilege; + * {@code false} otherwise. + */ @VisibleForTesting public static boolean checkReadPhoneStateOnAnyActiveSub( Context context, Supplier<ITelephony> telephonySupplier, int pid, int uid, @@ -453,6 +488,11 @@ public final class TelephonyPermissions { context, TELEPHONY_SUPPLIER, subId, pid, uid, callingPackage, callingPackageName); } + /** + * Check whether the app with the given pid/uid can read the call log. + * @return {@code true} if the specified app has the read call log permission and AppOpp granted + * to it, {@code false} otherwise. + */ @VisibleForTesting public static boolean checkReadCallLog( Context context, Supplier<ITelephony> telephonySupplier, int subId, int pid, int uid, @@ -490,6 +530,12 @@ public final class TelephonyPermissions { callingPackage, callingFeatureId, message); } + /** + * Returns whether the caller can read phone numbers. + * + * <p>Besides apps with the ability to read phone state per {@link #checkReadPhoneState}, the + * default SMS app and apps with READ_SMS or READ_PHONE_NUMBERS can also read phone numbers. + */ @VisibleForTesting public static boolean checkReadPhoneNumber( Context context, Supplier<ITelephony> telephonySupplier, int subId, int pid, int uid, @@ -529,10 +575,10 @@ public final class TelephonyPermissions { } catch (SecurityException readPhoneNumberSecurityException) { } - throw new SecurityException(message + ": Neither user " + uid + - " nor current process has " + android.Manifest.permission.READ_PHONE_STATE + - ", " + android.Manifest.permission.READ_SMS + ", or " + - android.Manifest.permission.READ_PHONE_NUMBERS); + throw new SecurityException(message + ": Neither user " + uid + + " nor current process has " + android.Manifest.permission.READ_PHONE_STATE + + ", " + android.Manifest.permission.READ_SMS + ", or " + + android.Manifest.permission.READ_PHONE_NUMBERS); } /** @@ -543,8 +589,8 @@ public final class TelephonyPermissions { */ public static void enforceCallingOrSelfModifyPermissionOrCarrierPrivilege( Context context, int subId, String message) { - if (context.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE) == - PERMISSION_GRANTED) { + if (context.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + == PERMISSION_GRANTED) { return; } @@ -586,8 +632,8 @@ public final class TelephonyPermissions { } if (DBG) { - Rlog.d(LOG_TAG, "No READ_PRIVILEDED_PHONE_STATE permission, " + - "check carrier privilege next."); + Rlog.d(LOG_TAG, "No READ_PRIVILEDED_PHONE_STATE permission, " + + "check carrier privilege next."); } enforceCallingOrSelfCarrierPrivilege(subId, message); @@ -612,8 +658,8 @@ public final class TelephonyPermissions { private static void enforceCarrierPrivilege( Supplier<ITelephony> telephonySupplier, int subId, int uid, String message) { - if (getCarrierPrivilegeStatus(telephonySupplier, subId, uid) != - TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) { + if (getCarrierPrivilegeStatus(telephonySupplier, subId, uid) + != TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) { if (DBG) Rlog.e(LOG_TAG, "No Carrier Privilege."); throw new SecurityException(message); } diff --git a/telephony/java/android/telephony/Annotation.java b/telephony/java/android/telephony/Annotation.java index 72f758eba39a..f89bbc7d179a 100644 --- a/telephony/java/android/telephony/Annotation.java +++ b/telephony/java/android/telephony/Annotation.java @@ -1,6 +1,7 @@ package android.telephony; import android.annotation.IntDef; +import android.telecom.Connection; import android.telephony.data.ApnSetting; import java.lang.annotation.Retention; @@ -509,4 +510,30 @@ public class Annotation { ServiceState.RIL_RADIO_TECHNOLOGY_LTE_CA, ServiceState.RIL_RADIO_TECHNOLOGY_NR}) public @interface RilRadioTechnology {} + + @IntDef({ + Connection.AUDIO_CODEC_NONE, + Connection.AUDIO_CODEC_AMR, + Connection.AUDIO_CODEC_AMR_WB, + Connection.AUDIO_CODEC_QCELP13K, + Connection.AUDIO_CODEC_EVRC, + Connection.AUDIO_CODEC_EVRC_B, + Connection.AUDIO_CODEC_EVRC_WB, + Connection.AUDIO_CODEC_EVRC_NW, + Connection.AUDIO_CODEC_GSM_EFR, + Connection.AUDIO_CODEC_GSM_FR, + Connection.AUDIO_CODEC_G711U, + Connection.AUDIO_CODEC_G723, + Connection.AUDIO_CODEC_G711A, + Connection.AUDIO_CODEC_G722, + Connection.AUDIO_CODEC_G711AB, + Connection.AUDIO_CODEC_G729, + Connection.AUDIO_CODEC_EVS_NB, + Connection.AUDIO_CODEC_EVS_WB, + Connection.AUDIO_CODEC_EVS_SWB, + Connection.AUDIO_CODEC_EVS_FB + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ImsAudioCodec { + } } diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 98d76613bde5..1b86c09ce86c 100755 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -100,6 +100,16 @@ public class CarrierConfigManager { KEY_CARRIER_VOLTE_PROVISIONED_BOOL = "carrier_volte_provisioned_bool"; /** + * Boolean indicating the Supplementary Services(SS) is disable when airplane mode on in the + * Call Settings menu. + * {@code true}: SS is disable when airplane mode on. + * {@code false}: SS is enable when airplane mode on. + * The default value for this key is {@code false} + */ + public static final String KEY_DISABLE_SUPPLEMENTARY_SERVICES_IN_AIRPLANE_MODE_BOOL = + "disable_supplementary_services_in_airplane_mode_bool"; + + /** * Boolean indicating if the "Call forwarding" item is visible in the Call Settings menu. * true means visible. false means gone. * @hide @@ -3439,6 +3449,7 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_CALL_FORWARDING_WHEN_UNREACHABLE_SUPPORTED_BOOL, true); sDefaults.putBoolean(KEY_ADDITIONAL_SETTINGS_CALLER_ID_VISIBILITY_BOOL, true); sDefaults.putBoolean(KEY_ADDITIONAL_SETTINGS_CALL_WAITING_VISIBILITY_BOOL, true); + sDefaults.putBoolean(KEY_DISABLE_SUPPLEMENTARY_SERVICES_IN_AIRPLANE_MODE_BOOL, false); sDefaults.putBoolean(KEY_IGNORE_SIM_NETWORK_LOCKED_EVENTS_BOOL, false); sDefaults.putBoolean(KEY_MDN_IS_ADDITIONAL_VOICEMAIL_NUMBER_BOOL, false); sDefaults.putBoolean(KEY_OPERATOR_SELECTION_EXPAND_BOOL, true); diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java index 9ace86cea0c1..d2c851789b96 100644 --- a/telephony/java/android/telephony/ServiceState.java +++ b/telephony/java/android/telephony/ServiceState.java @@ -278,12 +278,9 @@ public class ServiceState implements Parcelable { */ public static final int UNKNOWN_ID = -1; - private String mVoiceOperatorAlphaLong; - private String mVoiceOperatorAlphaShort; - private String mVoiceOperatorNumeric; - private String mDataOperatorAlphaLong; - private String mDataOperatorAlphaShort; - private String mDataOperatorNumeric; + private String mOperatorAlphaLong; + private String mOperatorAlphaShort; + private String mOperatorNumeric; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) private boolean mIsManualNetworkSelection; @@ -379,12 +376,9 @@ public class ServiceState implements Parcelable { protected void copyFrom(ServiceState s) { mVoiceRegState = s.mVoiceRegState; mDataRegState = s.mDataRegState; - mVoiceOperatorAlphaLong = s.mVoiceOperatorAlphaLong; - mVoiceOperatorAlphaShort = s.mVoiceOperatorAlphaShort; - mVoiceOperatorNumeric = s.mVoiceOperatorNumeric; - mDataOperatorAlphaLong = s.mDataOperatorAlphaLong; - mDataOperatorAlphaShort = s.mDataOperatorAlphaShort; - mDataOperatorNumeric = s.mDataOperatorNumeric; + mOperatorAlphaLong = s.mOperatorAlphaLong; + mOperatorAlphaShort = s.mOperatorAlphaShort; + mOperatorNumeric = s.mOperatorNumeric; mIsManualNetworkSelection = s.mIsManualNetworkSelection; mCssIndicator = s.mCssIndicator; mNetworkId = s.mNetworkId; @@ -418,12 +412,9 @@ public class ServiceState implements Parcelable { public ServiceState(Parcel in) { mVoiceRegState = in.readInt(); mDataRegState = in.readInt(); - mVoiceOperatorAlphaLong = in.readString(); - mVoiceOperatorAlphaShort = in.readString(); - mVoiceOperatorNumeric = in.readString(); - mDataOperatorAlphaLong = in.readString(); - mDataOperatorAlphaShort = in.readString(); - mDataOperatorNumeric = in.readString(); + mOperatorAlphaLong = in.readString(); + mOperatorAlphaShort = in.readString(); + mOperatorNumeric = in.readString(); mIsManualNetworkSelection = in.readInt() != 0; mCssIndicator = (in.readInt() != 0); mNetworkId = in.readInt(); @@ -448,12 +439,9 @@ public class ServiceState implements Parcelable { public void writeToParcel(Parcel out, int flags) { out.writeInt(mVoiceRegState); out.writeInt(mDataRegState); - out.writeString(mVoiceOperatorAlphaLong); - out.writeString(mVoiceOperatorAlphaShort); - out.writeString(mVoiceOperatorNumeric); - out.writeString(mDataOperatorAlphaLong); - out.writeString(mDataOperatorAlphaShort); - out.writeString(mDataOperatorNumeric); + out.writeString(mOperatorAlphaLong); + out.writeString(mOperatorAlphaShort); + out.writeString(mOperatorNumeric); out.writeInt(mIsManualNetworkSelection ? 1 : 0); out.writeInt(mCssIndicator ? 1 : 0); out.writeInt(mNetworkId); @@ -691,7 +679,7 @@ public class ServiceState implements Parcelable { * @return long name of operator, null if unregistered or unknown */ public String getOperatorAlphaLong() { - return mVoiceOperatorAlphaLong; + return mOperatorAlphaLong; } /** @@ -699,18 +687,10 @@ public class ServiceState implements Parcelable { * @return long name of operator * @hide */ - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, + publicAlternatives = "Use {@link #getOperatorAlphaLong} instead.") public String getVoiceOperatorAlphaLong() { - return mVoiceOperatorAlphaLong; - } - - /** - * Get current registered data network operator name in long alphanumeric format. - * @return long name of voice operator - * @hide - */ - public String getDataOperatorAlphaLong() { - return mDataOperatorAlphaLong; + return mOperatorAlphaLong; } /** @@ -721,7 +701,7 @@ public class ServiceState implements Parcelable { * @return short name of operator, null if unregistered or unknown */ public String getOperatorAlphaShort() { - return mVoiceOperatorAlphaShort; + return mOperatorAlphaShort; } /** @@ -729,9 +709,10 @@ public class ServiceState implements Parcelable { * @return short name of operator, null if unregistered or unknown * @hide */ - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, + publicAlternatives = "Use {@link #getOperatorAlphaShort} instead.") public String getVoiceOperatorAlphaShort() { - return mVoiceOperatorAlphaShort; + return mOperatorAlphaShort; } /** @@ -739,9 +720,10 @@ public class ServiceState implements Parcelable { * @return short name of operator, null if unregistered or unknown * @hide */ - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, + publicAlternatives = "Use {@link #getOperatorAlphaShort} instead.") public String getDataOperatorAlphaShort() { - return mDataOperatorAlphaShort; + return mOperatorAlphaShort; } /** @@ -755,11 +737,11 @@ public class ServiceState implements Parcelable { * @hide */ public String getOperatorAlpha() { - if (TextUtils.isEmpty(mVoiceOperatorAlphaLong)) { - return mVoiceOperatorAlphaShort; + if (TextUtils.isEmpty(mOperatorAlphaLong)) { + return mOperatorAlphaShort; } - return mVoiceOperatorAlphaLong; + return mOperatorAlphaLong; } /** @@ -775,7 +757,7 @@ public class ServiceState implements Parcelable { * {@link com.android.internal.telephony.MccTable#countryCodeForMcc(int)}. */ public String getOperatorNumeric() { - return mVoiceOperatorNumeric; + return mOperatorNumeric; } /** @@ -785,7 +767,7 @@ public class ServiceState implements Parcelable { */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) public String getVoiceOperatorNumeric() { - return mVoiceOperatorNumeric; + return mOperatorNumeric; } /** @@ -793,9 +775,10 @@ public class ServiceState implements Parcelable { * @return numeric format of operator, null if unregistered or unknown * @hide */ - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, + publicAlternatives = "Use {@link #getOperatorNumeric} instead.") public String getDataOperatorNumeric() { - return mDataOperatorNumeric; + return mOperatorNumeric; } /** @@ -815,12 +798,9 @@ public class ServiceState implements Parcelable { mDataRegState, mChannelNumber, Arrays.hashCode(mCellBandwidths), - mVoiceOperatorAlphaLong, - mVoiceOperatorAlphaShort, - mVoiceOperatorNumeric, - mDataOperatorAlphaLong, - mDataOperatorAlphaShort, - mDataOperatorNumeric, + mOperatorAlphaLong, + mOperatorAlphaShort, + mOperatorNumeric, mIsManualNetworkSelection, mCssIndicator, mNetworkId, @@ -850,12 +830,9 @@ public class ServiceState implements Parcelable { && mIsManualNetworkSelection == s.mIsManualNetworkSelection && mChannelNumber == s.mChannelNumber && Arrays.equals(mCellBandwidths, s.mCellBandwidths) - && equalsHandlesNulls(mVoiceOperatorAlphaLong, s.mVoiceOperatorAlphaLong) - && equalsHandlesNulls(mVoiceOperatorAlphaShort, s.mVoiceOperatorAlphaShort) - && equalsHandlesNulls(mVoiceOperatorNumeric, s.mVoiceOperatorNumeric) - && equalsHandlesNulls(mDataOperatorAlphaLong, s.mDataOperatorAlphaLong) - && equalsHandlesNulls(mDataOperatorAlphaShort, s.mDataOperatorAlphaShort) - && equalsHandlesNulls(mDataOperatorNumeric, s.mDataOperatorNumeric) + && equalsHandlesNulls(mOperatorAlphaLong, s.mOperatorAlphaLong) + && equalsHandlesNulls(mOperatorAlphaShort, s.mOperatorAlphaShort) + && equalsHandlesNulls(mOperatorNumeric, s.mOperatorNumeric) && equalsHandlesNulls(mCssIndicator, s.mCssIndicator) && equalsHandlesNulls(mNetworkId, s.mNetworkId) && equalsHandlesNulls(mSystemId, s.mSystemId) @@ -1007,10 +984,8 @@ public class ServiceState implements Parcelable { .append(", mChannelNumber=").append(mChannelNumber) .append(", duplexMode()=").append(getDuplexMode()) .append(", mCellBandwidths=").append(Arrays.toString(mCellBandwidths)) - .append(", mVoiceOperatorAlphaLong=").append(mVoiceOperatorAlphaLong) - .append(", mVoiceOperatorAlphaShort=").append(mVoiceOperatorAlphaShort) - .append(", mDataOperatorAlphaLong=").append(mDataOperatorAlphaLong) - .append(", mDataOperatorAlphaShort=").append(mDataOperatorAlphaShort) + .append(", mOperatorAlphaLong=").append(mOperatorAlphaLong) + .append(", mOperatorAlphaShort=").append(mOperatorAlphaShort) .append(", isManualNetworkSelection=").append(mIsManualNetworkSelection) .append(mIsManualNetworkSelection ? "(manual)" : "(automatic)") .append(", getRilVoiceRadioTechnology=").append(getRilVoiceRadioTechnology()) @@ -1040,12 +1015,9 @@ public class ServiceState implements Parcelable { mDataRegState = STATE_OUT_OF_SERVICE; mChannelNumber = -1; mCellBandwidths = new int[0]; - mVoiceOperatorAlphaLong = null; - mVoiceOperatorAlphaShort = null; - mVoiceOperatorNumeric = null; - mDataOperatorAlphaLong = null; - mDataOperatorAlphaShort = null; - mDataOperatorNumeric = null; + mOperatorAlphaLong = null; + mOperatorAlphaShort = null; + mOperatorNumeric = null; mIsManualNetworkSelection = false; mCssIndicator = false; mNetworkId = -1; @@ -1204,26 +1176,9 @@ public class ServiceState implements Parcelable { } public void setOperatorName(String longName, String shortName, String numeric) { - mVoiceOperatorAlphaLong = longName; - mVoiceOperatorAlphaShort = shortName; - mVoiceOperatorNumeric = numeric; - mDataOperatorAlphaLong = longName; - mDataOperatorAlphaShort = shortName; - mDataOperatorNumeric = numeric; - } - - /** @hide */ - public void setVoiceOperatorName(String longName, String shortName, String numeric) { - mVoiceOperatorAlphaLong = longName; - mVoiceOperatorAlphaShort = shortName; - mVoiceOperatorNumeric = numeric; - } - - /** @hide */ - public void setDataOperatorName(String longName, String shortName, String numeric) { - mDataOperatorAlphaLong = longName; - mDataOperatorAlphaShort = shortName; - mDataOperatorNumeric = numeric; + mOperatorAlphaLong = longName; + mOperatorAlphaShort = shortName; + mOperatorNumeric = numeric; } /** @@ -1233,19 +1188,8 @@ public class ServiceState implements Parcelable { * @hide */ @UnsupportedAppUsage - public void setOperatorAlphaLong(String longName) { - mVoiceOperatorAlphaLong = longName; - mDataOperatorAlphaLong = longName; - } - - /** @hide */ - public void setVoiceOperatorAlphaLong(String longName) { - mVoiceOperatorAlphaLong = longName; - } - - /** @hide */ - public void setDataOperatorAlphaLong(String longName) { - mDataOperatorAlphaLong = longName; + public void setOperatorAlphaLong(@Nullable String longName) { + mOperatorAlphaLong = longName; } public void setIsManualSelection(boolean isManual) { @@ -1293,12 +1237,12 @@ public class ServiceState implements Parcelable { m.putInt("dataRegState", mDataRegState); m.putInt("dataRoamingType", getDataRoamingType()); m.putInt("voiceRoamingType", getVoiceRoamingType()); - m.putString("operator-alpha-long", mVoiceOperatorAlphaLong); - m.putString("operator-alpha-short", mVoiceOperatorAlphaShort); - m.putString("operator-numeric", mVoiceOperatorNumeric); - m.putString("data-operator-alpha-long", mDataOperatorAlphaLong); - m.putString("data-operator-alpha-short", mDataOperatorAlphaShort); - m.putString("data-operator-numeric", mDataOperatorNumeric); + m.putString("operator-alpha-long", mOperatorAlphaLong); + m.putString("operator-alpha-short", mOperatorAlphaShort); + m.putString("operator-numeric", mOperatorNumeric); + m.putString("data-operator-alpha-long", mOperatorAlphaLong); + m.putString("data-operator-alpha-short", mOperatorAlphaShort); + m.putString("data-operator-numeric", mOperatorNumeric); m.putBoolean("manual", mIsManualNetworkSelection); m.putInt("radioTechnology", getRilVoiceRadioTechnology()); m.putInt("dataRadioTechnology", getRadioTechnology()); @@ -1933,12 +1877,9 @@ public class ServiceState implements Parcelable { } if (!removeCoarseLocation) return state; - state.mDataOperatorAlphaLong = null; - state.mDataOperatorAlphaShort = null; - state.mDataOperatorNumeric = null; - state.mVoiceOperatorAlphaLong = null; - state.mVoiceOperatorAlphaShort = null; - state.mVoiceOperatorNumeric = null; + state.mOperatorAlphaLong = null; + state.mOperatorAlphaShort = null; + state.mOperatorNumeric = null; return state; } diff --git a/tools/processors/unsupportedappusage/test/Android.bp b/tests/ManagedProfileLifecycleStressTest/Android.bp index 49ea3d4bbc96..639ce3cfe935 100644 --- a/tools/processors/unsupportedappusage/test/Android.bp +++ b/tests/ManagedProfileLifecycleStressTest/Android.bp @@ -13,16 +13,11 @@ // limitations under the License. java_test_host { - name: "unsupportedappusage-processor-test", - + name: "ManagedProfileLifecycleStressTest", srcs: ["src/**/*.java"], - - static_libs: [ - "libjavac", - "unsupportedappusage-annotation-processor-lib", - "truth-host-prebuilt", - "mockito-host", - "junit-host", - "objenesis", + libs: ["tradefed"], + test_suites: ["device-tests"], + target_required: [ + "DummyDPC", ], } diff --git a/tests/ManagedProfileLifecycleStressTest/AndroidTest.xml b/tests/ManagedProfileLifecycleStressTest/AndroidTest.xml new file mode 100644 index 000000000000..e7dbc5118457 --- /dev/null +++ b/tests/ManagedProfileLifecycleStressTest/AndroidTest.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2019 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<configuration description="Stress test for managed profile lifecycle"> + <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" > + <option name="jar" value="ManagedProfileLifecycleStressTest.jar" /> + </test> +</configuration> diff --git a/tests/ManagedProfileLifecycleStressTest/app/DummyDPC/Android.bp b/tests/ManagedProfileLifecycleStressTest/app/DummyDPC/Android.bp new file mode 100644 index 000000000000..d95af340337e --- /dev/null +++ b/tests/ManagedProfileLifecycleStressTest/app/DummyDPC/Android.bp @@ -0,0 +1,20 @@ +// Copyright (C) 2019 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +android_test { + name: "DummyDPC", + defaults: ["cts_defaults"], + srcs: ["src/**/*.java"], + sdk_version: "current", +} diff --git a/tests/ManagedProfileLifecycleStressTest/app/DummyDPC/AndroidManifest.xml b/tests/ManagedProfileLifecycleStressTest/app/DummyDPC/AndroidManifest.xml new file mode 100644 index 000000000000..860940d4e025 --- /dev/null +++ b/tests/ManagedProfileLifecycleStressTest/app/DummyDPC/AndroidManifest.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2019 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.dummydpc"> + + <application + android:testOnly="true"> + <receiver + android:name="com.android.dummydpc.DummyDeviceAdminReceiver" + android:permission="android.permission.BIND_DEVICE_ADMIN"> + <meta-data android:name="android.app.device_admin" + android:resource="@xml/device_admin" /> + <intent-filter> + <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" /> + </intent-filter> + </receiver> + </application> +</manifest> diff --git a/tests/ManagedProfileLifecycleStressTest/app/DummyDPC/res/xml/device_admin.xml b/tests/ManagedProfileLifecycleStressTest/app/DummyDPC/res/xml/device_admin.xml new file mode 100644 index 000000000000..4b3581e3e8da --- /dev/null +++ b/tests/ManagedProfileLifecycleStressTest/app/DummyDPC/res/xml/device_admin.xml @@ -0,0 +1,4 @@ +<device-admin> + <uses-policies> + </uses-policies> +</device-admin> diff --git a/tests/ManagedProfileLifecycleStressTest/app/DummyDPC/src/com/android/dummydpc/DummyDeviceAdminReceiver.java b/tests/ManagedProfileLifecycleStressTest/app/DummyDPC/src/com/android/dummydpc/DummyDeviceAdminReceiver.java new file mode 100644 index 000000000000..92190b73b0ff --- /dev/null +++ b/tests/ManagedProfileLifecycleStressTest/app/DummyDPC/src/com/android/dummydpc/DummyDeviceAdminReceiver.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.dummydpc; + +import android.app.admin.DeviceAdminReceiver; + +/** + * Empty admin to use as a managed profile owner. + */ +public class DummyDeviceAdminReceiver extends DeviceAdminReceiver { +} + diff --git a/tests/ManagedProfileLifecycleStressTest/src/com/android/test/stress/ManagedProfileLifecycleStressTest.java b/tests/ManagedProfileLifecycleStressTest/src/com/android/test/stress/ManagedProfileLifecycleStressTest.java new file mode 100644 index 000000000000..e323592d40ca --- /dev/null +++ b/tests/ManagedProfileLifecycleStressTest/src/com/android/test/stress/ManagedProfileLifecycleStressTest.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.test.stress; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import com.android.tradefed.log.LogUtil.CLog; +import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; +import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * A test to exercise Android Framework parts related to creating, starting, stopping, and deleting + * a managed profile as much as possible. The aim is to catch any issues in this code before it + * affects managed profile CTS tests. + */ +@RunWith(DeviceJUnit4ClassRunner.class) +public class ManagedProfileLifecycleStressTest extends BaseHostJUnit4Test { + // Stop the test once this time limit has been reached. 25 minutes used as a limit to make total + // test time less than 30 minutes, so that it can be put into presubmit. + private static final int TIME_LIMIT_MINUTES = 25; + + private static final String DUMMY_DPC_APK = "DummyDPC.apk"; + private static final String DUMMY_DPC_COMPONENT = + "com.android.dummydpc/com.android.dummydpc.DummyDeviceAdminReceiver"; + private static final Pattern CREATE_USER_OUTPUT_REGEX = + Pattern.compile("Success: created user id (\\d+)"); + + /** + * Create, start, and kill managed profiles in a loop. + */ + @Test + public void testCreateStartDelete() throws Exception { + int iteration = 0; + final long deadline = System.nanoTime() + TimeUnit.MINUTES.toNanos(TIME_LIMIT_MINUTES); + while (System.nanoTime() < deadline) { + iteration++; + CLog.w("Iteration N" + iteration); + final int userId = createManagedProfile(); + startUser(userId); + installPackageAsUser(DUMMY_DPC_APK, true /* grantPermissions */, userId, "-t"); + setProfileOwner(DUMMY_DPC_COMPONENT, userId); + removeUser(userId); + } + CLog.w("Completed " + iteration + " iterations."); + } + + private int createManagedProfile() throws Exception { + final String output = getDevice().executeShellCommand( + "pm create-user --profileOf 0 --managed TestProfile"); + final Matcher matcher = CREATE_USER_OUTPUT_REGEX.matcher(output.trim()); + if (!matcher.matches() || matcher.groupCount() != 1) { + fail("user creation failed, output: " + output); + } + return Integer.parseInt(matcher.group(1)); + } + + private void setProfileOwner(String componentName, int userId) throws Exception { + String command = "dpm set-profile-owner --user " + userId + " '" + componentName + "'"; + String commandOutput = getDevice().executeShellCommand(command); + assertTrue("Unexpected dpm output: " + commandOutput, commandOutput.startsWith("Success:")); + } + + private void removeUser(int userId) throws Exception { + final String output = getDevice().executeShellCommand("pm remove-user " + userId).trim(); + assertEquals("Unexpected pm output: " + output, "Success: removed user", output); + } + + private void startUser(int userId) throws Exception { + final String output = getDevice().executeShellCommand("am start-user -w " + userId).trim(); + assertEquals("Unexpected am output: " + output, "Success: user started", output); + } +} diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index fd3ed7d0eb6c..a24426b43059 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -3134,14 +3134,11 @@ public class ConnectivityServiceTest { .addTransportType(TRANSPORT_CELLULAR).build(); final TestNetworkCallback cellCallback = new TestNetworkCallback(); mCm.requestNetwork(cellRequest, cellCallback); - // NOTE: This request causes the network's capabilities to change. This - // is currently delivered before the onAvailable() callbacks. - // TODO: Fix this. - cellCallback.expectCapabilitiesWith(NET_CAPABILITY_FOREGROUND, mCellNetworkAgent); cellCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); fgCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); // Expect a network capabilities update with FOREGROUND, because the most recent // request causes its state to change. + cellCallback.expectCapabilitiesWith(NET_CAPABILITY_FOREGROUND, mCellNetworkAgent); callback.expectCapabilitiesWith(NET_CAPABILITY_FOREGROUND, mCellNetworkAgent); assertTrue(isForegroundNetwork(mCellNetworkAgent)); assertTrue(isForegroundNetwork(mWiFiNetworkAgent)); diff --git a/tools/processors/unsupportedappusage/Android.bp b/tools/processors/unsupportedappusage/Android.bp deleted file mode 100644 index 1e96234543c8..000000000000 --- a/tools/processors/unsupportedappusage/Android.bp +++ /dev/null @@ -1,34 +0,0 @@ - -java_library_host { - name: "unsupportedappusage-annotation-processor-lib", - srcs: [ - "src/**/*.java", - ], - static_libs: [ - "guava", - "unsupportedappusage-annotation" - ], - openjdk9: { - javacflags: [ - "--add-modules=jdk.compiler", - "--add-exports jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", - "--add-exports jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED", - "--add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", - "--add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", - ], - }, -} - -java_plugin { - name: "unsupportedappusage-annotation-processor", - processor_class: "android.processor.unsupportedappusage.UnsupportedAppUsageProcessor", - - java_resources: [ - "META-INF/**/*", - ], - static_libs: [ - "unsupportedappusage-annotation-processor-lib" - ], - - use_tools_jar: true, -} diff --git a/tools/processors/unsupportedappusage/META-INF/services/javax.annotation.processing.Processor b/tools/processors/unsupportedappusage/META-INF/services/javax.annotation.processing.Processor deleted file mode 100644 index 4a969d319070..000000000000 --- a/tools/processors/unsupportedappusage/META-INF/services/javax.annotation.processing.Processor +++ /dev/null @@ -1 +0,0 @@ -android.processor.unsupportedappusage.UnsupportedAppUsageProcessor diff --git a/tools/processors/unsupportedappusage/src/android/processor/unsupportedappusage/SignatureBuilder.java b/tools/processors/unsupportedappusage/src/android/processor/unsupportedappusage/SignatureBuilder.java deleted file mode 100644 index 65fc733fa364..000000000000 --- a/tools/processors/unsupportedappusage/src/android/processor/unsupportedappusage/SignatureBuilder.java +++ /dev/null @@ -1,258 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.processor.unsupportedappusage; - -import static javax.lang.model.element.ElementKind.PACKAGE; -import static javax.tools.Diagnostic.Kind.ERROR; -import static javax.tools.Diagnostic.Kind.WARNING; - -import com.google.common.base.Strings; -import com.google.common.collect.ImmutableMap; -import com.sun.tools.javac.code.Type; - -import java.lang.annotation.Annotation; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -import javax.annotation.processing.Messager; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.PackageElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.ArrayType; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; - -/** - * Builds a dex signature for a given method or field. - */ -public class SignatureBuilder { - - private static final Map<TypeKind, String> TYPE_MAP = ImmutableMap.<TypeKind, String>builder() - .put(TypeKind.BOOLEAN, "Z") - .put(TypeKind.BYTE, "B") - .put(TypeKind.CHAR, "C") - .put(TypeKind.DOUBLE, "D") - .put(TypeKind.FLOAT, "F") - .put(TypeKind.INT, "I") - .put(TypeKind.LONG, "J") - .put(TypeKind.SHORT, "S") - .put(TypeKind.VOID, "V") - .build(); - - private final Messager mMessager; - - /** - * Exception used internally when we can't build a signature. Whenever this is thrown, an error - * will also be written to the Messager. - */ - private class SignatureBuilderException extends Exception { - public SignatureBuilderException(String message) { - super(message); - } - - public void report(Element offendingElement) { - mMessager.printMessage(ERROR, getMessage(), offendingElement); - } - } - - public SignatureBuilder(Messager messager) { - mMessager = messager; - } - - /** - * Returns a list of enclosing elements for the given element, with the package first, and - * excluding the element itself. - */ - private List<Element> getEnclosingElements(Element e) { - List<Element> enclosing = new ArrayList<>(); - e = e.getEnclosingElement(); // don't include the element itself. - while (e != null) { - enclosing.add(e); - e = e.getEnclosingElement(); - } - Collections.reverse(enclosing); - return enclosing; - } - - /** - * Get the dex signature for a clazz, in format "Lpackage/name/Outer$Inner;" - */ - private String getClassSignature(TypeElement clazz) { - StringBuilder sb = new StringBuilder("L"); - for (Element enclosing : getEnclosingElements(clazz)) { - switch (enclosing.getKind()) { - case MODULE: - // ignore this. - break; - case PACKAGE: - sb.append(((PackageElement) enclosing) - .getQualifiedName() - .toString() - .replace('.', '/')); - sb.append('/'); - break; - default: - sb.append(enclosing.getSimpleName()).append('$'); - break; - } - - } - return sb - .append(clazz.getSimpleName()) - .append(";") - .toString(); - } - - /** - * Returns the type signature for a given type. For primitive types, a single character. - * For classes, the class signature. For arrays, a "[" preceeding the component type. - */ - private String getTypeSignature(TypeMirror type) throws SignatureBuilderException { - String sig = TYPE_MAP.get(type.getKind()); - if (sig != null) { - return sig; - } - switch (type.getKind()) { - case ARRAY: - return "[" + getTypeSignature(((ArrayType) type).getComponentType()); - case DECLARED: - Element declaring = ((DeclaredType) type).asElement(); - if (!(declaring instanceof TypeElement)) { - throw new SignatureBuilderException( - "Can't handle declared type of kind " + declaring.getKind()); - } - return getClassSignature((TypeElement) declaring); - case TYPEVAR: - Type.TypeVar typeVar = (Type.TypeVar) type; - if (typeVar.getLowerBound().getKind() != TypeKind.NULL) { - return getTypeSignature(typeVar.getLowerBound()); - } else if (typeVar.getUpperBound().getKind() != TypeKind.NULL) { - return getTypeSignature(typeVar.getUpperBound()); - } else { - throw new SignatureBuilderException("Can't handle typevar with no bound"); - } - - default: - throw new SignatureBuilderException("Can't handle type of kind " + type.getKind()); - } - } - - /** - * Get the signature for an executable, either a method or a constructor. - * - * @param name "<init>" for constructor, else the method name - * @param method The executable element in question. - */ - private String getExecutableSignature(CharSequence name, ExecutableElement method) - throws SignatureBuilderException { - StringBuilder sig = new StringBuilder(); - sig.append(getClassSignature((TypeElement) method.getEnclosingElement())) - .append("->") - .append(name) - .append("("); - for (VariableElement param : method.getParameters()) { - sig.append(getTypeSignature(param.asType())); - } - sig.append(")") - .append(getTypeSignature(method.getReturnType())); - return sig.toString(); - } - - private String buildMethodSignature(ExecutableElement method) throws SignatureBuilderException { - return getExecutableSignature(method.getSimpleName(), method); - } - - private String buildConstructorSignature(ExecutableElement cons) - throws SignatureBuilderException { - return getExecutableSignature("<init>", cons); - } - - private String buildFieldSignature(VariableElement field) throws SignatureBuilderException { - StringBuilder sig = new StringBuilder(); - sig.append(getClassSignature((TypeElement) field.getEnclosingElement())) - .append("->") - .append(field.getSimpleName()) - .append(":") - .append(getTypeSignature(field.asType())) - ; - return sig.toString(); - } - - /** - * Creates the signature for an annotated element. - * - * @param annotationType type of annotation being processed. - * @param element element for which we want to create a signature. - */ - public String buildSignature(Class<? extends Annotation> annotationType, Element element) { - try { - String signature; - switch (element.getKind()) { - case METHOD: - signature = buildMethodSignature((ExecutableElement) element); - break; - case CONSTRUCTOR: - signature = buildConstructorSignature((ExecutableElement) element); - break; - case FIELD: - signature = buildFieldSignature((VariableElement) element); - break; - default: - return null; - } - // Obtain annotation objects - Annotation annotation = element.getAnnotation(annotationType); - if (annotation == null) { - throw new IllegalStateException( - "Element doesn't have any UnsupportedAppUsage annotation"); - } - try { - Method expectedSignatureMethod = annotationType.getMethod("expectedSignature"); - // If we have an expected signature on the annotation, warn if it doesn't match. - String expectedSignature = expectedSignatureMethod.invoke(annotation).toString(); - if (!Strings.isNullOrEmpty(expectedSignature)) { - if (!signature.equals(expectedSignature)) { - mMessager.printMessage( - WARNING, - String.format( - "Expected signature doesn't match generated signature.\n" - + " Expected: %s\n Generated: %s", - expectedSignature, signature), - element); - } - } - return signature; - } catch (NoSuchMethodException e) { - throw new IllegalStateException( - "Annotation type does not have expectedSignature parameter", e); - } catch (IllegalAccessException | InvocationTargetException e) { - throw new IllegalStateException( - "Could not get expectedSignature parameter for annotation", e); - } - } catch (SignatureBuilderException problem) { - problem.report(element); - return null; - } - } -} diff --git a/tools/processors/unsupportedappusage/src/android/processor/unsupportedappusage/UnsupportedAppUsageProcessor.java b/tools/processors/unsupportedappusage/src/android/processor/unsupportedappusage/UnsupportedAppUsageProcessor.java deleted file mode 100644 index 5bb956a1fea2..000000000000 --- a/tools/processors/unsupportedappusage/src/android/processor/unsupportedappusage/UnsupportedAppUsageProcessor.java +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.processor.unsupportedappusage; - -import static javax.tools.StandardLocation.CLASS_OUTPUT; - -import com.google.common.base.Joiner; -import com.google.common.collect.ImmutableSet; -import com.sun.tools.javac.model.JavacElements; -import com.sun.tools.javac.tree.JCTree; -import com.sun.tools.javac.util.Pair; -import com.sun.tools.javac.util.Position; - -import java.io.IOException; -import java.io.PrintStream; -import java.lang.annotation.Annotation; -import java.net.URLEncoder; -import java.util.Map; -import java.util.Set; -import java.util.TreeMap; -import java.util.stream.Stream; - -import javax.annotation.processing.AbstractProcessor; -import javax.annotation.processing.RoundEnvironment; -import javax.annotation.processing.SupportedAnnotationTypes; -import javax.lang.model.SourceVersion; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.AnnotationValue; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; - -/** - * Annotation processor for {@link UnsupportedAppUsage} annotations. - * - * This processor currently outputs a CSV file with a mapping of dex signatures to corresponding - * source positions. - * - * This is used for automating updates to the annotations themselves. - */ -@SupportedAnnotationTypes({"android.annotation.UnsupportedAppUsage", - "dalvik.annotation.compat.UnsupportedAppUsage" -}) -public class UnsupportedAppUsageProcessor extends AbstractProcessor { - - // Package name for writing output. Output will be written to the "class output" location within - // this package. - private static final String PACKAGE = "unsupportedappusage"; - private static final String INDEX_CSV = "unsupportedappusage_index.csv"; - - private static final ImmutableSet<Class<? extends Annotation>> SUPPORTED_ANNOTATIONS = - ImmutableSet.of(android.annotation.UnsupportedAppUsage.class, - dalvik.annotation.compat.UnsupportedAppUsage.class); - private static final ImmutableSet<String> SUPPORTED_ANNOTATION_NAMES = - SUPPORTED_ANNOTATIONS.stream().map(annotation -> annotation.getCanonicalName()).collect( - ImmutableSet.toImmutableSet()); - - @Override - public SourceVersion getSupportedSourceVersion() { - return SourceVersion.latest(); - } - - /** - * Write the contents of a stream to a text file, with one line per item. - */ - private void writeToFile(String name, - String headerLine, - Stream<?> contents) throws IOException { - PrintStream out = new PrintStream(processingEnv.getFiler().createResource( - CLASS_OUTPUT, - PACKAGE, - name) - .openOutputStream()); - out.println(headerLine); - contents.forEach(o -> out.println(o)); - if (out.checkError()) { - throw new IOException("Error when writing to " + name); - } - out.close(); - } - - /** - * Find the annotation mirror for the @UnsupportedAppUsage annotation on the given element. - */ - private AnnotationMirror getUnsupportedAppUsageAnnotationMirror(Element e) { - for (AnnotationMirror m : e.getAnnotationMirrors()) { - TypeElement type = (TypeElement) m.getAnnotationType().asElement(); - if (SUPPORTED_ANNOTATION_NAMES.contains(type.getQualifiedName().toString())) { - return m; - } - } - return null; - } - - /** - * Returns a CSV header line for the columns returned by - * {@link #getAnnotationIndex(String, Element)}. - */ - private String getCsvHeaders() { - return Joiner.on(',').join( - "signature", - "file", - "startline", - "startcol", - "endline", - "endcol", - "properties" - ); - } - - private String encodeAnnotationProperties(AnnotationMirror annotation) { - StringBuilder sb = new StringBuilder(); - for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> e - : annotation.getElementValues().entrySet()) { - if (sb.length() > 0) { - sb.append("&"); - } - sb.append(e.getKey().getSimpleName()) - .append("=") - .append(URLEncoder.encode(e.getValue().toString())); - } - return sb.toString(); - } - - /** - * Maps an annotated element to the source position of the @UnsupportedAppUsage annotation - * attached to it. It returns CSV in the format: - * dex-signature,filename,start-line,start-col,end-line,end-col - * - * The positions refer to the annotation itself, *not* the annotated member. This can therefore - * be used to read just the annotation from the file, and to perform in-place edits on it. - * - * @param signature the dex signature for the element. - * @param annotatedElement The annotated element - * @return A single line of CSV text - */ - private String getAnnotationIndex(String signature, Element annotatedElement) { - JavacElements javacElem = (JavacElements) processingEnv.getElementUtils(); - AnnotationMirror unsupportedAppUsage = - getUnsupportedAppUsageAnnotationMirror(annotatedElement); - Pair<JCTree, JCTree.JCCompilationUnit> pair = - javacElem.getTreeAndTopLevel(annotatedElement, unsupportedAppUsage, null); - Position.LineMap lines = pair.snd.lineMap; - return Joiner.on(",").join( - signature, - pair.snd.getSourceFile().getName(), - lines.getLineNumber(pair.fst.pos().getStartPosition()), - lines.getColumnNumber(pair.fst.pos().getStartPosition()), - lines.getLineNumber(pair.fst.pos().getEndPosition(pair.snd.endPositions)), - lines.getColumnNumber(pair.fst.pos().getEndPosition(pair.snd.endPositions)), - encodeAnnotationProperties(unsupportedAppUsage)); - } - - /** - * This is the main entry point in the processor, called by the compiler. - */ - @Override - public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { - Map<String, Element> signatureMap = new TreeMap<>(); - SignatureBuilder sb = new SignatureBuilder(processingEnv.getMessager()); - for (Class<? extends Annotation> supportedAnnotation : SUPPORTED_ANNOTATIONS) { - Set<? extends Element> annotated = roundEnv.getElementsAnnotatedWith( - supportedAnnotation); - if (annotated.size() == 0) { - continue; - } - // Build signatures for each annotated member and put them in a map from signature to - // member. - for (Element e : annotated) { - String sig = sb.buildSignature(supportedAnnotation, e); - if (sig != null) { - signatureMap.put(sig, e); - } - } - } - - if (!signatureMap.isEmpty()) { - try { - writeToFile(INDEX_CSV, - getCsvHeaders(), - signatureMap.entrySet() - .stream() - .map(e -> getAnnotationIndex(e.getKey(), e.getValue()))); - } catch (IOException e) { - throw new RuntimeException("Failed to write output", e); - } - } - return true; - } -} diff --git a/tools/processors/unsupportedappusage/test/src/android/processor/unsupportedappusage/CsvReader.java b/tools/processors/unsupportedappusage/test/src/android/processor/unsupportedappusage/CsvReader.java deleted file mode 100644 index 23db99e81194..000000000000 --- a/tools/processors/unsupportedappusage/test/src/android/processor/unsupportedappusage/CsvReader.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.processor.unsupportedappusage; - -import com.google.common.base.Splitter; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class CsvReader { - - private final Splitter mSplitter; - private final List<String> mColumns; - private final List<Map<String, String>> mContents; - - public CsvReader(InputStream in) throws IOException { - mSplitter = Splitter.on(","); - BufferedReader br = new BufferedReader(new InputStreamReader(in)); - mColumns = mSplitter.splitToList(br.readLine()); - mContents = new ArrayList<>(); - String line = br.readLine(); - while (line != null) { - List<String> contents = mSplitter.splitToList(line); - Map<String, String> contentMap = new HashMap<>(); - for (int i = 0; i < Math.min(contents.size(), mColumns.size()); ++i) { - contentMap.put(mColumns.get(i), contents.get(i)); - } - mContents.add(contentMap); - line = br.readLine(); - } - br.close(); - } - - public List<String> getColumns() { - return mColumns; - } - - public List<Map<String, String>> getContents() { - return mContents; - } -} diff --git a/tools/processors/unsupportedappusage/test/src/android/processor/unsupportedappusage/UnsupportedAppUsageProcessorTest.java b/tools/processors/unsupportedappusage/test/src/android/processor/unsupportedappusage/UnsupportedAppUsageProcessorTest.java deleted file mode 100644 index 012e88f17924..000000000000 --- a/tools/processors/unsupportedappusage/test/src/android/processor/unsupportedappusage/UnsupportedAppUsageProcessorTest.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.processor.unsupportedappusage; - -import static com.google.common.truth.Truth.assertThat; - -import com.android.javac.Javac; - -import com.google.common.base.Joiner; - -import org.junit.Before; -import org.junit.Test; - -import java.io.IOException; -import java.util.Map; - -public class UnsupportedAppUsageProcessorTest { - - private Javac mJavac; - - @Before - public void setup() throws IOException { - mJavac = new Javac(); - mJavac.addSource("dalvik.annotation.compat.UnsupportedAppUsage", Joiner.on('\n').join( - "package dalvik.annotation.compat;", - "public @interface UnsupportedAppUsage {", - " String expectedSignature() default \"\";\n", - " String someProperty() default \"\";", - "}")); - } - - private CsvReader compileAndReadCsv() throws IOException { - mJavac.compileWithAnnotationProcessor(new UnsupportedAppUsageProcessor()); - return new CsvReader( - mJavac.getOutputFile("unsupportedappusage/unsupportedappusage_index.csv")); - } - - @Test - public void testSignatureFormat() throws Exception { - mJavac.addSource("a.b.Class", Joiner.on('\n').join( - "package a.b;", - "import dalvik.annotation.compat.UnsupportedAppUsage;", - "public class Class {", - " @UnsupportedAppUsage", - " public void method() {}", - "}")); - assertThat(compileAndReadCsv().getContents().get(0)).containsEntry( - "signature", "La/b/Class;->method()V" - ); - } - - @Test - public void testSourcePosition() throws Exception { - mJavac.addSource("a.b.Class", Joiner.on('\n').join( - "package a.b;", // 1 - "import dalvik.annotation.compat.UnsupportedAppUsage;", // 2 - "public class Class {", // 3 - " @UnsupportedAppUsage", // 4 - " public void method() {}", // 5 - "}")); - Map<String, String> row = compileAndReadCsv().getContents().get(0); - assertThat(row).containsEntry("startline", "4"); - assertThat(row).containsEntry("startcol", "3"); - assertThat(row).containsEntry("endline", "4"); - assertThat(row).containsEntry("endcol", "23"); - } - - @Test - public void testAnnotationProperties() throws Exception { - mJavac.addSource("a.b.Class", Joiner.on('\n').join( - "package a.b;", // 1 - "import dalvik.annotation.compat.UnsupportedAppUsage;", // 2 - "public class Class {", // 3 - " @UnsupportedAppUsage(someProperty=\"value\")", // 4 - " public void method() {}", // 5 - "}")); - assertThat(compileAndReadCsv().getContents().get(0)).containsEntry( - "properties", "someProperty=%22value%22"); - } - - -} diff --git a/wifi/java/android/net/wifi/IOnWifiActivityEnergyInfoListener.aidl b/wifi/java/android/net/wifi/IOnWifiActivityEnergyInfoListener.aidl new file mode 100644 index 000000000000..7e25fd8a3be2 --- /dev/null +++ b/wifi/java/android/net/wifi/IOnWifiActivityEnergyInfoListener.aidl @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.wifi; + +import android.os.connectivity.WifiActivityEnergyInfo; + +/** + * Interface for Wi-Fi activity energy info listener. + * + * @hide + */ +oneway interface IOnWifiActivityEnergyInfoListener +{ + /** + * Service to manager callback providing current Wi-Fi activity energy info. + * @param info the Wi-Fi activity energy info + */ + void onWifiActivityEnergyInfo(in WifiActivityEnergyInfo info); +} diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl index 032f66af2433..fccbcf7c101f 100644 --- a/wifi/java/android/net/wifi/IWifiManager.aidl +++ b/wifi/java/android/net/wifi/IWifiManager.aidl @@ -28,15 +28,15 @@ import android.net.wifi.IActionListener; import android.net.wifi.IDppCallback; import android.net.wifi.ILocalOnlyHotspotCallback; import android.net.wifi.INetworkRequestMatchCallback; +import android.net.wifi.IOnWifiActivityEnergyInfoListener; +import android.net.wifi.IOnWifiUsabilityStatsListener; import android.net.wifi.IScanResultsCallback; import android.net.wifi.ISoftApCallback; import android.net.wifi.ISuggestionConnectionStatusListener; import android.net.wifi.ITrafficStateCallback; import android.net.wifi.ITxPacketCountListener; -import android.net.wifi.IOnWifiUsabilityStatsListener; import android.net.wifi.ScanResult; import android.net.wifi.SoftApConfiguration; -import android.net.wifi.WifiActivityEnergyInfo; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiInfo; import android.net.wifi.WifiNetworkSuggestion; @@ -44,6 +44,7 @@ import android.net.wifi.WifiNetworkSuggestion; import android.os.Messenger; import android.os.ResultReceiver; import android.os.WorkSource; +import android.os.connectivity.WifiActivityEnergyInfo; /** * Interface that allows controlling and querying Wi-Fi connectivity. @@ -56,13 +57,7 @@ interface IWifiManager WifiActivityEnergyInfo reportActivityInfo(); - /** - * Requests the controller activity info asynchronously. - * The implementor is expected to reply with the - * {@link android.net.wifi.WifiActivityEnergyInfo} object placed into the Bundle with the key - * {@link android.os.BatteryStats#RESULT_RECEIVER_CONTROLLER_KEY}. The result code is ignored. - */ - oneway void requestActivityInfo(in ResultReceiver result); + oneway void getWifiActivityEnergyInfoAsync(in IOnWifiActivityEnergyInfoListener listener); ParceledListSlice getConfiguredNetworks(String packageName, String featureId); diff --git a/wifi/java/android/net/wifi/ScanResult.java b/wifi/java/android/net/wifi/ScanResult.java index 9956901434d0..729ef61f0e4d 100644 --- a/wifi/java/android/net/wifi/ScanResult.java +++ b/wifi/java/android/net/wifi/ScanResult.java @@ -499,6 +499,13 @@ public class ScanResult implements Parcelable { /** * @hide + */ + public boolean is6GHz() { + return ScanResult.is6GHz(frequency); + } + + /** + * @hide * TODO: makes real freq boundaries */ public static boolean is5GHz(int freq) { @@ -506,6 +513,13 @@ public class ScanResult implements Parcelable { } /** + * @hide + */ + public static boolean is6GHz(int freq) { + return freq > 5925 && freq < 7125; + } + + /** * @hide * anqp lines from supplicant BSS response */ diff --git a/wifi/java/android/net/wifi/SoftApConfiguration.java b/wifi/java/android/net/wifi/SoftApConfiguration.java index 8030bd66e313..d755053e60d2 100644 --- a/wifi/java/android/net/wifi/SoftApConfiguration.java +++ b/wifi/java/android/net/wifi/SoftApConfiguration.java @@ -388,7 +388,7 @@ public final class SoftApConfiguration implements Parcelable { @NonNull public Builder setBssid(@Nullable MacAddress bssid) { if (bssid != null) { - Preconditions.checkArgument(!bssid.equals(MacAddress.ALL_ZEROS_ADDRESS)); + Preconditions.checkArgument(!bssid.equals(WifiManager.ALL_ZEROS_MAC_ADDRESS)); Preconditions.checkArgument(!bssid.equals(MacAddress.BROADCAST_ADDRESS)); } mBssid = bssid; diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index 52f5eeebe7ad..0108d5aa936c 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -35,6 +35,7 @@ import android.content.Context; import android.content.pm.ParceledListSlice; import android.net.ConnectivityManager; import android.net.DhcpInfo; +import android.net.MacAddress; import android.net.Network; import android.net.NetworkStack; import android.net.wifi.hotspot2.IProvisioningCallback; @@ -48,6 +49,7 @@ import android.os.IBinder; import android.os.Looper; import android.os.RemoteException; import android.os.WorkSource; +import android.os.connectivity.WifiActivityEnergyInfo; import android.text.TextUtils; import android.util.Log; import android.util.Pair; @@ -1186,6 +1188,10 @@ public class WifiManager { /** Indicates an invalid SSID. */ public static final String UNKNOWN_SSID = "<unknown ssid>"; + /** @hide */ + public static final MacAddress ALL_ZEROS_MAC_ADDRESS = + MacAddress.fromString("00:00:00:00:00:00"); + /* Number of currently active WifiLocks and MulticastLocks */ @UnsupportedAppUsage private int mActiveLockCount; @@ -1241,6 +1247,7 @@ public class WifiManager { * <li>allowedAuthAlgorithms</li> * <li>allowedPairwiseCiphers</li> * <li>allowedGroupCiphers</li> + * <li>status</li> * </ul> * @return a list of network configurations in the form of a list * of {@link WifiConfiguration} objects. @@ -2391,6 +2398,82 @@ public class WifiManager { } /** + * Interface for Wi-Fi activity energy info listener. Should be implemented by applications and + * set when calling {@link WifiManager#getWifiActivityEnergyInfoAsync}. + * + * @hide + */ + @SystemApi + public interface OnWifiActivityEnergyInfoListener { + /** + * Called when Wi-Fi activity energy info is available. + * Note: this listener is triggered at most once for each call to + * {@link #getWifiActivityEnergyInfoAsync}. + * + * @param info the latest {@link WifiActivityEnergyInfo}, or null if unavailable. + */ + void onWifiActivityEnergyInfo(@Nullable WifiActivityEnergyInfo info); + } + + private static class OnWifiActivityEnergyInfoProxy + extends IOnWifiActivityEnergyInfoListener.Stub { + private final Object mLock = new Object(); + @Nullable @GuardedBy("mLock") private Executor mExecutor; + @Nullable @GuardedBy("mLock") private OnWifiActivityEnergyInfoListener mListener; + + OnWifiActivityEnergyInfoProxy(Executor executor, + OnWifiActivityEnergyInfoListener listener) { + mExecutor = executor; + mListener = listener; + } + + @Override + public void onWifiActivityEnergyInfo(WifiActivityEnergyInfo info) { + Executor executor; + OnWifiActivityEnergyInfoListener listener; + synchronized (mLock) { + if (mExecutor == null || mListener == null) { + return; + } + executor = mExecutor; + listener = mListener; + // null out to allow garbage collection, prevent triggering listener more than once + mExecutor = null; + mListener = null; + } + Binder.clearCallingIdentity(); + executor.execute(() -> listener.onWifiActivityEnergyInfo(info)); + } + } + + /** + * Request to get the current {@link WifiActivityEnergyInfo} asynchronously. + * Note: This method will return null if {@link #isEnhancedPowerReportingSupported()} returns + * false. + * + * @param executor the executor that the listener will be invoked on + * @param listener the listener that will receive the {@link WifiActivityEnergyInfo} object + * when it becomes available. The listener will be triggered at most once for + * each call to this method. + * + * @hide + */ + @SystemApi + @RequiresPermission(ACCESS_WIFI_STATE) + public void getWifiActivityEnergyInfoAsync( + @NonNull @CallbackExecutor Executor executor, + @NonNull OnWifiActivityEnergyInfoListener listener) { + if (executor == null) throw new IllegalArgumentException("executor cannot be null"); + if (listener == null) throw new IllegalArgumentException("listener cannot be null"); + try { + mService.getWifiActivityEnergyInfoAsync( + new OnWifiActivityEnergyInfoProxy(executor, listener)); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Request a scan for access points. Returns immediately. The availability * of the results is made known later by means of an asynchronous event sent * on completion of the scan. diff --git a/wifi/java/android/net/wifi/WifiNetworkScoreCache.java b/wifi/java/android/net/wifi/WifiNetworkScoreCache.java index 5a212a824452..be37c229dd14 100755 --- a/wifi/java/android/net/wifi/WifiNetworkScoreCache.java +++ b/wifi/java/android/net/wifi/WifiNetworkScoreCache.java @@ -22,7 +22,6 @@ import android.annotation.Nullable; import android.content.Context; import android.net.INetworkScoreCache; import android.net.NetworkKey; -import android.net.NetworkScoreManager; import android.net.ScoredNetwork; import android.os.Handler; import android.os.Process; @@ -39,10 +38,10 @@ import java.util.List; /** * {@link INetworkScoreCache} implementation for Wifi Networks. * + * TODO: This should not be part of wifi mainline module. * @hide */ -public class WifiNetworkScoreCache extends INetworkScoreCache.Stub - implements NetworkScoreManager.NetworkScoreCallback { +public class WifiNetworkScoreCache extends INetworkScoreCache.Stub { private static final String TAG = "WifiNetworkScoreCache"; private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG); @@ -248,17 +247,6 @@ public class WifiNetworkScoreCache extends INetworkScoreCache.Stub } @Override protected final void dump(FileDescriptor fd, PrintWriter writer, String[] args) { - WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE); - dumpWithLatestScanResults(fd, writer, args, wifiManager.getScanResults()); - } - - /** - * This is directly invoked from within Wifi-Service (on it's instance of this class), hence - * avoid making the WifiManager.getScanResults() call to avoid a deadlock. - */ - public final void dumpWithLatestScanResults( - FileDescriptor fd, PrintWriter writer, String[] args, - List<ScanResult> latestScanResults) { mContext.enforceCallingOrSelfPermission(permission.DUMP, TAG); String header = String.format("WifiNetworkScoreCache (%s/%d)", mContext.getPackageName(), Process.myUid()); @@ -269,7 +257,8 @@ public class WifiNetworkScoreCache extends INetworkScoreCache.Stub writer.println(" " + score); } writer.println(" Network scores for latest ScanResults:"); - for (ScanResult scanResult : latestScanResults) { + WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE); + for (ScanResult scanResult : wifiManager.getScanResults()) { writer.println( " " + buildNetworkKey(scanResult) + ": " + getNetworkScore(scanResult)); } diff --git a/wifi/java/android/net/wifi/WifiNetworkSpecifier.java b/wifi/java/android/net/wifi/WifiNetworkSpecifier.java index ba9dd37398a1..07afd7fb6714 100644 --- a/wifi/java/android/net/wifi/WifiNetworkSpecifier.java +++ b/wifi/java/android/net/wifi/WifiNetworkSpecifier.java @@ -49,11 +49,11 @@ public final class WifiNetworkSpecifier extends NetworkSpecifier implements Parc private static final String MATCH_ALL_SSID_PATTERN_PATH = ".*"; private static final String MATCH_EMPTY_SSID_PATTERN_PATH = ""; private static final Pair<MacAddress, MacAddress> MATCH_NO_BSSID_PATTERN1 = - new Pair(MacAddress.BROADCAST_ADDRESS, MacAddress.BROADCAST_ADDRESS); + new Pair<>(MacAddress.BROADCAST_ADDRESS, MacAddress.BROADCAST_ADDRESS); private static final Pair<MacAddress, MacAddress> MATCH_NO_BSSID_PATTERN2 = - new Pair(MacAddress.ALL_ZEROS_ADDRESS, MacAddress.BROADCAST_ADDRESS); + new Pair<>(WifiManager.ALL_ZEROS_MAC_ADDRESS, MacAddress.BROADCAST_ADDRESS); private static final Pair<MacAddress, MacAddress> MATCH_ALL_BSSID_PATTERN = - new Pair(MacAddress.ALL_ZEROS_ADDRESS, MacAddress.ALL_ZEROS_ADDRESS); + new Pair<>(WifiManager.ALL_ZEROS_MAC_ADDRESS, WifiManager.ALL_ZEROS_MAC_ADDRESS); private static final MacAddress MATCH_EXACT_BSSID_PATTERN_MASK = MacAddress.BROADCAST_ADDRESS; diff --git a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java index a5ca82c50627..e78104d3da38 100644 --- a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java +++ b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java @@ -545,7 +545,7 @@ public final class WifiNetworkSuggestion implements Parcelable { } if (mBssid != null && (mBssid.equals(MacAddress.BROADCAST_ADDRESS) - || mBssid.equals(MacAddress.ALL_ZEROS_ADDRESS))) { + || mBssid.equals(WifiManager.ALL_ZEROS_MAC_ADDRESS))) { throw new IllegalStateException("invalid bssid for suggestion"); } wifiConfiguration = buildWifiConfiguration(); diff --git a/wifi/java/android/net/wifi/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java index b2930772c217..760497b727cd 100644 --- a/wifi/java/android/net/wifi/WifiScanner.java +++ b/wifi/java/android/net/wifi/WifiScanner.java @@ -65,14 +65,17 @@ public class WifiScanner { /** @hide */ public static final int WIFI_BAND_INDEX_5_GHZ_DFS_ONLY = 2; /** @hide */ - public static final int WIFI_BAND_COUNT = 3; + public static final int WIFI_BAND_INDEX_6_GHZ = 3; + /** @hide */ + public static final int WIFI_BAND_COUNT = 4; /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = {"WIFI_BAND_INDEX_"}, value = { WIFI_BAND_INDEX_24_GHZ, WIFI_BAND_INDEX_5_GHZ, - WIFI_BAND_INDEX_5_GHZ_DFS_ONLY}) + WIFI_BAND_INDEX_5_GHZ_DFS_ONLY, + WIFI_BAND_INDEX_6_GHZ}) public @interface WifiBandIndex {} /** no band specified; use channel list instead */ @@ -83,6 +86,8 @@ public class WifiScanner { public static final int WIFI_BAND_5_GHZ = 1 << WIFI_BAND_INDEX_5_GHZ; /** DFS channels from 5 GHz band only */ public static final int WIFI_BAND_5_GHZ_DFS_ONLY = 1 << WIFI_BAND_INDEX_5_GHZ_DFS_ONLY; + /** 6 GHz band */ + public static final int WIFI_BAND_6_GHZ = 1 << WIFI_BAND_INDEX_6_GHZ; /** @hide */ @Retention(RetentionPolicy.SOURCE) @@ -90,7 +95,8 @@ public class WifiScanner { WIFI_BAND_UNSPECIFIED, WIFI_BAND_24_GHZ, WIFI_BAND_5_GHZ, - WIFI_BAND_5_GHZ_DFS_ONLY}) + WIFI_BAND_5_GHZ_DFS_ONLY, + WIFI_BAND_6_GHZ}) public @interface WifiBandBasic {} /** @@ -111,6 +117,11 @@ public class WifiScanner { /** Both 2.4 GHz band and 5 GHz band; with DFS channels */ public static final int WIFI_BAND_BOTH_WITH_DFS = WIFI_BAND_24_GHZ | WIFI_BAND_5_GHZ | WIFI_BAND_5_GHZ_DFS_ONLY; + /** 2.4 GHz band and 5 GHz band (no DFS channels) and 6 GHz */ + public static final int WIFI_BAND_24_5_6_GHZ = WIFI_BAND_BOTH | WIFI_BAND_6_GHZ; + /** 2.4 GHz band and 5 GHz band; with DFS channels and 6 GHz */ + public static final int WIFI_BAND_24_5_WITH_DFS_6_GHZ = + WIFI_BAND_BOTH_WITH_DFS | WIFI_BAND_6_GHZ; /** @hide */ @Retention(RetentionPolicy.SOURCE) @@ -122,14 +133,17 @@ public class WifiScanner { WIFI_BAND_5_GHZ_DFS_ONLY, WIFI_BAND_24_GHZ_WITH_5GHZ_DFS, WIFI_BAND_5_GHZ_WITH_DFS, - WIFI_BAND_BOTH_WITH_DFS}) + WIFI_BAND_BOTH_WITH_DFS, + WIFI_BAND_6_GHZ, + WIFI_BAND_24_5_6_GHZ, + WIFI_BAND_24_5_WITH_DFS_6_GHZ}) public @interface WifiBand {} /** * Max band value * @hide */ - public static final int WIFI_BAND_MAX = 8; + public static final int WIFI_BAND_MAX = 0x10; /** Minimum supported scanning period */ public static final int MIN_SCAN_PERIOD_MS = 1000; @@ -174,7 +188,7 @@ public class WifiScanner { @SystemApi @NonNull @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) - public List<Integer> getAvailableChannels(@WifiBand int band) { + public List<Integer> getAvailableChannels(int band) { try { Bundle bundle = mService.getAvailableChannels(band, mContext.getOpPackageName(), mContext.getFeatureId()); @@ -726,6 +740,8 @@ public class WifiScanner { public int min5GHzRssi; /** Minimum 2.4GHz RSSI for a BSSID to be considered */ public int min24GHzRssi; + /** Minimum 6GHz RSSI for a BSSID to be considered */ + public int min6GHzRssi; /** Maximum score that a network can have before bonuses */ public int initialScoreMax; /** @@ -739,6 +755,8 @@ public class WifiScanner { public int secureBonus; /** 5GHz RSSI score bonus (applied to all 5GHz networks) */ public int band5GHzBonus; + /** 6GHz RSSI score bonus (applied to all 5GHz networks) */ + public int band6GHzBonus; /** Pno Network filter list */ public PnoNetwork[] networkList; @@ -752,11 +770,13 @@ public class WifiScanner { dest.writeInt(isConnected ? 1 : 0); dest.writeInt(min5GHzRssi); dest.writeInt(min24GHzRssi); + dest.writeInt(min6GHzRssi); dest.writeInt(initialScoreMax); dest.writeInt(currentConnectionBonus); dest.writeInt(sameNetworkBonus); dest.writeInt(secureBonus); dest.writeInt(band5GHzBonus); + dest.writeInt(band6GHzBonus); if (networkList != null) { dest.writeInt(networkList.length); for (int i = 0; i < networkList.length; i++) { @@ -778,11 +798,13 @@ public class WifiScanner { settings.isConnected = in.readInt() == 1; settings.min5GHzRssi = in.readInt(); settings.min24GHzRssi = in.readInt(); + settings.min6GHzRssi = in.readInt(); settings.initialScoreMax = in.readInt(); settings.currentConnectionBonus = in.readInt(); settings.sameNetworkBonus = in.readInt(); settings.secureBonus = in.readInt(); settings.band5GHzBonus = in.readInt(); + settings.band6GHzBonus = in.readInt(); int numNetworks = in.readInt(); settings.networkList = new PnoNetwork[numNetworks]; for (int i = 0; i < numNetworks; i++) { diff --git a/wifi/java/com/android/server/wifi/BaseWifiService.java b/wifi/java/com/android/server/wifi/BaseWifiService.java index bc8683921321..367cfa069e74 100644 --- a/wifi/java/com/android/server/wifi/BaseWifiService.java +++ b/wifi/java/com/android/server/wifi/BaseWifiService.java @@ -25,6 +25,7 @@ import android.net.wifi.IActionListener; import android.net.wifi.IDppCallback; import android.net.wifi.ILocalOnlyHotspotCallback; import android.net.wifi.INetworkRequestMatchCallback; +import android.net.wifi.IOnWifiActivityEnergyInfoListener; import android.net.wifi.IOnWifiUsabilityStatsListener; import android.net.wifi.IScanResultsCallback; import android.net.wifi.IScanResultsListener; @@ -35,7 +36,6 @@ import android.net.wifi.ITxPacketCountListener; import android.net.wifi.IWifiManager; import android.net.wifi.ScanResult; import android.net.wifi.SoftApConfiguration; -import android.net.wifi.WifiActivityEnergyInfo; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiInfo; import android.net.wifi.WifiNetworkSuggestion; @@ -46,6 +46,7 @@ import android.os.IBinder; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.WorkSource; +import android.os.connectivity.WifiActivityEnergyInfo; import java.util.List; import java.util.Map; @@ -80,12 +81,16 @@ public class BaseWifiService extends IWifiManager.Stub { throw new UnsupportedOperationException(); } - @Override public void requestActivityInfo(ResultReceiver result) { throw new UnsupportedOperationException(); } @Override + public void getWifiActivityEnergyInfoAsync(IOnWifiActivityEnergyInfoListener listener) { + throw new UnsupportedOperationException(); + } + + @Override public ParceledListSlice getConfiguredNetworks(String packageName, String featureId) { throw new UnsupportedOperationException(); } diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java index b3a73bc3ea91..de4514997b1c 100644 --- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java +++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java @@ -16,15 +16,31 @@ package android.net.wifi; +import static android.net.wifi.WifiManager.ActionListener; +import static android.net.wifi.WifiManager.BUSY; +import static android.net.wifi.WifiManager.ERROR; import static android.net.wifi.WifiManager.LocalOnlyHotspotCallback.ERROR_GENERIC; import static android.net.wifi.WifiManager.LocalOnlyHotspotCallback.ERROR_INCOMPATIBLE_MODE; import static android.net.wifi.WifiManager.LocalOnlyHotspotCallback.ERROR_NO_CHANNEL; import static android.net.wifi.WifiManager.LocalOnlyHotspotCallback.ERROR_TETHERING_DISALLOWED; import static android.net.wifi.WifiManager.LocalOnlyHotspotCallback.REQUEST_REGISTERED; +import static android.net.wifi.WifiManager.NOT_AUTHORIZED; +import static android.net.wifi.WifiManager.OnWifiActivityEnergyInfoListener; import static android.net.wifi.WifiManager.SAP_START_FAILURE_GENERAL; +import static android.net.wifi.WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS; +import static android.net.wifi.WifiManager.STATUS_SUGGESTION_CONNECTION_FAILURE_AUTHENTICATION; +import static android.net.wifi.WifiManager.TxPacketCountListener; import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED; import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLING; import static android.net.wifi.WifiManager.WIFI_AP_STATE_FAILED; +import static android.net.wifi.WifiManager.WIFI_FEATURE_DPP; +import static android.net.wifi.WifiManager.WIFI_FEATURE_OWE; +import static android.net.wifi.WifiManager.WIFI_FEATURE_P2P; +import static android.net.wifi.WifiManager.WIFI_FEATURE_PASSPOINT; +import static android.net.wifi.WifiManager.WIFI_FEATURE_SCANNER; +import static android.net.wifi.WifiManager.WIFI_FEATURE_WPA3_SAE; +import static android.net.wifi.WifiManager.WIFI_FEATURE_WPA3_SUITE_B; +import static android.net.wifi.WifiManager.WpsCallback; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; @@ -71,6 +87,7 @@ import android.os.Handler; import android.os.HandlerExecutor; import android.os.IBinder; import android.os.RemoteException; +import android.os.connectivity.WifiActivityEnergyInfo; import android.os.test.TestLooper; import androidx.test.filters.SmallTest; @@ -113,6 +130,7 @@ public class WifiManagerTest { @Mock TrafficStateCallback mTrafficStateCallback; @Mock NetworkRequestMatchCallback mNetworkRequestMatchCallback; @Mock OnWifiUsabilityStatsListener mOnWifiUsabilityStatsListener; + @Mock OnWifiActivityEnergyInfoListener mOnWifiActivityEnergyInfoListener; @Mock SuggestionConnectionStatusListener mListener; @Mock Runnable mRunnable; @Mock Executor mExecutor; @@ -124,6 +142,7 @@ public class WifiManagerTest { private WifiManager mWifiManager; private WifiNetworkSuggestion mWifiNetworkSuggestion; private ScanResultsCallback mScanResultsCallback; + private WifiActivityEnergyInfo mWifiActivityEnergyInfo; @Before public void setUp() throws Exception { @@ -142,6 +161,7 @@ public class WifiManagerTest { mRunnable.run(); } }; + mWifiActivityEnergyInfo = new WifiActivityEnergyInfo(0, 0, 0, 0, 0, 0, 0); } /** @@ -1069,7 +1089,7 @@ public class WifiManagerTest { } - class WpsCallbackTester extends WifiManager.WpsCallback { + class WpsCallbackTester extends WpsCallback { public boolean mStarted = false; public boolean mSucceeded = false; public boolean mFailed = false; @@ -1101,7 +1121,7 @@ public class WifiManagerTest { WpsCallbackTester wpsCallback = new WpsCallbackTester(); mWifiManager.startWps(null, wpsCallback); assertTrue(wpsCallback.mFailed); - assertEquals(WifiManager.ERROR, wpsCallback.mFailureCode); + assertEquals(ERROR, wpsCallback.mFailureCode); assertFalse(wpsCallback.mStarted); assertFalse(wpsCallback.mSucceeded); verifyNoMoreInteractions(mWifiService); @@ -1124,7 +1144,7 @@ public class WifiManagerTest { WpsCallbackTester wpsCallback = new WpsCallbackTester(); mWifiManager.cancelWps(wpsCallback); assertTrue(wpsCallback.mFailed); - assertEquals(WifiManager.ERROR, wpsCallback.mFailureCode); + assertEquals(ERROR, wpsCallback.mFailureCode); assertFalse(wpsCallback.mStarted); assertFalse(wpsCallback.mSucceeded); verifyNoMoreInteractions(mWifiService); @@ -1437,13 +1457,13 @@ public class WifiManagerTest { public void addGetRemoveNetworkSuggestions() throws Exception { List<WifiNetworkSuggestion> testList = new ArrayList<>(); when(mWifiService.addNetworkSuggestions(any(List.class), anyString(), - nullable(String.class))).thenReturn(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS); + nullable(String.class))).thenReturn(STATUS_NETWORK_SUGGESTIONS_SUCCESS); when(mWifiService.removeNetworkSuggestions(any(List.class), anyString())).thenReturn( - WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS); + STATUS_NETWORK_SUGGESTIONS_SUCCESS); when(mWifiService.getNetworkSuggestions(anyString())) .thenReturn(testList); - assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS, + assertEquals(STATUS_NETWORK_SUGGESTIONS_SUCCESS, mWifiManager.addNetworkSuggestions(testList)); verify(mWifiService).addNetworkSuggestions(anyList(), eq(TEST_PACKAGE_NAME), nullable(String.class)); @@ -1451,7 +1471,7 @@ public class WifiManagerTest { assertEquals(testList, mWifiManager.getNetworkSuggestions()); verify(mWifiService).getNetworkSuggestions(eq(TEST_PACKAGE_NAME)); - assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS, + assertEquals(STATUS_NETWORK_SUGGESTIONS_SUCCESS, mWifiManager.removeNetworkSuggestions(new ArrayList<>())); verify(mWifiService).removeNetworkSuggestions(anyList(), eq(TEST_PACKAGE_NAME)); } @@ -1515,10 +1535,10 @@ public class WifiManagerTest { @Test public void testIsEnhancedOpenSupported() throws Exception { when(mWifiService.getSupportedFeatures()) - .thenReturn(new Long(WifiManager.WIFI_FEATURE_OWE)); + .thenReturn(new Long(WIFI_FEATURE_OWE)); assertTrue(mWifiManager.isEnhancedOpenSupported()); when(mWifiService.getSupportedFeatures()) - .thenReturn(new Long(~WifiManager.WIFI_FEATURE_OWE)); + .thenReturn(new Long(~WIFI_FEATURE_OWE)); assertFalse(mWifiManager.isEnhancedOpenSupported()); } @@ -1528,10 +1548,10 @@ public class WifiManagerTest { @Test public void testIsWpa3SaeSupported() throws Exception { when(mWifiService.getSupportedFeatures()) - .thenReturn(new Long(WifiManager.WIFI_FEATURE_WPA3_SAE)); + .thenReturn(new Long(WIFI_FEATURE_WPA3_SAE)); assertTrue(mWifiManager.isWpa3SaeSupported()); when(mWifiService.getSupportedFeatures()) - .thenReturn(new Long(~WifiManager.WIFI_FEATURE_WPA3_SAE)); + .thenReturn(new Long(~WIFI_FEATURE_WPA3_SAE)); assertFalse(mWifiManager.isWpa3SaeSupported()); } @@ -1541,10 +1561,10 @@ public class WifiManagerTest { @Test public void testIsWpa3SuiteBSupported() throws Exception { when(mWifiService.getSupportedFeatures()) - .thenReturn(new Long(WifiManager.WIFI_FEATURE_WPA3_SUITE_B)); + .thenReturn(new Long(WIFI_FEATURE_WPA3_SUITE_B)); assertTrue(mWifiManager.isWpa3SuiteBSupported()); when(mWifiService.getSupportedFeatures()) - .thenReturn(new Long(~WifiManager.WIFI_FEATURE_WPA3_SUITE_B)); + .thenReturn(new Long(~WIFI_FEATURE_WPA3_SUITE_B)); assertFalse(mWifiManager.isWpa3SuiteBSupported()); } @@ -1554,10 +1574,10 @@ public class WifiManagerTest { @Test public void testIsEasyConnectSupported() throws Exception { when(mWifiService.getSupportedFeatures()) - .thenReturn(new Long(WifiManager.WIFI_FEATURE_DPP)); + .thenReturn(new Long(WIFI_FEATURE_DPP)); assertTrue(mWifiManager.isEasyConnectSupported()); when(mWifiService.getSupportedFeatures()) - .thenReturn(new Long(~WifiManager.WIFI_FEATURE_DPP)); + .thenReturn(new Long(~WIFI_FEATURE_DPP)); assertFalse(mWifiManager.isEasyConnectSupported()); } @@ -1666,9 +1686,9 @@ public class WifiManagerTest { @Test public void testGetSupportedFeatures() throws Exception { long supportedFeatures = - WifiManager.WIFI_FEATURE_SCANNER - | WifiManager.WIFI_FEATURE_PASSPOINT - | WifiManager.WIFI_FEATURE_P2P; + WIFI_FEATURE_SCANNER + | WIFI_FEATURE_PASSPOINT + | WIFI_FEATURE_P2P; when(mWifiService.getSupportedFeatures()) .thenReturn(Long.valueOf(supportedFeatures)); @@ -1700,6 +1720,58 @@ public class WifiManagerTest { } /** + * Tests that passing a null Executor to {@link WifiManager#getWifiActivityEnergyInfoAsync} + * throws an exception. + */ + @Test(expected = IllegalArgumentException.class) + public void testGetWifiActivityInfoNullExecutor() throws Exception { + mWifiManager.getWifiActivityEnergyInfoAsync(null, mOnWifiActivityEnergyInfoListener); + } + + /** + * Tests that passing a null listener to {@link WifiManager#getWifiActivityEnergyInfoAsync} + * throws an exception. + */ + @Test(expected = IllegalArgumentException.class) + public void testGetWifiActivityInfoNullListener() throws Exception { + mWifiManager.getWifiActivityEnergyInfoAsync(mExecutor, null); + } + + /** Tests that the listener runs on the correct Executor. */ + @Test + public void testGetWifiActivityInfoRunsOnCorrectExecutor() throws Exception { + mWifiManager.getWifiActivityEnergyInfoAsync(mExecutor, mOnWifiActivityEnergyInfoListener); + ArgumentCaptor<IOnWifiActivityEnergyInfoListener> listenerCaptor = + ArgumentCaptor.forClass(IOnWifiActivityEnergyInfoListener.class); + verify(mWifiService).getWifiActivityEnergyInfoAsync(listenerCaptor.capture()); + IOnWifiActivityEnergyInfoListener listener = listenerCaptor.getValue(); + listener.onWifiActivityEnergyInfo(mWifiActivityEnergyInfo); + verify(mExecutor).execute(any()); + + // ensure that the executor is only triggered once + listener.onWifiActivityEnergyInfo(mWifiActivityEnergyInfo); + verify(mExecutor).execute(any()); + } + + /** Tests that the correct listener runs. */ + @Test + public void testGetWifiActivityInfoRunsCorrectListener() throws Exception { + int[] flag = {0}; + mWifiManager.getWifiActivityEnergyInfoAsync( + new SynchronousExecutor(), info -> flag[0]++); + ArgumentCaptor<IOnWifiActivityEnergyInfoListener> listenerCaptor = + ArgumentCaptor.forClass(IOnWifiActivityEnergyInfoListener.class); + verify(mWifiService).getWifiActivityEnergyInfoAsync(listenerCaptor.capture()); + IOnWifiActivityEnergyInfoListener listener = listenerCaptor.getValue(); + listener.onWifiActivityEnergyInfo(mWifiActivityEnergyInfo); + assertEquals(1, flag[0]); + + // ensure that the listener is only triggered once + listener.onWifiActivityEnergyInfo(mWifiActivityEnergyInfo); + assertEquals(1, flag[0]); + } + + /** * Test behavior of {@link WifiManager#getConnectionInfo()} */ @Test @@ -1756,11 +1828,11 @@ public class WifiManagerTest { } /** - * Test behavior of {@link WifiManager#connect(int, WifiManager.ActionListener)} + * Test behavior of {@link WifiManager#connect(int, ActionListener)} */ @Test public void testConnectWithListener() throws Exception { - WifiManager.ActionListener externalListener = mock(WifiManager.ActionListener.class); + ActionListener externalListener = mock(ActionListener.class); mWifiManager.connect(TEST_NETWORK_ID, externalListener); ArgumentCaptor<IActionListener> binderListenerCaptor = @@ -1775,43 +1847,43 @@ public class WifiManagerTest { verify(externalListener).onSuccess(); // Trigger on failure. - binderListenerCaptor.getValue().onFailure(WifiManager.BUSY); + binderListenerCaptor.getValue().onFailure(BUSY); mLooper.dispatchAll(); - verify(externalListener).onFailure(WifiManager.BUSY); + verify(externalListener).onFailure(BUSY); } /** - * Test behavior of {@link WifiManager#connect(int, WifiManager.ActionListener)} + * Test behavior of {@link WifiManager#connect(int, ActionListener)} */ @Test public void testConnectWithListenerHandleSecurityException() throws Exception { doThrow(new SecurityException()).when(mWifiService) .connect(eq(null), anyInt(), any(IBinder.class), any(IActionListener.class), anyInt()); - WifiManager.ActionListener externalListener = mock(WifiManager.ActionListener.class); + ActionListener externalListener = mock(ActionListener.class); mWifiManager.connect(TEST_NETWORK_ID, externalListener); mLooper.dispatchAll(); - verify(externalListener).onFailure(WifiManager.NOT_AUTHORIZED); + verify(externalListener).onFailure(NOT_AUTHORIZED); } /** - * Test behavior of {@link WifiManager#connect(int, WifiManager.ActionListener)} + * Test behavior of {@link WifiManager#connect(int, ActionListener)} */ @Test public void testConnectWithListenerHandleRemoteException() throws Exception { doThrow(new RemoteException()).when(mWifiService) .connect(eq(null), anyInt(), any(IBinder.class), any(IActionListener.class), anyInt()); - WifiManager.ActionListener externalListener = mock(WifiManager.ActionListener.class); + ActionListener externalListener = mock(ActionListener.class); mWifiManager.connect(TEST_NETWORK_ID, externalListener); mLooper.dispatchAll(); - verify(externalListener).onFailure(WifiManager.ERROR); + verify(externalListener).onFailure(ERROR); } /** - * Test behavior of {@link WifiManager#connect(int, WifiManager.ActionListener)} + * Test behavior of {@link WifiManager#connect(int, ActionListener)} */ @Test public void testConnectWithoutListener() throws Exception { @@ -1823,12 +1895,12 @@ public class WifiManagerTest { } /** - * Test behavior of {@link WifiManager#getTxPacketCount(WifiManager.TxPacketCountListener)} + * Test behavior of {@link WifiManager#getTxPacketCount(TxPacketCountListener)} */ @Test public void testGetTxPacketCount() throws Exception { - WifiManager.TxPacketCountListener externalListener = - mock(WifiManager.TxPacketCountListener.class); + TxPacketCountListener externalListener = + mock(TxPacketCountListener.class); mWifiManager.getTxPacketCount(externalListener); ArgumentCaptor<ITxPacketCountListener> binderListenerCaptor = @@ -1843,9 +1915,9 @@ public class WifiManagerTest { verify(externalListener).onSuccess(6); // Trigger on failure. - binderListenerCaptor.getValue().onFailure(WifiManager.BUSY); + binderListenerCaptor.getValue().onFailure(BUSY); mLooper.dispatchAll(); - verify(externalListener).onFailure(WifiManager.BUSY); + verify(externalListener).onFailure(BUSY); } /** @@ -1947,7 +2019,7 @@ public class WifiManagerTest { */ @Test public void testAddSuggestionConnectionStatusListenerAndReceiveEvent() throws Exception { - int errorCode = WifiManager.STATUS_SUGGESTION_CONNECTION_FAILURE_AUTHENTICATION; + int errorCode = STATUS_SUGGESTION_CONNECTION_FAILURE_AUTHENTICATION; ArgumentCaptor<ISuggestionConnectionStatusListener.Stub> callbackCaptor = ArgumentCaptor.forClass(ISuggestionConnectionStatusListener.Stub.class); Executor executor = new SynchronousExecutor(); @@ -1963,7 +2035,7 @@ public class WifiManagerTest { */ @Test public void testAddSuggestionConnectionStatusListenerWithTheTargetExecutor() throws Exception { - int errorCode = WifiManager.STATUS_SUGGESTION_CONNECTION_FAILURE_AUTHENTICATION; + int errorCode = STATUS_SUGGESTION_CONNECTION_FAILURE_AUTHENTICATION; ArgumentCaptor<ISuggestionConnectionStatusListener.Stub> callbackCaptor = ArgumentCaptor.forClass(ISuggestionConnectionStatusListener.Stub.class); mWifiManager.addSuggestionConnectionStatusListener(mExecutor, mListener); diff --git a/wifi/tests/src/android/net/wifi/WifiNetworkAgentSpecifierTest.java b/wifi/tests/src/android/net/wifi/WifiNetworkAgentSpecifierTest.java index e6eece85cb19..adc41f0df4b4 100644 --- a/wifi/tests/src/android/net/wifi/WifiNetworkAgentSpecifierTest.java +++ b/wifi/tests/src/android/net/wifi/WifiNetworkAgentSpecifierTest.java @@ -208,7 +208,7 @@ public class WifiNetworkAgentSpecifierTest { PatternMatcher ssidPattern = new PatternMatcher(TEST_SSID_PATTERN, PatternMatcher.PATTERN_PREFIX); Pair<MacAddress, MacAddress> bssidPattern = - Pair.create(MacAddress.ALL_ZEROS_ADDRESS, MacAddress.ALL_ZEROS_ADDRESS); + Pair.create(WifiManager.ALL_ZEROS_MAC_ADDRESS, WifiManager.ALL_ZEROS_MAC_ADDRESS); WifiConfiguration wificonfigurationNetworkSpecifier = new WifiConfiguration(); wificonfigurationNetworkSpecifier.allowedKeyManagement .set(WifiConfiguration.KeyMgmt.WPA_PSK); @@ -299,7 +299,7 @@ public class WifiNetworkAgentSpecifierTest { PatternMatcher ssidPattern = new PatternMatcher(TEST_SSID_PATTERN, PatternMatcher.PATTERN_PREFIX); Pair<MacAddress, MacAddress> bssidPattern = - Pair.create(MacAddress.ALL_ZEROS_ADDRESS, MacAddress.ALL_ZEROS_ADDRESS); + Pair.create(WifiManager.ALL_ZEROS_MAC_ADDRESS, WifiManager.ALL_ZEROS_MAC_ADDRESS); WifiConfiguration wificonfigurationNetworkSpecifier = new WifiConfiguration(); wificonfigurationNetworkSpecifier.allowedKeyManagement .set(WifiConfiguration.KeyMgmt.WPA_PSK); diff --git a/wifi/tests/src/android/net/wifi/WifiNetworkSpecifierTest.java b/wifi/tests/src/android/net/wifi/WifiNetworkSpecifierTest.java index edb43d8bdf52..16197443b9d9 100644 --- a/wifi/tests/src/android/net/wifi/WifiNetworkSpecifierTest.java +++ b/wifi/tests/src/android/net/wifi/WifiNetworkSpecifierTest.java @@ -65,8 +65,10 @@ public class WifiNetworkSpecifierTest { assertEquals(Process.myUid(), wifiNetworkSpecifier.requestorUid); assertEquals(TEST_SSID, wifiNetworkSpecifier.ssidPatternMatcher.getPath()); assertEquals(PATTERN_PREFIX, wifiNetworkSpecifier.ssidPatternMatcher.getType()); - assertEquals(MacAddress.ALL_ZEROS_ADDRESS, wifiNetworkSpecifier.bssidPatternMatcher.first); - assertEquals(MacAddress.ALL_ZEROS_ADDRESS, wifiNetworkSpecifier.bssidPatternMatcher.second); + assertEquals(WifiManager.ALL_ZEROS_MAC_ADDRESS, + wifiNetworkSpecifier.bssidPatternMatcher.first); + assertEquals(WifiManager.ALL_ZEROS_MAC_ADDRESS, + wifiNetworkSpecifier.bssidPatternMatcher.second); assertTrue(wifiNetworkSpecifier.wifiConfiguration.allowedKeyManagement .get(WifiConfiguration.KeyMgmt.NONE)); } @@ -210,7 +212,8 @@ public class WifiNetworkSpecifierTest { @Test(expected = IllegalStateException.class) public void testWifiNetworkSpecifierBuilderWithMatchAllBssidPattern() { new WifiNetworkSpecifier.Builder() - .setBssidPattern(MacAddress.ALL_ZEROS_ADDRESS, MacAddress.ALL_ZEROS_ADDRESS) + .setBssidPattern(WifiManager.ALL_ZEROS_MAC_ADDRESS, + WifiManager.ALL_ZEROS_MAC_ADDRESS) .build(); } @@ -265,7 +268,7 @@ public class WifiNetworkSpecifierTest { @Test(expected = IllegalStateException.class) public void testWifiNetworkSpecifierBuilderWithMatchNoneBssidPattern3() { new WifiNetworkSpecifier.Builder() - .setBssid(MacAddress.ALL_ZEROS_ADDRESS) + .setBssid(WifiManager.ALL_ZEROS_MAC_ADDRESS) .build(); } @@ -513,7 +516,8 @@ public class WifiNetworkSpecifierTest { WifiNetworkSpecifier specifier2 = new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL), - Pair.create(MacAddress.ALL_ZEROS_ADDRESS, MacAddress.ALL_ZEROS_ADDRESS), + Pair.create(WifiManager.ALL_ZEROS_MAC_ADDRESS, + WifiManager.ALL_ZEROS_MAC_ADDRESS), wifiConfiguration, TEST_UID, TEST_PACKAGE_NAME); diff --git a/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java b/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java index ce085f55b0dd..8a5a0fd6805b 100644 --- a/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java +++ b/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java @@ -294,7 +294,7 @@ public class WifiNetworkSuggestionTest { public void testWifiNetworkSuggestionBuilderWithInvalidAllZeroBssid() { new WifiNetworkSuggestion.Builder() .setSsid(TEST_SSID) - .setBssid(MacAddress.ALL_ZEROS_ADDRESS) + .setBssid(WifiManager.ALL_ZEROS_MAC_ADDRESS) .build(); } diff --git a/wifi/tests/src/android/net/wifi/WifiScannerTest.java b/wifi/tests/src/android/net/wifi/WifiScannerTest.java index b1436c90f5dd..fa4f711056db 100644 --- a/wifi/tests/src/android/net/wifi/WifiScannerTest.java +++ b/wifi/tests/src/android/net/wifi/WifiScannerTest.java @@ -79,15 +79,17 @@ public class WifiScannerTest { private static final boolean TEST_PNOSETTINGS_IS_CONNECTED = false; private static final int TEST_PNOSETTINGS_MIN_5GHZ_RSSI = -60; private static final int TEST_PNOSETTINGS_MIN_2GHZ_RSSI = -70; + private static final int TEST_PNOSETTINGS_MIN_6GHZ_RSSI = -55; private static final int TEST_PNOSETTINGS_INITIAL_SCORE_MAX = 50; private static final int TEST_PNOSETTINGS_CURRENT_CONNECTION_BONUS = 10; private static final int TEST_PNOSETTINGS_SAME_NETWORK_BONUS = 11; private static final int TEST_PNOSETTINGS_SECURE_BONUS = 12; private static final int TEST_PNOSETTINGS_BAND_5GHZ_BONUS = 13; + private static final int TEST_PNOSETTINGS_BAND_6GHZ_BONUS = 15; private static final String TEST_SSID_1 = "TEST1"; private static final String TEST_SSID_2 = "TEST2"; private static final int[] TEST_FREQUENCIES_1 = {}; - private static final int[] TEST_FREQUENCIES_2 = {2500, 5124}; + private static final int[] TEST_FREQUENCIES_2 = {2500, 5124, 6245}; private static final String DESCRIPTION_NOT_AUTHORIZED = "Not authorized"; private WifiScanner mWifiScanner; @@ -183,11 +185,13 @@ public class WifiScannerTest { pnoSettings.isConnected = TEST_PNOSETTINGS_IS_CONNECTED; pnoSettings.min5GHzRssi = TEST_PNOSETTINGS_MIN_5GHZ_RSSI; pnoSettings.min24GHzRssi = TEST_PNOSETTINGS_MIN_2GHZ_RSSI; + pnoSettings.min6GHzRssi = TEST_PNOSETTINGS_MIN_6GHZ_RSSI; pnoSettings.initialScoreMax = TEST_PNOSETTINGS_INITIAL_SCORE_MAX; pnoSettings.currentConnectionBonus = TEST_PNOSETTINGS_CURRENT_CONNECTION_BONUS; pnoSettings.sameNetworkBonus = TEST_PNOSETTINGS_SAME_NETWORK_BONUS; pnoSettings.secureBonus = TEST_PNOSETTINGS_SECURE_BONUS; pnoSettings.band5GHzBonus = TEST_PNOSETTINGS_BAND_5GHZ_BONUS; + pnoSettings.band6GHzBonus = TEST_PNOSETTINGS_BAND_6GHZ_BONUS; Parcel parcel = Parcel.obtain(); pnoSettings.writeToParcel(parcel, 0); @@ -200,6 +204,7 @@ public class WifiScannerTest { assertEquals(TEST_PNOSETTINGS_IS_CONNECTED, pnoSettingsDeserialized.isConnected); assertEquals(TEST_PNOSETTINGS_MIN_5GHZ_RSSI, pnoSettingsDeserialized.min5GHzRssi); assertEquals(TEST_PNOSETTINGS_MIN_2GHZ_RSSI, pnoSettingsDeserialized.min24GHzRssi); + assertEquals(TEST_PNOSETTINGS_MIN_6GHZ_RSSI, pnoSettingsDeserialized.min6GHzRssi); assertEquals(TEST_PNOSETTINGS_INITIAL_SCORE_MAX, pnoSettingsDeserialized.initialScoreMax); assertEquals(TEST_PNOSETTINGS_CURRENT_CONNECTION_BONUS, pnoSettingsDeserialized.currentConnectionBonus); @@ -207,6 +212,7 @@ public class WifiScannerTest { pnoSettingsDeserialized.sameNetworkBonus); assertEquals(TEST_PNOSETTINGS_SECURE_BONUS, pnoSettingsDeserialized.secureBonus); assertEquals(TEST_PNOSETTINGS_BAND_5GHZ_BONUS, pnoSettingsDeserialized.band5GHzBonus); + assertEquals(TEST_PNOSETTINGS_BAND_6GHZ_BONUS, pnoSettingsDeserialized.band6GHzBonus); // Test parsing of PnoNetwork assertEquals(pnoSettings.networkList.length, pnoSettingsDeserialized.networkList.length); |