diff --git a/AndroidManifest.xml b/AndroidManifest.xml index d062663fe31..a4ac2d7eedf 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -3959,6 +3959,9 @@ + + 104 105 106 + 107 1 diff --git a/src/com/android/settings/network/SatelliteRepository.kt b/src/com/android/settings/network/SatelliteRepository.kt index 09b7781d107..f5bac1ee7ab 100644 --- a/src/com/android/settings/network/SatelliteRepository.kt +++ b/src/com/android/settings/network/SatelliteRepository.kt @@ -82,6 +82,8 @@ class SatelliteRepository( * `false` otherwise. */ fun requestIsSessionStarted(executor: Executor): ListenableFuture { + isSessionStarted?.let { return immediateFuture(it) } + val satelliteManager: SatelliteManager? = context.getSystemService(SatelliteManager::class.java) if (satelliteManager == null) { @@ -166,10 +168,6 @@ class SatelliteRepository( } } - companion object { - private const val TAG: String = "SatelliteRepository" - } - /** * Check if the modem is in a satellite session. * @@ -184,5 +182,16 @@ class SatelliteRepository( else -> true } } + + companion object { + private const val TAG: String = "SatelliteRepository" + + private var isSessionStarted: Boolean? = null + + @VisibleForTesting + fun setIsSessionStartedForTesting(isEnabled: Boolean) { + this.isSessionStarted = isEnabled + } + } } diff --git a/src/com/android/settings/sim/PrimarySubscriptionListChangedService.kt b/src/com/android/settings/sim/PrimarySubscriptionListChangedService.kt new file mode 100644 index 00000000000..51a910f4ad2 --- /dev/null +++ b/src/com/android/settings/sim/PrimarySubscriptionListChangedService.kt @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2024 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.settings.sim + +import android.app.job.JobInfo +import android.app.job.JobParameters +import android.app.job.JobScheduler +import android.app.job.JobService +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.util.Log +import com.android.settings.R +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.launch + +/** A JobService work on primary subscription list changed. */ +class PrimarySubscriptionListChangedService : JobService() { + private var job: Job? = null + + override fun onStartJob(params: JobParameters): Boolean { + job = CoroutineScope(Dispatchers.Default + SupervisorJob()).launch { + try { + val intent = Intent() + intent.putExtras(params.transientExtras) + SimSelectNotification.onPrimarySubscriptionListChanged( + this@PrimarySubscriptionListChangedService, + intent + ) + } catch (exception: Throwable) { + Log.e(TAG, "Exception running job", exception) + } + jobFinished(params, false) + } + return true + } + + override fun onStopJob(params: JobParameters): Boolean { + job?.cancel() + return false + } + + companion object { + private const val TAG = "PrimarySubscriptionListChangedService" + + /** + * Schedules a service to work on primary subscription changed. + * + * @param context is the caller context. + */ + @JvmStatic + fun scheduleJob(context: Context, intent: Intent) { + val component = + ComponentName(context, PrimarySubscriptionListChangedService::class.java) + val jobScheduler = context.getSystemService(JobScheduler::class.java)!! + + val jobInfoBuilder = + JobInfo.Builder(R.integer.primary_subscription_list_changed, component) + intent.extras?.let { + jobInfoBuilder.setTransientExtras(it) + } + jobScheduler.schedule(jobInfoBuilder.build()) + } + } +} \ No newline at end of file diff --git a/src/com/android/settings/sim/SimSelectNotification.java b/src/com/android/settings/sim/SimSelectNotification.java index 9b235cea931..c0cb212385c 100644 --- a/src/com/android/settings/sim/SimSelectNotification.java +++ b/src/com/android/settings/sim/SimSelectNotification.java @@ -49,13 +49,28 @@ import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.util.Log; +import androidx.annotation.NonNull; +import androidx.annotation.WorkerThread; + import com.android.internal.annotations.VisibleForTesting; import com.android.settings.HelpTrampoline; import com.android.settings.R; +import com.android.settings.network.SatelliteRepository; import com.android.settings.network.SubscriptionUtil; +import com.google.common.util.concurrent.ListenableFuture; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + public class SimSelectNotification extends BroadcastReceiver { private static final String TAG = "SimSelectNotification"; + + private static final int DEFAULT_TIMEOUT_MS = 1000; + @VisibleForTesting public static final int SIM_SELECT_NOTIFICATION_ID = 1; @VisibleForTesting @@ -90,7 +105,7 @@ public class SimSelectNotification extends BroadcastReceiver { switch (action) { case TelephonyManager.ACTION_PRIMARY_SUBSCRIPTION_LIST_CHANGED: - onPrimarySubscriptionListChanged(context, intent); + PrimarySubscriptionListChangedService.scheduleJob(context, intent); break; case Settings.ACTION_ENABLE_MMS_DATA_REQUEST: onEnableMmsDataRequest(context, intent); @@ -150,12 +165,41 @@ public class SimSelectNotification extends BroadcastReceiver { createEnableMmsNotification(context, notificationTitle, notificationSummary, subId); } - private void onPrimarySubscriptionListChanged(Context context, Intent intent) { - startSimSelectDialogIfNeeded(context, intent); - sendSimCombinationWarningIfNeeded(context, intent); + /** + * Handles changes to the primary subscription list, performing actions only + * if the device is not currently in a satellite session. This method is + * intended to be executed on a worker thread. + * + * @param context The application context + * @param intent The intent signaling a primary subscription change + */ + @WorkerThread + public static void onPrimarySubscriptionListChanged(@NonNull Context context, + @NonNull Intent intent) { + Log.d(TAG, "Checking satellite enabled status"); + Executor executor = Executors.newSingleThreadExecutor(); + ListenableFuture isSatelliteSessionStartedFuture = + new SatelliteRepository(context).requestIsSessionStarted(executor); + boolean isSatelliteSessionStarted = false; + try { + isSatelliteSessionStarted = + isSatelliteSessionStartedFuture.get(DEFAULT_TIMEOUT_MS, TimeUnit.MILLISECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + Log.w(TAG, "Can't get satellite session status", e); + } finally { + if (isSatelliteSessionStarted) { + Log.i(TAG, "Device is in a satellite session.g Unable to handle primary" + + " subscription list changes"); + } else { + Log.i(TAG, "Device is not in a satellite session. Handle primary" + + " subscription list changes"); + startSimSelectDialogIfNeeded(context, intent); + sendSimCombinationWarningIfNeeded(context, intent); + } + } } - private void startSimSelectDialogIfNeeded(Context context, Intent intent) { + private static void startSimSelectDialogIfNeeded(Context context, Intent intent) { int dialogType = intent.getIntExtra(EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE, EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_NONE); @@ -195,7 +239,7 @@ public class SimSelectNotification extends BroadcastReceiver { } } - private void sendSimCombinationWarningIfNeeded(Context context, Intent intent) { + private static void sendSimCombinationWarningIfNeeded(Context context, Intent intent) { final int warningType = intent.getIntExtra(EXTRA_SIM_COMBINATION_WARNING_TYPE, EXTRA_SIM_COMBINATION_WARNING_TYPE_NONE); @@ -208,7 +252,7 @@ public class SimSelectNotification extends BroadcastReceiver { } } - private void createSimSelectNotification(Context context){ + private static void createSimSelectNotification(Context context) { final Resources resources = context.getResources(); NotificationChannel notificationChannel = new NotificationChannel( @@ -218,11 +262,11 @@ public class SimSelectNotification extends BroadcastReceiver { Notification.Builder builder = new Notification.Builder(context, SIM_SELECT_NOTIFICATION_CHANNEL) - .setSmallIcon(R.drawable.ic_sim_alert) - .setColor(context.getColor(R.color.sim_noitification)) - .setContentTitle(resources.getText(R.string.sim_notification_title)) - .setContentText(resources.getText(R.string.sim_notification_summary)) - .setAutoCancel(true); + .setSmallIcon(R.drawable.ic_sim_alert) + .setColor(context.getColor(R.color.sim_noitification)) + .setContentTitle(resources.getText(R.string.sim_notification_title)) + .setContentText(resources.getText(R.string.sim_notification_summary)) + .setAutoCancel(true); Intent resultIntent = new Intent(Settings.ACTION_WIRELESS_SETTINGS); resultIntent.setPackage(SETTINGS_PACKAGE_NAME); resultIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); @@ -280,7 +324,7 @@ public class SimSelectNotification extends BroadcastReceiver { notificationManager.cancel(ENABLE_MMS_NOTIFICATION_ID); } - private void createSimCombinationWarningNotification(Context context, Intent intent){ + private static void createSimCombinationWarningNotification(Context context, Intent intent) { final Resources resources = context.getResources(); final String simNames = intent.getStringExtra(EXTRA_SIM_COMBINATION_NAMES); diff --git a/tests/robotests/src/com/android/settings/sim/PrimarySubscriptionListChangedServiceTest.java b/tests/robotests/src/com/android/settings/sim/PrimarySubscriptionListChangedServiceTest.java new file mode 100644 index 00000000000..3e868e49017 --- /dev/null +++ b/tests/robotests/src/com/android/settings/sim/PrimarySubscriptionListChangedServiceTest.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2024 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.settings.sim; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.job.JobInfo; +import android.app.job.JobScheduler; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; + +import androidx.test.core.app.ApplicationProvider; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; + +import java.util.List; +import java.util.Objects; + +@RunWith(RobolectricTestRunner.class) +public class PrimarySubscriptionListChangedServiceTest { + + @Test + public void schedulePrimarySubscriptionChanged_addSchedule_intentPassToJobInfo() { + Robolectric.setupService(PrimarySubscriptionListChangedService.class); + Context context = ApplicationProvider.getApplicationContext(); + Intent intent = new Intent(); + intent.putExtra("int", 1); + intent.putExtra("string", "foo"); + + PrimarySubscriptionListChangedService.scheduleJob(context, intent); + + List jobs = Objects.requireNonNull(context.getSystemService(JobScheduler.class)) + .getAllPendingJobs(); + assertThat(jobs).hasSize(1); + JobInfo job = jobs.get(0); + assertThat(job.isPersisted()).isFalse(); + Bundle bundle = job.getTransientExtras(); + assertThat(bundle.getInt("int")).isEqualTo(1); + assertThat(bundle.getString("string")).isEqualTo("foo"); + } + + @Test + public void schedulePrimarySubscriptionChanged_addSchedule_whenInvoked() { + Context context = spy(ApplicationProvider.getApplicationContext()); + JobScheduler jobScheduler = mock(JobScheduler.class); + when(context.getSystemService(JobScheduler.class)).thenReturn(jobScheduler); + + PrimarySubscriptionListChangedService.scheduleJob(context, new Intent()); + + verify(jobScheduler).schedule(any()); + } +} diff --git a/tests/robotests/src/com/android/settings/sim/SimSelectNotificationTest.java b/tests/robotests/src/com/android/settings/sim/SimSelectNotificationTest.java index cbdcf3c356e..36f6cd4058e 100644 --- a/tests/robotests/src/com/android/settings/sim/SimSelectNotificationTest.java +++ b/tests/robotests/src/com/android/settings/sim/SimSelectNotificationTest.java @@ -63,6 +63,7 @@ import android.util.DisplayMetrics; import androidx.test.core.app.ApplicationProvider; import com.android.settings.R; +import com.android.settings.network.SatelliteRepository; import com.android.settings.network.SubscriptionUtil; import com.android.settings.testutils.shadow.ShadowAlertDialogCompat; @@ -141,6 +142,8 @@ public class SimSelectNotificationTest { SubscriptionUtil.setActiveSubscriptionsForTesting(Arrays.asList(mSubInfo)); when(mSubscriptionManager.isActiveSubscriptionId(mSubId)).thenReturn(true); when(mSubscriptionManager.getActiveSubscriptionInfo(mSubId)).thenReturn(mSubInfo); + SatelliteRepository.Companion.setIsSessionStartedForTesting(false); + when(mSubInfo.getSubscriptionId()).thenReturn(mSubId); when(mSubInfo.getDisplayName()).thenReturn(mFakeDisplayName); when(mContext.getResources()).thenReturn(mResources); @@ -219,17 +222,30 @@ public class SimSelectNotificationTest { Intent intent = new Intent(TelephonyManager.ACTION_PRIMARY_SUBSCRIPTION_LIST_CHANGED); // EXTRA_SUB_ID and EXTRA_ENABLE_MMS_DATA_REQUEST_REASON are required. - mSimSelectNotification.onReceive(mContext, intent); + SimSelectNotification.onPrimarySubscriptionListChanged(mContext, intent); verify(mNotificationManager, never()).createNotificationChannel(any()); } + @Test + public void onReceivePrimarySubListChange_isSatelliteEnabled_activityShouldNotStart() { + SatelliteRepository.Companion.setIsSessionStartedForTesting(true); + + Intent intent = new Intent(TelephonyManager.ACTION_PRIMARY_SUBSCRIPTION_LIST_CHANGED); + intent.putExtra(EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE, + EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_DATA); + + SimSelectNotification.onPrimarySubscriptionListChanged(mContext, intent); + + verify(mContext, never()).startActivity(any()); + } + @Test public void onReceivePrimarySubListChange_WithDataPickExtra_shouldStartActivity() { Intent intent = new Intent(TelephonyManager.ACTION_PRIMARY_SUBSCRIPTION_LIST_CHANGED); intent.putExtra(EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE, EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_DATA); - mSimSelectNotification.onReceive(mContext, intent); + SimSelectNotification.onPrimarySubscriptionListChanged(mContext, intent); ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); verify(mContext).startActivity(intentCaptor.capture()); @@ -252,12 +268,13 @@ public class SimSelectNotificationTest { intent.putExtra(EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE, EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_DISMISS); - mSimSelectNotification.onReceive(mContext, intent); + SimSelectNotification.onPrimarySubscriptionListChanged(mContext, intent); clearInvocations(mContext); // Dismiss. verify(mExecutor).execute(any()); } + @Test public void onReceivePrimarySubListChange_DualCdmaWarning_notificationShouldSend() { Intent intent = new Intent(TelephonyManager.ACTION_PRIMARY_SUBSCRIPTION_LIST_CHANGED); @@ -266,7 +283,7 @@ public class SimSelectNotificationTest { intent.putExtra(EXTRA_SIM_COMBINATION_WARNING_TYPE, EXTRA_SIM_COMBINATION_WARNING_TYPE_DUAL_CDMA); - mSimSelectNotification.onReceive(mContext, intent); + SimSelectNotification.onPrimarySubscriptionListChanged(mContext, intent); // Capture the notification channel created and verify its fields. ArgumentCaptor nc = ArgumentCaptor.forClass(NotificationChannel.class);