diff options
16 files changed, 571 insertions, 109 deletions
diff --git a/Android.mk b/Android.mk index aea0c951052f..d3627e118ae3 100644 --- a/Android.mk +++ b/Android.mk @@ -36,15 +36,6 @@ include $(CLEAR_VARS) # always included. INTERNAL_SDK_SOURCE_DIRS := $(addprefix $(LOCAL_PATH)/,$(dirs_to_document)) -$(call dist-for-goals,sdk,$(INTERNAL_PLATFORM_API_FILE)) -$(call dist-for-goals,sdk,$(INTERNAL_PLATFORM_SYSTEM_API_FILE)) -$(call dist-for-goals,sdk,$(INTERNAL_PLATFORM_TEST_API_FILE)) -$(call dist-for-goals,sdk,$(INTERNAL_PLATFORM_API_FILE):apistubs/android/public/api/android.txt) -$(call dist-for-goals,sdk,$(INTERNAL_PLATFORM_SYSTEM_API_FILE):apistubs/android/system/api/android.txt) -$(call dist-for-goals,sdk,$(INTERNAL_PLATFORM_TEST_API_FILE):apistubs/android/test/api/android.txt) -$(call dist-for-goals,sdk,$(INTERNAL_PLATFORM_MODULE_LIB_API_FILE):apistubs/android/module-lib/api/android.txt) -$(call dist-for-goals,sdk,$(INTERNAL_PLATFORM_SYSTEM_SERVER_API_FILE):apistubs/android/system-server/api/android.txt) - # sdk.atree needs to copy the whole dir: $(OUT_DOCS)/offline-sdk to the final zip. # So keep offline-sdk-timestamp target here, and unzip offline-sdk-docs.zip to # $(OUT_DOCS)/offline-sdk. diff --git a/StubLibraries.bp b/StubLibraries.bp index 304f231cb3af..20c8a83ff83e 100644 --- a/StubLibraries.bp +++ b/StubLibraries.bp @@ -110,6 +110,11 @@ droidstubs { baseline_file: "api/lint-baseline.txt", }, }, + dist: { + targets: ["sdk", "win_sdk"], + dir: "apistubs/android/public/api", + dest: "android.txt", + }, jdiff_enabled: true, } @@ -151,6 +156,11 @@ droidstubs { baseline_file: "api/system-lint-baseline.txt", }, }, + dist: { + targets: ["sdk", "win_sdk"], + dir: "apistubs/android/system/api", + dest: "android.txt", + }, jdiff_enabled: true, } @@ -174,6 +184,11 @@ droidstubs { baseline_file: "api/test-lint-baseline.txt", }, }, + dist: { + targets: ["sdk", "win_sdk"], + dir: "apistubs/android/test/api", + dest: "android.txt", + }, } ///////////////////////////////////////////////////////////////////// @@ -213,6 +228,11 @@ droidstubs { baseline_file: "api/module-lib-lint-baseline.txt", }, }, + dist: { + targets: ["sdk", "win_sdk"], + dir: "apistubs/android/module-lib/api", + dest: "android.txt", + }, } diff --git a/packages/Tethering/res/values-mcc310-mnc004/config.xml b/packages/Tethering/res/values-mcc310-mnc004/config.xml index 8c627d5df058..5c5be0466a36 100644 --- a/packages/Tethering/res/values-mcc310-mnc004/config.xml +++ b/packages/Tethering/res/values-mcc310-mnc004/config.xml @@ -17,4 +17,7 @@ <!-- Delay(millisecond) to show no upstream notification after there's no Backhaul. Set delay to "0" for disable this feature. --> <integer name="delay_to_show_no_upstream_after_no_backhaul">5000</integer> + + <!-- Config for showing upstream roaming notification. --> + <bool name="config_upstream_roaming_notification">true</bool> </resources>
\ No newline at end of file diff --git a/packages/Tethering/res/values-mcc311-mnc480/config.xml b/packages/Tethering/res/values-mcc311-mnc480/config.xml index 8c627d5df058..5c5be0466a36 100644 --- a/packages/Tethering/res/values-mcc311-mnc480/config.xml +++ b/packages/Tethering/res/values-mcc311-mnc480/config.xml @@ -17,4 +17,7 @@ <!-- Delay(millisecond) to show no upstream notification after there's no Backhaul. Set delay to "0" for disable this feature. --> <integer name="delay_to_show_no_upstream_after_no_backhaul">5000</integer> + + <!-- Config for showing upstream roaming notification. --> + <bool name="config_upstream_roaming_notification">true</bool> </resources>
\ No newline at end of file diff --git a/packages/Tethering/res/values/config.xml b/packages/Tethering/res/values/config.xml index 780a015961b4..aed5ab8df6af 100644 --- a/packages/Tethering/res/values/config.xml +++ b/packages/Tethering/res/values/config.xml @@ -206,4 +206,9 @@ <!-- Delay(millisecond) to show no upstream notification after there's no Backhaul. Set delay to "-1" for disable this feature. --> <integer name="delay_to_show_no_upstream_after_no_backhaul">-1</integer> + + <!-- Cellular roaming notification is shown when upstream is cellular network and in roaming + state. --> + <!-- Config for showing upstream roaming notification. --> + <bool name="config_upstream_roaming_notification">false</bool> </resources> diff --git a/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java b/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java index 14d288699e99..97b19466b38b 100644 --- a/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java +++ b/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java @@ -81,6 +81,7 @@ import android.net.IpPrefix; import android.net.LinkAddress; import android.net.LinkProperties; import android.net.Network; +import android.net.NetworkCapabilities; import android.net.NetworkInfo; import android.net.TetherStatesParcel; import android.net.TetheredClient; @@ -1476,7 +1477,7 @@ public class Tethering { if (mTetherUpstream != newUpstream) { mTetherUpstream = newUpstream; mUpstreamNetworkMonitor.setCurrentUpstream(mTetherUpstream); - reportUpstreamChanged(mTetherUpstream); + reportUpstreamChanged(ns); } } @@ -1598,7 +1599,8 @@ public class Tethering { } } - private void handleUpstreamNetworkMonitorCallback(int arg1, Object o) { + @VisibleForTesting + void handleUpstreamNetworkMonitorCallback(int arg1, Object o) { if (arg1 == UpstreamNetworkMonitor.NOTIFY_LOCAL_PREFIXES) { mOffload.sendOffloadExemptPrefixes((Set<IpPrefix>) o); return; @@ -1624,6 +1626,9 @@ public class Tethering { switch (arg1) { case UpstreamNetworkMonitor.EVENT_ON_CAPABILITIES: + if (ns.network.equals(mTetherUpstream)) { + mNotificationUpdater.onUpstreamCapabilitiesChanged(ns.networkCapabilities); + } handleNewUpstreamNetworkState(ns); break; case UpstreamNetworkMonitor.EVENT_ON_LINKPROPERTIES: @@ -2009,8 +2014,10 @@ public class Tethering { }); } - private void reportUpstreamChanged(Network network) { + private void reportUpstreamChanged(UpstreamNetworkState ns) { final int length = mTetheringEventCallbacks.beginBroadcast(); + final Network network = (ns != null) ? ns.network : null; + final NetworkCapabilities capabilities = (ns != null) ? ns.networkCapabilities : null; try { for (int i = 0; i < length; i++) { try { @@ -2022,7 +2029,9 @@ public class Tethering { } finally { mTetheringEventCallbacks.finishBroadcast(); } - mNotificationUpdater.onUpstreamNetworkChanged(network); + // Need to notify capabilities change after upstream network changed because new network's + // capabilities should be checked every time. + mNotificationUpdater.onUpstreamCapabilitiesChanged(capabilities); } private void reportConfigurationChanged(TetheringConfigurationParcel config) { diff --git a/packages/Tethering/src/com/android/networkstack/tethering/TetheringNotificationUpdater.java b/packages/Tethering/src/com/android/networkstack/tethering/TetheringNotificationUpdater.java index de2f90e50e2f..f490cc4719fa 100644 --- a/packages/Tethering/src/com/android/networkstack/tethering/TetheringNotificationUpdater.java +++ b/packages/Tethering/src/com/android/networkstack/tethering/TetheringNotificationUpdater.java @@ -16,6 +16,7 @@ package com.android.networkstack.tethering; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING; import static android.net.TetheringManager.TETHERING_BLUETOOTH; import static android.net.TetheringManager.TETHERING_USB; import static android.net.TetheringManager.TETHERING_WIFI; @@ -30,7 +31,7 @@ import android.content.Context; import android.content.Intent; import android.content.res.Configuration; import android.content.res.Resources; -import android.net.Network; +import android.net.NetworkCapabilities; import android.os.Handler; import android.os.Looper; import android.os.Message; @@ -50,6 +51,9 @@ import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * A class to display tethering-related notifications. * @@ -82,6 +86,9 @@ public class TetheringNotificationUpdater { // Id to update and cancel no upstream notification. Must be unique within the tethering app. @VisibleForTesting static final int NO_UPSTREAM_NOTIFICATION_ID = 1002; + // Id to update and cancel roaming notification. Must be unique within the tethering app. + @VisibleForTesting + static final int ROAMING_NOTIFICATION_ID = 1003; @VisibleForTesting static final int NO_ICON_ID = 0; @VisibleForTesting @@ -95,13 +102,14 @@ public class TetheringNotificationUpdater { private final Handler mHandler; // WARNING : the constructor is called on a different thread. Thread safety therefore - // relies on these values being initialized to 0 or false, and not any other value. If you need - // to change this, you will need to change the thread where the constructor is invoked, - // or to introduce synchronization. + // relies on these values being initialized to 0, false or null, and not any other value. If you + // need to change this, you will need to change the thread where the constructor is invoked, or + // to introduce synchronization. // Downstream type is one of ConnectivityManager.TETHERING_* constants, 0 1 or 2. // This value has to be made 1 2 and 4, and OR'd with the others. private int mDownstreamTypesMask = DOWNSTREAM_NONE; private boolean mNoUpstream = false; + private boolean mRoaming = false; // WARNING : this value is not able to being initialized to 0 and must have volatile because // telephony service is not guaranteed that is up before tethering service starts. If telephony @@ -110,7 +118,13 @@ public class TetheringNotificationUpdater { // INVALID_SUBSCRIPTION_ID. private volatile int mActiveDataSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; - @IntDef({ENABLE_NOTIFICATION_ID, RESTRICTED_NOTIFICATION_ID, NO_UPSTREAM_NOTIFICATION_ID}) + @Retention(RetentionPolicy.SOURCE) + @IntDef(value = { + ENABLE_NOTIFICATION_ID, + RESTRICTED_NOTIFICATION_ID, + NO_UPSTREAM_NOTIFICATION_ID, + ROAMING_NOTIFICATION_ID + }) @interface NotificationId {} private static final class MccMncOverrideInfo { @@ -160,26 +174,22 @@ public class TetheringNotificationUpdater { /** Called when downstream has changed */ public void onDownstreamChanged(@IntRange(from = 0, to = 7) final int downstreamTypesMask) { - if (mDownstreamTypesMask == downstreamTypesMask) return; - mDownstreamTypesMask = downstreamTypesMask; - updateEnableNotification(); - updateNoUpstreamNotification(); + updateActiveNotifications( + mActiveDataSubId, downstreamTypesMask, mNoUpstream, mRoaming); } /** Called when active data subscription id changed */ public void onActiveDataSubscriptionIdChanged(final int subId) { - if (mActiveDataSubId == subId) return; - mActiveDataSubId = subId; - updateEnableNotification(); - updateNoUpstreamNotification(); + updateActiveNotifications(subId, mDownstreamTypesMask, mNoUpstream, mRoaming); } - /** Called when upstream network changed */ - public void onUpstreamNetworkChanged(@Nullable final Network network) { - final boolean isNoUpstream = (network == null); - if (mNoUpstream == isNoUpstream) return; - mNoUpstream = isNoUpstream; - updateNoUpstreamNotification(); + /** Called when upstream network capabilities changed */ + public void onUpstreamCapabilitiesChanged(@Nullable final NetworkCapabilities capabilities) { + final boolean isNoUpstream = (capabilities == null); + final boolean isRoaming = capabilities != null + && !capabilities.hasCapability(NET_CAPABILITY_NOT_ROAMING); + updateActiveNotifications( + mActiveDataSubId, mDownstreamTypesMask, isNoUpstream, isRoaming); } @NonNull @@ -208,6 +218,25 @@ public class TetheringNotificationUpdater { return res; } + private void updateActiveNotifications(final int subId, final int downstreamTypes, + final boolean noUpstream, final boolean isRoaming) { + final boolean tetheringActiveChanged = + (downstreamTypes == DOWNSTREAM_NONE) != (mDownstreamTypesMask == DOWNSTREAM_NONE); + final boolean subIdChanged = subId != mActiveDataSubId; + final boolean downstreamChanged = downstreamTypes != mDownstreamTypesMask; + final boolean upstreamChanged = noUpstream != mNoUpstream; + final boolean roamingChanged = isRoaming != mRoaming; + final boolean updateAll = tetheringActiveChanged || subIdChanged; + mActiveDataSubId = subId; + mDownstreamTypesMask = downstreamTypes; + mNoUpstream = noUpstream; + mRoaming = isRoaming; + + if (updateAll || downstreamChanged) updateEnableNotification(); + if (updateAll || upstreamChanged) updateNoUpstreamNotification(); + if (updateAll || roamingChanged) updateRoamingNotification(); + } + private void updateEnableNotification() { final boolean tetheringInactive = mDownstreamTypesMask == DOWNSTREAM_NONE; @@ -219,14 +248,20 @@ public class TetheringNotificationUpdater { private void updateNoUpstreamNotification() { final boolean tetheringInactive = mDownstreamTypesMask == DOWNSTREAM_NONE; - if (tetheringInactive - || !mNoUpstream - || setupNoUpstreamNotification() == NO_NOTIFY) { + if (tetheringInactive || !mNoUpstream || setupNoUpstreamNotification() == NO_NOTIFY) { clearNotification(NO_UPSTREAM_NOTIFICATION_ID); mHandler.removeMessages(EVENT_SHOW_NO_UPSTREAM); } } + private void updateRoamingNotification() { + final boolean tetheringInactive = mDownstreamTypesMask == DOWNSTREAM_NONE; + + if (tetheringInactive || !mRoaming || setupRoamingNotification() == NO_NOTIFY) { + clearNotification(ROAMING_NOTIFICATION_ID); + } + } + @VisibleForTesting void tetheringRestrictionLifted() { clearNotification(RESTRICTED_NOTIFICATION_ID); @@ -333,6 +368,29 @@ public class TetheringNotificationUpdater { return icons; } + private boolean setupRoamingNotification() { + final Resources res = getResourcesForSubId(mContext, mActiveDataSubId); + final boolean upstreamRoamingNotification = + res.getBoolean(R.bool.config_upstream_roaming_notification); + + if (!upstreamRoamingNotification) return NO_NOTIFY; + + final String title = res.getString(R.string.upstream_roaming_notification_title); + final String message = res.getString(R.string.upstream_roaming_notification_message); + if (isEmpty(title) || isEmpty(message)) return NO_NOTIFY; + + final PendingIntent pi = PendingIntent.getActivity( + mContext.createContextAsUser(UserHandle.CURRENT, 0 /* flags */), + 0 /* requestCode */, + new Intent(Settings.ACTION_TETHER_SETTINGS), + Intent.FLAG_ACTIVITY_NEW_TASK, + null /* options */); + + showNotification(R.drawable.stat_sys_tether_general, title, message, + ROAMING_NOTIFICATION_ID, pi, new Action[0]); + return NOTIFY_DONE; + } + private boolean setupNoUpstreamNotification() { final Resources res = getResourcesForSubId(mContext, mActiveDataSubId); final int delayToShowUpstreamNotification = diff --git a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt index 294bf1b7e169..04f31a7a2880 100644 --- a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt +++ b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt @@ -23,10 +23,11 @@ import android.content.res.Resources import android.net.ConnectivityManager.TETHERING_BLUETOOTH import android.net.ConnectivityManager.TETHERING_USB import android.net.ConnectivityManager.TETHERING_WIFI -import android.net.Network import android.os.Handler import android.os.HandlerThread import android.os.Looper +import android.net.NetworkCapabilities +import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING import android.os.UserHandle import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID import android.telephony.TelephonyManager @@ -39,6 +40,7 @@ import com.android.networkstack.tethering.TetheringNotificationUpdater.ENABLE_NO import com.android.networkstack.tethering.TetheringNotificationUpdater.EVENT_SHOW_NO_UPSTREAM import com.android.networkstack.tethering.TetheringNotificationUpdater.NO_UPSTREAM_NOTIFICATION_ID import com.android.networkstack.tethering.TetheringNotificationUpdater.RESTRICTED_NOTIFICATION_ID +import com.android.networkstack.tethering.TetheringNotificationUpdater.ROAMING_NOTIFICATION_ID import com.android.networkstack.tethering.TetheringNotificationUpdater.VERIZON_CARRIER_ID import com.android.testutils.waitForIdle import org.junit.After @@ -75,6 +77,8 @@ const val TEST_MESSAGE = "Tap to set up hotspot." const val TEST_NO_UPSTREAM_TITLE = "Hotspot has no internet access" const val TEST_NO_UPSTREAM_MESSAGE = "Device cannot connect to internet." const val TEST_NO_UPSTREAM_BUTTON = "Turn off hotspot" +const val TEST_ROAMING_TITLE = "Hotspot is on" +const val TEST_ROAMING_MESSAGE = "Additional charges may apply while roaming." @RunWith(AndroidJUnit4::class) @SmallTest @@ -98,6 +102,11 @@ class TetheringNotificationUpdaterTest { "WIFI|BT;android.test:drawable/general", "WIFI|USB;android.test:drawable/general", "USB|BT;android.test:drawable/general", "WIFI|USB|BT;android.test:drawable/general") + private val ROAMING_CAPABILITIES = NetworkCapabilities() + private val HOME_CAPABILITIES = NetworkCapabilities().addCapability(NET_CAPABILITY_NOT_ROAMING) + private val NOTIFICATION_ICON_ID = R.drawable.stat_sys_tether_general + private val TIMEOUT_MS = 500L + private inner class TestContext(c: Context) : BroadcastInterceptingContext(c) { override fun createContextAsUser(user: UserHandle, flags: Int) = if (user == UserHandle.ALL) mockContext else this @@ -123,6 +132,8 @@ class TetheringNotificationUpdaterTest { .getStringArray(R.array.tethering_notification_icons) doReturn(5).`when`(testResources) .getInteger(R.integer.delay_to_show_no_upstream_after_no_backhaul) + doReturn(true).`when`(testResources) + .getBoolean(R.bool.config_upstream_roaming_notification) doReturn(TITLE).`when`(defaultResources).getString(R.string.tethering_notification_title) doReturn(MESSAGE).`when`(defaultResources) .getString(R.string.tethering_notification_message) @@ -135,6 +146,10 @@ class TetheringNotificationUpdaterTest { .getString(R.string.no_upstream_notification_message) doReturn(TEST_NO_UPSTREAM_BUTTON).`when`(testResources) .getString(R.string.no_upstream_notification_disable_button) + doReturn(TEST_ROAMING_TITLE).`when`(testResources) + .getString(R.string.upstream_roaming_notification_title) + doReturn(TEST_ROAMING_MESSAGE).`when`(testResources) + .getString(R.string.upstream_roaming_notification_message) doReturn(USB_ICON_ID).`when`(defaultResources) .getIdentifier(eq("android.test:drawable/usb"), any(), any()) doReturn(BT_ICON_ID).`when`(defaultResources) @@ -176,41 +191,25 @@ class TetheringNotificationUpdaterTest { assertEquals(iconId, notification.smallIcon.resId) assertEquals(title, notification.title()) assertEquals(text, notification.text()) - } - - private fun verifyNotificationCancelled(id: Int) = - verify(notificationManager, times(1)).cancel(any(), eq(id)) - private val tetheringActiveNotifications = - listOf(NO_UPSTREAM_NOTIFICATION_ID, ENABLE_NOTIFICATION_ID) - - private fun verifyCancelAllTetheringActiveNotifications() { - tetheringActiveNotifications.forEach { - verifyNotificationCancelled(it) - } reset(notificationManager) } - private fun verifyOnlyTetheringActiveNotification( - notifyId: Int, - iconId: Int, - title: String, - text: String + private fun verifyNotificationCancelled( + notificationIds: List<Int>, + resetAfterVerified: Boolean = true ) { - tetheringActiveNotifications.forEach { - when (it) { - notifyId -> verifyNotification(iconId, title, text, notifyId) - else -> verifyNotificationCancelled(it) - } + notificationIds.forEach { + verify(notificationManager, times(1)).cancel(any(), eq(it)) } - reset(notificationManager) + if (resetAfterVerified) reset(notificationManager) } @Test fun testNotificationWithDownstreamChanged() { // Wifi downstream. No notification. notificationUpdater.onDownstreamChanged(WIFI_MASK) - verifyCancelAllTetheringActiveNotifications() + verifyNotificationCancelled(listOf(ENABLE_NOTIFICATION_ID)) // Same downstream changed. Nothing happened. notificationUpdater.onDownstreamChanged(WIFI_MASK) @@ -218,23 +217,23 @@ class TetheringNotificationUpdaterTest { // Wifi and usb downstreams. Show enable notification notificationUpdater.onDownstreamChanged(WIFI_MASK or USB_MASK) - verifyOnlyTetheringActiveNotification( - ENABLE_NOTIFICATION_ID, GENERAL_ICON_ID, TITLE, MESSAGE) + verifyNotification(GENERAL_ICON_ID, TITLE, MESSAGE, ENABLE_NOTIFICATION_ID) // Usb downstream. Still show enable notification. notificationUpdater.onDownstreamChanged(USB_MASK) - verifyOnlyTetheringActiveNotification(ENABLE_NOTIFICATION_ID, USB_ICON_ID, TITLE, MESSAGE) + verifyNotification(USB_ICON_ID, TITLE, MESSAGE, ENABLE_NOTIFICATION_ID) // No downstream. No notification. notificationUpdater.onDownstreamChanged(DOWNSTREAM_NONE) - verifyCancelAllTetheringActiveNotifications() + verifyNotificationCancelled(listOf(ENABLE_NOTIFICATION_ID, NO_UPSTREAM_NOTIFICATION_ID, + ROAMING_NOTIFICATION_ID)) } @Test fun testNotificationWithActiveDataSubscriptionIdChanged() { // Usb downstream. Showed enable notification with default resource. notificationUpdater.onDownstreamChanged(USB_MASK) - verifyOnlyTetheringActiveNotification(ENABLE_NOTIFICATION_ID, USB_ICON_ID, TITLE, MESSAGE) + verifyNotification(USB_ICON_ID, TITLE, MESSAGE, ENABLE_NOTIFICATION_ID) // Same subId changed. Nothing happened. notificationUpdater.onActiveDataSubscriptionIdChanged(INVALID_SUBSCRIPTION_ID) @@ -242,16 +241,17 @@ class TetheringNotificationUpdaterTest { // Set test sub id. Clear notification with test resource. notificationUpdater.onActiveDataSubscriptionIdChanged(TEST_SUBID) - verifyCancelAllTetheringActiveNotifications() + verifyNotificationCancelled(listOf(ENABLE_NOTIFICATION_ID, NO_UPSTREAM_NOTIFICATION_ID, + ROAMING_NOTIFICATION_ID)) // Wifi downstream. Show enable notification with test resource. notificationUpdater.onDownstreamChanged(WIFI_MASK) - verifyOnlyTetheringActiveNotification( - ENABLE_NOTIFICATION_ID, WIFI_ICON_ID, TEST_TITLE, TEST_MESSAGE) + verifyNotification(WIFI_ICON_ID, TEST_TITLE, TEST_MESSAGE, ENABLE_NOTIFICATION_ID) // No downstream. No notification. notificationUpdater.onDownstreamChanged(DOWNSTREAM_NONE) - verifyCancelAllTetheringActiveNotifications() + verifyNotificationCancelled(listOf(ENABLE_NOTIFICATION_ID, NO_UPSTREAM_NOTIFICATION_ID, + ROAMING_NOTIFICATION_ID)) } private fun assertIconNumbers(number: Int, configs: Array<String?>) { @@ -305,24 +305,21 @@ class TetheringNotificationUpdaterTest { // User restrictions on. Show restricted notification. notificationUpdater.notifyTetheringDisabledByRestriction() - verifyNotification(R.drawable.stat_sys_tether_general, title, message, - RESTRICTED_NOTIFICATION_ID) - reset(notificationManager) + verifyNotification(NOTIFICATION_ICON_ID, title, message, RESTRICTED_NOTIFICATION_ID) // User restrictions off. Clear notification. notificationUpdater.tetheringRestrictionLifted() - verifyNotificationCancelled(RESTRICTED_NOTIFICATION_ID) - reset(notificationManager) + verifyNotificationCancelled(listOf(RESTRICTED_NOTIFICATION_ID)) // Set test sub id. No notification. notificationUpdater.onActiveDataSubscriptionIdChanged(TEST_SUBID) - verifyCancelAllTetheringActiveNotifications() + verifyNotificationCancelled(listOf(ENABLE_NOTIFICATION_ID, NO_UPSTREAM_NOTIFICATION_ID, + ROAMING_NOTIFICATION_ID)) // User restrictions on again. Show restricted notification with test resource. notificationUpdater.notifyTetheringDisabledByRestriction() - verifyNotification(R.drawable.stat_sys_tether_general, disallowTitle, disallowMessage, + verifyNotification(NOTIFICATION_ICON_ID, disallowTitle, disallowMessage, RESTRICTED_NOTIFICATION_ID) - reset(notificationManager) } val MAX_BACKOFF_MS = 200L @@ -359,51 +356,53 @@ class TetheringNotificationUpdaterTest { } @Test - fun testNotificationWithUpstreamNetworkChanged() { + fun testNotificationWithUpstreamCapabilitiesChanged_NoUpstream() { // Set test sub id. No notification. notificationUpdater.onActiveDataSubscriptionIdChanged(TEST_SUBID) - verifyCancelAllTetheringActiveNotifications() + verifyNotificationCancelled(listOf(ENABLE_NOTIFICATION_ID, NO_UPSTREAM_NOTIFICATION_ID, + ROAMING_NOTIFICATION_ID)) // Wifi downstream. Show enable notification with test resource. notificationUpdater.onDownstreamChanged(WIFI_MASK) - verifyOnlyTetheringActiveNotification( - ENABLE_NOTIFICATION_ID, WIFI_ICON_ID, TEST_TITLE, TEST_MESSAGE) + verifyNotification(WIFI_ICON_ID, TEST_TITLE, TEST_MESSAGE, ENABLE_NOTIFICATION_ID) // There is no upstream. Show no upstream notification. - notificationUpdater.onUpstreamNetworkChanged(null) - notificationUpdater.handler.waitForDelayedMessage(EVENT_SHOW_NO_UPSTREAM, 500L) - verifyNotification(R.drawable.stat_sys_tether_general, TEST_NO_UPSTREAM_TITLE, - TEST_NO_UPSTREAM_MESSAGE, NO_UPSTREAM_NOTIFICATION_ID) - reset(notificationManager) + notificationUpdater.onUpstreamCapabilitiesChanged(null) + notificationUpdater.handler.waitForDelayedMessage(EVENT_SHOW_NO_UPSTREAM, TIMEOUT_MS) + verifyNotification(NOTIFICATION_ICON_ID, TEST_NO_UPSTREAM_TITLE, TEST_NO_UPSTREAM_MESSAGE, + NO_UPSTREAM_NOTIFICATION_ID) - // Same upstream network changed. Nothing happened. - notificationUpdater.onUpstreamNetworkChanged(null) + // Same capabilities changed. Nothing happened. + notificationUpdater.onUpstreamCapabilitiesChanged(null) verifyZeroInteractions(notificationManager) // Upstream come back. Clear no upstream notification. - notificationUpdater.onUpstreamNetworkChanged(Network(1000)) - verifyNotificationCancelled(NO_UPSTREAM_NOTIFICATION_ID) - reset(notificationManager) + notificationUpdater.onUpstreamCapabilitiesChanged(HOME_CAPABILITIES) + verifyNotificationCancelled(listOf(NO_UPSTREAM_NOTIFICATION_ID)) // No upstream again. Show no upstream notification. - notificationUpdater.onUpstreamNetworkChanged(null) - notificationUpdater.handler.waitForDelayedMessage(EVENT_SHOW_NO_UPSTREAM, 500L) - verifyNotification(R.drawable.stat_sys_tether_general, TEST_NO_UPSTREAM_TITLE, - TEST_NO_UPSTREAM_MESSAGE, NO_UPSTREAM_NOTIFICATION_ID) - reset(notificationManager) + notificationUpdater.onUpstreamCapabilitiesChanged(null) + notificationUpdater.handler.waitForDelayedMessage(EVENT_SHOW_NO_UPSTREAM, TIMEOUT_MS) + verifyNotification(NOTIFICATION_ICON_ID, TEST_NO_UPSTREAM_TITLE, TEST_NO_UPSTREAM_MESSAGE, + NO_UPSTREAM_NOTIFICATION_ID) // No downstream. No notification. notificationUpdater.onDownstreamChanged(DOWNSTREAM_NONE) - verifyCancelAllTetheringActiveNotifications() + verifyNotificationCancelled(listOf(ENABLE_NOTIFICATION_ID, NO_UPSTREAM_NOTIFICATION_ID, + ROAMING_NOTIFICATION_ID)) - // Set R.integer.delay_to_show_no_upstream_after_no_backhaul to 0 and have wifi downstream - // again. Show enable notification only. + // Put up enable notification with wifi downstream and home capabilities. + notificationUpdater.onDownstreamChanged(WIFI_MASK) + notificationUpdater.onUpstreamCapabilitiesChanged(HOME_CAPABILITIES) + verifyNotification(WIFI_ICON_ID, TEST_TITLE, TEST_MESSAGE, ENABLE_NOTIFICATION_ID) + + // Set R.integer.delay_to_show_no_upstream_after_no_backhaul to -1 and change to no upstream + // again. Don't put up no upstream notification. doReturn(-1).`when`(testResources) .getInteger(R.integer.delay_to_show_no_upstream_after_no_backhaul) - notificationUpdater.onDownstreamChanged(WIFI_MASK) - notificationUpdater.handler.waitForDelayedMessage(EVENT_SHOW_NO_UPSTREAM, 500L) - verifyOnlyTetheringActiveNotification( - ENABLE_NOTIFICATION_ID, WIFI_ICON_ID, TEST_TITLE, TEST_MESSAGE) + notificationUpdater.onUpstreamCapabilitiesChanged(null) + notificationUpdater.handler.waitForDelayedMessage(EVENT_SHOW_NO_UPSTREAM, TIMEOUT_MS) + verifyNotificationCancelled(listOf(NO_UPSTREAM_NOTIFICATION_ID)) } @Test @@ -428,4 +427,57 @@ class TetheringNotificationUpdaterTest { assertEquals(311, res.configuration.mcc) assertEquals(480, res.configuration.mnc) } + + @Test + fun testNotificationWithUpstreamCapabilitiesChanged_Roaming() { + // Set test sub id. Clear notification. + notificationUpdater.onActiveDataSubscriptionIdChanged(TEST_SUBID) + verifyNotificationCancelled(listOf(ENABLE_NOTIFICATION_ID, NO_UPSTREAM_NOTIFICATION_ID, + ROAMING_NOTIFICATION_ID)) + + // Wifi downstream. Show enable notification with test resource. + notificationUpdater.onDownstreamChanged(WIFI_MASK) + verifyNotification(WIFI_ICON_ID, TEST_TITLE, TEST_MESSAGE, ENABLE_NOTIFICATION_ID) + + // Upstream capabilities changed to roaming state. Show roaming notification. + notificationUpdater.onUpstreamCapabilitiesChanged(ROAMING_CAPABILITIES) + verifyNotification(NOTIFICATION_ICON_ID, TEST_ROAMING_TITLE, TEST_ROAMING_MESSAGE, + ROAMING_NOTIFICATION_ID) + + // Same capabilities change. Nothing happened. + notificationUpdater.onUpstreamCapabilitiesChanged(ROAMING_CAPABILITIES) + verifyZeroInteractions(notificationManager) + + // Upstream capabilities changed to home state. Clear roaming notification. + notificationUpdater.onUpstreamCapabilitiesChanged(HOME_CAPABILITIES) + verifyNotificationCancelled(listOf(ROAMING_NOTIFICATION_ID)) + + // Upstream capabilities changed to roaming state again. Show roaming notification. + notificationUpdater.onUpstreamCapabilitiesChanged(ROAMING_CAPABILITIES) + verifyNotification(NOTIFICATION_ICON_ID, TEST_ROAMING_TITLE, TEST_ROAMING_MESSAGE, + ROAMING_NOTIFICATION_ID) + + // No upstream. Clear roaming notification and show no upstream notification. + notificationUpdater.onUpstreamCapabilitiesChanged(null) + notificationUpdater.handler.waitForDelayedMessage(EVENT_SHOW_NO_UPSTREAM, TIMEOUT_MS) + verifyNotificationCancelled(listOf(ROAMING_NOTIFICATION_ID), false) + verifyNotification(NOTIFICATION_ICON_ID, TEST_NO_UPSTREAM_TITLE, TEST_NO_UPSTREAM_MESSAGE, + NO_UPSTREAM_NOTIFICATION_ID) + + // No downstream. No notification. + notificationUpdater.onDownstreamChanged(DOWNSTREAM_NONE) + verifyNotificationCancelled(listOf(ENABLE_NOTIFICATION_ID, NO_UPSTREAM_NOTIFICATION_ID, + ROAMING_NOTIFICATION_ID)) + + // Wifi downstream again. Show enable notification with test resource. + notificationUpdater.onDownstreamChanged(WIFI_MASK) + verifyNotification(WIFI_ICON_ID, TEST_TITLE, TEST_MESSAGE, ENABLE_NOTIFICATION_ID) + + // Set R.bool.config_upstream_roaming_notification to false and change upstream + // network to roaming state again. No roaming notification. + doReturn(false).`when`(testResources) + .getBoolean(R.bool.config_upstream_roaming_notification) + notificationUpdater.onUpstreamCapabilitiesChanged(ROAMING_CAPABILITIES) + verifyNotificationCancelled(listOf(NO_UPSTREAM_NOTIFICATION_ID, ROAMING_NOTIFICATION_ID)) + } } diff --git a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java index 15e253af12a9..28bfae0ced4a 100644 --- a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java +++ b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java @@ -48,6 +48,7 @@ import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED; import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; import static com.android.networkstack.tethering.TetheringNotificationUpdater.DOWNSTREAM_NONE; +import static com.android.networkstack.tethering.UpstreamNetworkMonitor.EVENT_ON_CAPABILITIES; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; @@ -1700,7 +1701,29 @@ public class TetheringTest { stateMachine.chooseUpstreamType(true); verify(mUpstreamNetworkMonitor, times(1)).setCurrentUpstream(eq(upstreamState.network)); - verify(mNotificationUpdater, times(1)).onUpstreamNetworkChanged(eq(upstreamState.network)); + verify(mNotificationUpdater, times(1)).onUpstreamCapabilitiesChanged(any()); + } + + @Test + public void testUpstreamCapabilitiesChanged() { + final Tethering.TetherMasterSM stateMachine = (Tethering.TetherMasterSM) + mTetheringDependencies.mUpstreamNetworkMonitorMasterSM; + final UpstreamNetworkState upstreamState = buildMobileIPv4UpstreamState(); + when(mUpstreamNetworkMonitor.selectPreferredUpstreamType(any())).thenReturn(upstreamState); + stateMachine.chooseUpstreamType(true); + + stateMachine.handleUpstreamNetworkMonitorCallback(EVENT_ON_CAPABILITIES, upstreamState); + // Should have two onUpstreamCapabilitiesChanged(). + // One is called by reportUpstreamChanged(). One is called by EVENT_ON_CAPABILITIES. + verify(mNotificationUpdater, times(2)).onUpstreamCapabilitiesChanged(any()); + reset(mNotificationUpdater); + + // Verify that onUpstreamCapabilitiesChanged won't be called if not current upstream network + // capabilities changed. + final UpstreamNetworkState upstreamState2 = new UpstreamNetworkState( + upstreamState.linkProperties, upstreamState.networkCapabilities, new Network(101)); + stateMachine.handleUpstreamNetworkMonitorCallback(EVENT_ON_CAPABILITIES, upstreamState2); + verify(mNotificationUpdater, never()).onUpstreamCapabilitiesChanged(any()); } // TODO: Test that a request for hotspot mode doesn't interfere with an diff --git a/services/Android.bp b/services/Android.bp index 8af642cafffd..ab6fb7ff95d2 100644 --- a/services/Android.bp +++ b/services/Android.bp @@ -133,6 +133,11 @@ droidstubs { baseline_file: "api/lint-baseline.txt", }, }, + dist: { + targets: ["sdk", "win_sdk"], + dir: "apistubs/android/system-server/api", + dest: "android.txt", + }, } java_library { diff --git a/services/core/java/com/android/server/UserspaceRebootLogger.java b/services/core/java/com/android/server/UserspaceRebootLogger.java index 9a9374ce1822..2403b637581f 100644 --- a/services/core/java/com/android/server/UserspaceRebootLogger.java +++ b/services/core/java/com/android/server/UserspaceRebootLogger.java @@ -24,6 +24,7 @@ import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPOR import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__USER_ENCRYPTION_STATE__LOCKED; import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__USER_ENCRYPTION_STATE__UNLOCKED; +import android.os.PowerManager; import android.os.SystemClock; import android.os.SystemProperties; import android.text.TextUtils; @@ -53,8 +54,15 @@ public final class UserspaceRebootLogger { /** * Modifies internal state to note that {@code UserspaceRebootReported} atom needs to be * logged on the next successful boot. + * + * <p>This call should only be made on devices supporting userspace reboot. */ public static void noteUserspaceRebootWasRequested() { + if (!PowerManager.isRebootingUserspaceSupportedImpl()) { + Slog.wtf(TAG, "Userspace reboot is not supported."); + return; + } + SystemProperties.set(USERSPACE_REBOOT_SHOULD_LOG_PROPERTY, "1"); SystemProperties.set(USERSPACE_REBOOT_LAST_STARTED_PROPERTY, String.valueOf(SystemClock.elapsedRealtime())); @@ -64,16 +72,30 @@ public final class UserspaceRebootLogger { * Updates internal state on boot after successful userspace reboot. * * <p>Should be called right before framework sets {@code sys.boot_completed} property. + * + * <p>This call should only be made on devices supporting userspace reboot. */ public static void noteUserspaceRebootSuccess() { + if (!PowerManager.isRebootingUserspaceSupportedImpl()) { + Slog.wtf(TAG, "Userspace reboot is not supported."); + return; + } + SystemProperties.set(USERSPACE_REBOOT_LAST_FINISHED_PROPERTY, String.valueOf(SystemClock.elapsedRealtime())); } /** * Returns {@code true} if {@code UserspaceRebootReported} atom should be logged. + * + * <p>This call should only be made on devices supporting userspace reboot. */ public static boolean shouldLogUserspaceRebootEvent() { + if (!PowerManager.isRebootingUserspaceSupportedImpl()) { + Slog.wtf(TAG, "Userspace reboot is not supported."); + return false; + } + return SystemProperties.getBoolean(USERSPACE_REBOOT_SHOULD_LOG_PROPERTY, false); } @@ -83,8 +105,15 @@ public final class UserspaceRebootLogger { * <p>Should be called in the end of {@link * com.android.server.am.ActivityManagerService#finishBooting()} method, after framework have * tried to proactivelly unlock storage of the primary user. + * + * <p>This call should only be made on devices supporting userspace reboot. */ public static void logEventAsync(boolean userUnlocked, Executor executor) { + if (!PowerManager.isRebootingUserspaceSupportedImpl()) { + Slog.wtf(TAG, "Userspace reboot is not supported."); + return; + } + final int outcome = computeOutcome(); final long durationMillis; if (outcome == USERSPACE_REBOOT_REPORTED__OUTCOME__SUCCESS) { diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java index a1226115b6e4..d8111ab16e26 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java @@ -213,9 +213,11 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { mLocalDeviceAddresses = initLocalDeviceAddresses(); resetSelectRequestBuffer(); launchDeviceDiscovery(); + if (!mDelayedMessageBuffer.isBuffered(Constants.MESSAGE_ACTIVE_SOURCE)) { + mService.sendCecCommand(HdmiCecMessageBuilder.buildRequestActiveSource(mAddress)); + } } - @ServiceThreadOnly private List<Integer> initLocalDeviceAddresses() { assertRunOnServiceThread(); diff --git a/telephony/java/com/android/internal/telephony/DctConstants.java b/telephony/java/com/android/internal/telephony/DctConstants.java index 3e1d72c7eeb8..2b1d9e58c4d5 100644 --- a/telephony/java/com/android/internal/telephony/DctConstants.java +++ b/telephony/java/com/android/internal/telephony/DctConstants.java @@ -74,7 +74,6 @@ public class DctConstants { public static final int BASE = Protocol.BASE_DATA_CONNECTION_TRACKER; public static final int EVENT_DATA_SETUP_COMPLETE = BASE + 0; public static final int EVENT_RADIO_AVAILABLE = BASE + 1; - public static final int EVENT_RECORDS_LOADED = BASE + 2; public static final int EVENT_TRY_SETUP_DATA = BASE + 3; public static final int EVENT_RADIO_OFF_OR_NOT_AVAILABLE = BASE + 6; public static final int EVENT_VOICE_CALL_STARTED = BASE + 7; @@ -94,7 +93,6 @@ public class DctConstants { public static final int EVENT_CLEAN_UP_CONNECTION = BASE + 24; public static final int EVENT_RESTART_RADIO = BASE + 26; public static final int EVENT_CLEAN_UP_ALL_CONNECTIONS = BASE + 29; - public static final int EVENT_ICC_CHANGED = BASE + 33; public static final int EVENT_DATA_SETUP_COMPLETE_ERROR = BASE + 35; public static final int CMD_SET_ENABLE_FAIL_FAST_MOBILE_DATA = BASE + 36; public static final int CMD_ENABLE_MOBILE_PROVISIONING = BASE + 37; @@ -114,7 +112,7 @@ public class DctConstants { public static final int EVENT_SERVICE_STATE_CHANGED = BASE + 52; public static final int EVENT_5G_TIMER_HYSTERESIS = BASE + 53; public static final int EVENT_5G_TIMER_WATCHDOG = BASE + 54; - public static final int EVENT_UPDATE_CARRIER_CONFIGS = BASE + 55; + public static final int EVENT_CARRIER_CONFIG_CHANGED = BASE + 55; /***** Constants *****/ diff --git a/tests/AppLaunch/Android.bp b/tests/AppLaunch/Android.bp index f90f26f00e6d..75db55122553 100644 --- a/tests/AppLaunch/Android.bp +++ b/tests/AppLaunch/Android.bp @@ -8,6 +8,8 @@ android_test { "android.test.base", "android.test.runner", ], - static_libs: ["androidx.test.rules"], + static_libs: [ + "androidx.test.rules", + "ub-uiautomator"], test_suites: ["device-tests"], } diff --git a/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java b/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java index c64e7d1834c9..0d05044f2c25 100644 --- a/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java +++ b/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java @@ -15,6 +15,8 @@ */ package com.android.tests.applaunch; +import static org.junit.Assert.assertNotNull; + import android.accounts.Account; import android.accounts.AccountManager; import android.app.ActivityManager; @@ -29,7 +31,9 @@ import android.content.pm.ResolveInfo; import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.os.RemoteException; +import android.os.SystemClock; import android.os.UserHandle; +import android.support.test.uiautomator.UiDevice; import android.test.InstrumentationTestCase; import android.test.InstrumentationTestRunner; import android.util.Log; @@ -46,6 +50,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStreamWriter; +import java.nio.file.Paths; import java.time.format.DateTimeFormatter; import java.time.ZonedDateTime; import java.time.ZoneOffset; @@ -67,6 +72,7 @@ import java.util.Set; * in the following format: * -e apps <app name>^<result key>|<app name>^<result key> */ +@Deprecated public class AppLaunch extends InstrumentationTestCase { private static final int JOIN_TIMEOUT = 10000; @@ -94,6 +100,9 @@ public class AppLaunch extends InstrumentationTestCase { private static final String KEY_TRACE_DUMPINTERVAL = "tracedump_interval"; private static final String KEY_COMPILER_FILTERS = "compiler_filters"; private static final String KEY_FORCE_STOP_APP = "force_stop_app"; + private static final String ENABLE_SCREEN_RECORDING = "enable_screen_recording"; + private static final int MAX_RECORDING_PARTS = 5; + private static final long VIDEO_TAIL_BUFFER = 500; private static final String SIMPLEPERF_APP_CMD = "simpleperf --log fatal stat --csv -e cpu-cycles,major-faults --app %s & %s"; @@ -144,14 +153,17 @@ public class AppLaunch extends InstrumentationTestCase { private Map<String, Intent> mNameToIntent; private List<LaunchOrder> mLaunchOrderList = new ArrayList<LaunchOrder>(); + private RecordingThread mCurrentThread; private Map<String, String> mNameToResultKey; private Map<String, Map<String, List<AppLaunchResult>>> mNameToLaunchTime; private IActivityManager mAm; + private File launchSubDir = null; private String mSimplePerfCmd = null; private String mLaunchOrder = null; private boolean mDropCache = false; private int mLaunchIterations = 10; private boolean mForceStopApp = true; + private boolean mEnableRecording = false; private int mTraceLaunchCount = 0; private String mTraceDirectoryStr = null; private Bundle mResult = new Bundle(); @@ -166,6 +178,7 @@ public class AppLaunch extends InstrumentationTestCase { private boolean mCycleCleanUp = false; private boolean mTraceAll = false; private boolean mIterationCycle = false; + private UiDevice mDevice; enum IorapStatus { UNDEFINED, @@ -222,7 +235,7 @@ public class AppLaunch extends InstrumentationTestCase { } try { - File launchSubDir = new File(launchRootDir, LAUNCH_SUB_DIRECTORY); + launchSubDir = new File(launchRootDir, LAUNCH_SUB_DIRECTORY); if (!launchSubDir.exists() && !launchSubDir.mkdirs()) { throw new IOException("Unable to create the lauch file sub directory " @@ -921,9 +934,16 @@ public class AppLaunch extends InstrumentationTestCase { mLaunchIterations = Integer.parseInt(launchIterations); } String forceStopApp = args.getString(KEY_FORCE_STOP_APP); + if (forceStopApp != null) { mForceStopApp = Boolean.parseBoolean(forceStopApp); } + + String enableRecording = args.getString(ENABLE_SCREEN_RECORDING); + + if (enableRecording != null) { + mEnableRecording = Boolean.parseBoolean(enableRecording); + } String appList = args.getString(KEY_APPS); if (appList == null) return; @@ -1036,6 +1056,9 @@ public class AppLaunch extends InstrumentationTestCase { private AppLaunchResult startApp(String appName, String launchReason) throws NameNotFoundException, RemoteException { Log.i(TAG, "Starting " + appName); + if(mEnableRecording) { + startRecording(appName, launchReason); + } Intent startIntent = mNameToIntent.get(appName); if (startIntent == null) { @@ -1051,6 +1074,10 @@ public class AppLaunch extends InstrumentationTestCase { } catch (InterruptedException e) { // ignore } + + if(mEnableRecording) { + stopRecording(); + } return runnable.getResult(); } @@ -1358,4 +1385,126 @@ public class AppLaunch extends InstrumentationTestCase { } } + + /** + * Start the screen recording while launching the app. + * + * @param appName + * @param launchReason + */ + private void startRecording(String appName, String launchReason) { + Log.v(TAG, "Started Recording"); + mCurrentThread = new RecordingThread("test-screen-record", + String.format("%s_%s", appName, launchReason)); + mCurrentThread.start(); + } + + /** + * Stop already started screen recording. + */ + private void stopRecording() { + // Skip if not directory. + if (launchSubDir == null) { + return; + } + + // Add some extra time to the video end. + SystemClock.sleep(VIDEO_TAIL_BUFFER); + // Ctrl + C all screen record processes. + mCurrentThread.cancel(); + // Wait for the thread to completely die. + try { + mCurrentThread.join(); + } catch (InterruptedException ex) { + Log.e(TAG, "Interrupted when joining the recording thread.", ex); + } + Log.v(TAG, "Stopped Recording"); + } + + /** Returns the recording's name for part {@code part} of launch description. */ + private File getOutputFile(String description, int part) { + // Omit the iteration number for the first iteration. + final String fileName = + String.format( + "%s-video%s.mp4", description, part == 1 ? "" : part); + return Paths.get(launchSubDir.getAbsolutePath(), description).toFile(); + } + + + /** + * Encapsulates the start and stop screen recording logic. + * Copied from ScreenRecordCollector. + */ + private class RecordingThread extends Thread { + private final String mDescription; + private final List<File> mRecordings; + + private boolean mContinue; + + public RecordingThread(String name, String description) { + super(name); + + mContinue = true; + mRecordings = new ArrayList<>(); + + assertNotNull("No test description provided for recording.", description); + mDescription = description; + } + + @Override + public void run() { + try { + // Start at i = 1 to encode parts as X.mp4, X2.mp4, X3.mp4, etc. + for (int i = 1; i <= MAX_RECORDING_PARTS && mContinue; i++) { + File output = getOutputFile(mDescription, i); + Log.d( + TAG, + String.format("Recording screen to %s", output.getAbsolutePath())); + mRecordings.add(output); + // Make sure not to block on this background command in the main thread so + // that the test continues to run, but block in this thread so it does not + // trigger a new screen recording session before the prior one completes. + getDevice().executeShellCommand( + String.format("screenrecord %s", output.getAbsolutePath())); + } + } catch (IOException e) { + throw new RuntimeException("Caught exception while screen recording."); + } + } + + public void cancel() { + mContinue = false; + + // Identify the screenrecord PIDs and send SIGINT 2 (Ctrl + C) to each. + try { + String[] pids = getDevice().executeShellCommand( + "pidof screenrecord").split(" "); + for (String pid : pids) { + // Avoid empty process ids, because of weird splitting behavior. + if (pid.isEmpty()) { + continue; + } + + getDevice().executeShellCommand( + String.format("kill -2 %s", pid)); + Log.d( + TAG, + String.format("Sent SIGINT 2 to screenrecord process (%s)", pid)); + } + } catch (IOException e) { + throw new RuntimeException("Failed to kill screen recording process."); + } + } + + public List<File> getRecordings() { + return mRecordings; + } + } + + public UiDevice getDevice() { + if (mDevice == null) { + mDevice = UiDevice.getInstance(getInstrumentation()); + } + return mDevice; + } } diff --git a/tests/net/common/java/android/net/DhcpInfoTest.java b/tests/net/common/java/android/net/DhcpInfoTest.java new file mode 100644 index 000000000000..bd5533f33910 --- /dev/null +++ b/tests/net/common/java/android/net/DhcpInfoTest.java @@ -0,0 +1,113 @@ +/* + * 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 android.net; + +import static android.net.shared.Inet4AddressUtils.inet4AddressToIntHTL; + +import static com.android.testutils.MiscAssertsKt.assertFieldCountEquals; +import static com.android.testutils.ParcelUtilsKt.parcelingRoundTrip; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.annotation.Nullable; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.net.Inet4Address; +import java.net.InetAddress; + +@RunWith(AndroidJUnit4.class) +public class DhcpInfoTest { + private static final String STR_ADDR1 = "255.255.255.255"; + private static final String STR_ADDR2 = "127.0.0.1"; + private static final String STR_ADDR3 = "192.168.1.1"; + private static final String STR_ADDR4 = "192.168.1.0"; + private static final int LEASE_TIME = 9999; + + private int ipToInteger(String ipString) throws Exception { + return inet4AddressToIntHTL((Inet4Address) InetAddress.getByName(ipString)); + } + + private DhcpInfo createDhcpInfoObject() throws Exception { + final DhcpInfo dhcpInfo = new DhcpInfo(); + dhcpInfo.ipAddress = ipToInteger(STR_ADDR1); + dhcpInfo.gateway = ipToInteger(STR_ADDR2); + dhcpInfo.netmask = ipToInteger(STR_ADDR3); + dhcpInfo.dns1 = ipToInteger(STR_ADDR4); + dhcpInfo.dns2 = ipToInteger(STR_ADDR4); + dhcpInfo.serverAddress = ipToInteger(STR_ADDR2); + dhcpInfo.leaseDuration = LEASE_TIME; + return dhcpInfo; + } + + @Test + public void testConstructor() { + new DhcpInfo(); + } + + @Test + public void testToString() throws Exception { + final String expectedDefault = "ipaddr 0.0.0.0 gateway 0.0.0.0 netmask 0.0.0.0 " + + "dns1 0.0.0.0 dns2 0.0.0.0 DHCP server 0.0.0.0 lease 0 seconds"; + + DhcpInfo dhcpInfo = new DhcpInfo(); + + // Test default string. + assertEquals(expectedDefault, dhcpInfo.toString()); + + dhcpInfo = createDhcpInfoObject(); + + final String expected = "ipaddr " + STR_ADDR1 + " gateway " + STR_ADDR2 + " netmask " + + STR_ADDR3 + " dns1 " + STR_ADDR4 + " dns2 " + STR_ADDR4 + " DHCP server " + + STR_ADDR2 + " lease " + LEASE_TIME + " seconds"; + // Test with new values + assertEquals(expected, dhcpInfo.toString()); + } + + private boolean dhcpInfoEquals(@Nullable DhcpInfo left, @Nullable DhcpInfo right) { + if (left == null && right == null) return true; + + if (left == null || right == null) return false; + + return left.ipAddress == right.ipAddress + && left.gateway == right.gateway + && left.netmask == right.netmask + && left.dns1 == right.dns1 + && left.dns2 == right.dns2 + && left.serverAddress == right.serverAddress + && left.leaseDuration == right.leaseDuration; + } + + @Test + public void testParcelDhcpInfo() throws Exception { + // Cannot use assertParcelSane() here because this requires .equals() to work as + // defined, but DhcpInfo has a different legacy behavior that we cannot change. + final DhcpInfo dhcpInfo = createDhcpInfoObject(); + assertFieldCountEquals(7, DhcpInfo.class); + + final DhcpInfo dhcpInfoRoundTrip = parcelingRoundTrip(dhcpInfo); + assertTrue(dhcpInfoEquals(null, null)); + assertFalse(dhcpInfoEquals(null, dhcpInfoRoundTrip)); + assertFalse(dhcpInfoEquals(dhcpInfo, null)); + assertTrue(dhcpInfoEquals(dhcpInfo, dhcpInfoRoundTrip)); + } +} |