diff --git a/res/values/strings.xml b/res/values/strings.xml
index 061164c04be..3f3c0f33d83 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -12490,6 +12490,10 @@
Connect to public networks
network connection, internet, wireless, data, wifi, wi-fi, wi fi, cellular, mobile, cell carrier, 4g, 3g, 2g, lte
+
+ View airplane-safe networks
+
+ Turn off Airplane Mode
Unavailable because bedtime mode is on
diff --git a/src/com/android/settings/network/AirplaneSafeNetworksSlice.java b/src/com/android/settings/network/AirplaneSafeNetworksSlice.java
new file mode 100644
index 00000000000..fbef28273e3
--- /dev/null
+++ b/src/com/android/settings/network/AirplaneSafeNetworksSlice.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2020 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.network;
+
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.net.Uri;
+import android.net.wifi.WifiManager;
+import android.util.Log;
+
+import androidx.annotation.IntDef;
+import androidx.core.graphics.drawable.IconCompat;
+import androidx.slice.Slice;
+import androidx.slice.builders.ListBuilder;
+import androidx.slice.builders.ListBuilder.RowBuilder;
+import androidx.slice.builders.SliceAction;
+
+import com.android.settings.AirplaneModeEnabler;
+import com.android.settings.R;
+import com.android.settings.Utils;
+import com.android.settings.slices.CustomSliceRegistry;
+import com.android.settings.slices.CustomSliceable;
+import com.android.settings.slices.SliceBackgroundWorker;
+import com.android.settings.slices.SliceBroadcastReceiver;
+import com.android.settingslib.WirelessUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * {@link CustomSliceable} for airplane-safe networks, used by generic clients.
+ */
+// TODO(b/173413889): Need to update the slice to Button style.
+public class AirplaneSafeNetworksSlice implements CustomSliceable,
+ AirplaneModeEnabler.OnAirplaneModeChangedListener {
+
+ private static final String TAG = "AirplaneSafeNetworksSlice";
+
+ public static final String ACTION_INTENT_EXTRA = "action";
+
+ /**
+ * Annotation for different action of the slice.
+ *
+ * {@code VIEW_AIRPLANE_SAFE_NETWORKS} for action of turning on Wi-Fi.
+ * {@code TURN_OFF_AIRPLANE_MODE} for action of turning off Airplane Mode.
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value = {
+ Action.VIEW_AIRPLANE_SAFE_NETWORKS,
+ Action.TURN_OFF_AIRPLANE_MODE,
+ })
+ public @interface Action {
+ int VIEW_AIRPLANE_SAFE_NETWORKS = 1;
+ int TURN_OFF_AIRPLANE_MODE = 2;
+ }
+
+ private final Context mContext;
+ private final AirplaneModeEnabler mAirplaneModeEnabler;
+ private final WifiManager mWifiManager;
+
+ public AirplaneSafeNetworksSlice(Context context) {
+ mContext = context;
+ mAirplaneModeEnabler = new AirplaneModeEnabler(context, this);
+ mWifiManager = mContext.getSystemService(WifiManager.class);
+ }
+
+ private static void logd(String s) {
+ Log.d(TAG, s);
+ }
+
+ @Override
+ public Slice getSlice() {
+ if (!WirelessUtils.isAirplaneModeOn(mContext)) {
+ return null;
+ }
+
+ return new ListBuilder(mContext, getUri(), ListBuilder.INFINITY)
+ .addRow(new RowBuilder()
+ .setTitle(getTitle())
+ .setPrimaryAction(getSliceAction()))
+ .build();
+ }
+
+ @Override
+ public Uri getUri() {
+ return CustomSliceRegistry.AIRPLANE_SAFE_NETWORKS_SLICE_URI;
+ }
+
+ @Override
+ public void onNotifyChange(Intent intent) {
+ final int action = intent.getIntExtra(ACTION_INTENT_EXTRA, 0);
+ if (action == Action.VIEW_AIRPLANE_SAFE_NETWORKS) {
+ if (!mWifiManager.isWifiEnabled()) {
+ logd("Action: turn on WiFi");
+ mWifiManager.setWifiEnabled(true);
+ }
+ } else if (action == Action.TURN_OFF_AIRPLANE_MODE) {
+ if (WirelessUtils.isAirplaneModeOn(mContext)) {
+ logd("Action: turn off Airplane mode");
+ mAirplaneModeEnabler.setAirplaneMode(false);
+ }
+ }
+ }
+
+ @Override
+ public void onAirplaneModeChanged(boolean isAirplaneModeOn) {
+ final AirplaneSafeNetworksWorker worker = SliceBackgroundWorker.getInstance(getUri());
+ if (worker != null) {
+ worker.updateSlice();
+ }
+ }
+
+ @Override
+ public Intent getIntent() {
+ return new Intent(getUri().toString())
+ .setData(getUri())
+ .setClass(mContext, SliceBroadcastReceiver.class)
+ .putExtra(ACTION_INTENT_EXTRA, getAction());
+ }
+
+ @Action
+ private int getAction() {
+ return mWifiManager.isWifiEnabled()
+ ? Action.TURN_OFF_AIRPLANE_MODE
+ : Action.VIEW_AIRPLANE_SAFE_NETWORKS;
+ }
+
+ private String getTitle() {
+ return mContext.getText(
+ (getAction() == Action.VIEW_AIRPLANE_SAFE_NETWORKS)
+ ? R.string.view_airplane_safe_networks
+ : R.string.turn_off_airplane_mode).toString();
+ }
+
+ private SliceAction getSliceAction() {
+ final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext,
+ 0 /* requestCode */, getIntent(),
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+ final IconCompat icon = Utils.createIconWithDrawable(new ColorDrawable(Color.TRANSPARENT));
+ return SliceAction.createDeeplink(pendingIntent, icon, ListBuilder.ACTION_WITH_LABEL,
+ getTitle());
+ }
+
+ @Override
+ public Class getBackgroundWorkerClass() {
+ return AirplaneSafeNetworksWorker.class;
+ }
+
+ public static class AirplaneSafeNetworksWorker extends SliceBackgroundWorker {
+
+ private final IntentFilter mIntentFilter;
+ private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(intent.getAction())) {
+ notifySliceChange();
+ }
+ }
+ };
+
+ public AirplaneSafeNetworksWorker(Context context, Uri uri) {
+ super(context, uri);
+ mIntentFilter = new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION);
+ }
+
+ @Override
+ protected void onSlicePinned() {
+ getContext().registerReceiver(mBroadcastReceiver, mIntentFilter);
+ }
+
+ @Override
+ protected void onSliceUnpinned() {
+ getContext().unregisterReceiver(mBroadcastReceiver);
+ }
+
+ @Override
+ public void close() {
+ // Do nothing.
+ }
+
+ public void updateSlice() {
+ notifySliceChange();
+ }
+ }
+}
diff --git a/src/com/android/settings/panel/InternetConnectivityPanel.java b/src/com/android/settings/panel/InternetConnectivityPanel.java
index db0c5e33fcc..6ae70895ee3 100644
--- a/src/com/android/settings/panel/InternetConnectivityPanel.java
+++ b/src/com/android/settings/panel/InternetConnectivityPanel.java
@@ -23,6 +23,7 @@ import android.net.Uri;
import android.provider.Settings;
import com.android.settings.R;
+import com.android.settings.Utils;
import com.android.settings.network.AirplaneModePreferenceController;
import com.android.settings.slices.CustomSliceRegistry;
@@ -58,7 +59,11 @@ public class InternetConnectivityPanel implements PanelContent {
final List uris = new ArrayList<>();
uris.add(CustomSliceRegistry.WIFI_SLICE_URI);
uris.add(CustomSliceRegistry.MOBILE_DATA_SLICE_URI);
- uris.add(AirplaneModePreferenceController.SLICE_URI);
+ if (Utils.isProviderModelEnabled(mContext)) {
+ uris.add(CustomSliceRegistry.AIRPLANE_SAFE_NETWORKS_SLICE_URI);
+ } else {
+ uris.add(AirplaneModePreferenceController.SLICE_URI);
+ }
return uris;
}
diff --git a/src/com/android/settings/slices/CustomSliceRegistry.java b/src/com/android/settings/slices/CustomSliceRegistry.java
index 30079538663..ce98d271830 100644
--- a/src/com/android/settings/slices/CustomSliceRegistry.java
+++ b/src/com/android/settings/slices/CustomSliceRegistry.java
@@ -41,6 +41,7 @@ import com.android.settings.media.MediaOutputGroupSlice;
import com.android.settings.media.MediaOutputIndicatorSlice;
import com.android.settings.media.MediaOutputSlice;
import com.android.settings.media.RemoteMediaSlice;
+import com.android.settings.network.AirplaneSafeNetworksSlice;
import com.android.settings.network.telephony.MobileDataSlice;
import com.android.settings.notification.zen.ZenModeButtonPreferenceController;
import com.android.settings.wifi.calling.WifiCallingSliceHelper;
@@ -314,6 +315,16 @@ public class CustomSliceRegistry {
.appendPath("always_on_display")
.build();
+ /**
+ * Backing Uri for the Always On Slice.
+ */
+ public static final Uri AIRPLANE_SAFE_NETWORKS_SLICE_URI = new Uri.Builder()
+ .scheme(ContentResolver.SCHEME_CONTENT)
+ .authority(SettingsSliceProvider.SLICE_AUTHORITY)
+ .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION)
+ .appendPath("airplane_safe_networks")
+ .build();
+
@VisibleForTesting
static final Map> sUriToSlice;
@@ -336,6 +347,7 @@ public class CustomSliceRegistry {
sUriToSlice.put(REMOTE_MEDIA_SLICE_URI, RemoteMediaSlice.class);
sUriToSlice.put(MEDIA_OUTPUT_GROUP_SLICE_URI, MediaOutputGroupSlice.class);
sUriToSlice.put(ALWAYS_ON_SLICE_URI, AlwaysOnDisplaySlice.class);
+ sUriToSlice.put(AIRPLANE_SAFE_NETWORKS_SLICE_URI, AirplaneSafeNetworksSlice.class);
}
public static Class extends CustomSliceable> getSliceClassByUri(Uri uri) {
diff --git a/tests/unit/src/com/android/settings/network/AirplaneSafeNetworksSliceTest.java b/tests/unit/src/com/android/settings/network/AirplaneSafeNetworksSliceTest.java
new file mode 100644
index 00000000000..ce47eb7d6dd
--- /dev/null
+++ b/tests/unit/src/com/android/settings/network/AirplaneSafeNetworksSliceTest.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2020 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.network;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.wifi.WifiManager;
+
+import androidx.slice.Slice;
+import androidx.slice.SliceItem;
+import androidx.slice.SliceMetadata;
+import androidx.slice.SliceProvider;
+import androidx.slice.widget.SliceLiveData;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.settings.AirplaneModeEnabler;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@RunWith(AndroidJUnit4.class)
+public class AirplaneSafeNetworksSliceTest {
+
+ private static final String VIEW_AIRPLANE_SAFE_NETWORKS = "View airplane-safe networks";
+ private static final String TURN_OFF_AIRPLANE_MODE = "Turn off Airplane Mode";
+
+ @Rule
+ public MockitoRule mMocks = MockitoJUnit.rule();
+ @Mock
+ private WifiManager mWifiManager;
+
+ private Context mContext;
+ private AirplaneModeEnabler mAirplaneModeEnabler;
+ private AirplaneSafeNetworksSlice mAirplaneSafeNetworksSlice;
+
+ @Before
+ public void setUp() {
+ mContext = spy(ApplicationProvider.getApplicationContext());
+ when(mContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mWifiManager);
+ mAirplaneModeEnabler =
+ new AirplaneModeEnabler(mContext, null /* OnAirplaneModeChangedListener */);
+
+ // Set-up specs for SliceMetadata.
+ SliceProvider.setSpecs(SliceLiveData.SUPPORTED_SPECS);
+
+ mAirplaneSafeNetworksSlice = new AirplaneSafeNetworksSlice(mContext);
+ }
+
+ @Test
+ public void getSlice_airplaneModeOff_shouldBeNull() {
+ mAirplaneModeEnabler.setAirplaneMode(false);
+
+ assertThat(mAirplaneSafeNetworksSlice.getSlice()).isNull();
+ }
+
+ @Test
+ public void getSlice_wifiDisabled_shouldShowViewAirplaneSafeNetworks() {
+ mAirplaneModeEnabler.setAirplaneMode(true);
+ when(mWifiManager.isWifiEnabled()).thenReturn(false);
+
+ final Slice slice = mAirplaneSafeNetworksSlice.getSlice();
+
+ assertThat(slice).isNotNull();
+ final SliceItem sliceTitle =
+ SliceMetadata.from(mContext, slice).getListContent().getHeader().getTitleItem();
+ assertThat(sliceTitle.getText()).isEqualTo(VIEW_AIRPLANE_SAFE_NETWORKS);
+ }
+
+ @Test
+ public void getSlice_wifiEnabled_shouldShowTurnOffAirplaneMode() {
+ mAirplaneModeEnabler.setAirplaneMode(true);
+ when(mWifiManager.isWifiEnabled()).thenReturn(true);
+
+ final Slice slice = mAirplaneSafeNetworksSlice.getSlice();
+
+ assertThat(slice).isNotNull();
+ final SliceItem sliceTitle =
+ SliceMetadata.from(mContext, slice).getListContent().getHeader().getTitleItem();
+ assertThat(sliceTitle.getText()).isEqualTo(TURN_OFF_AIRPLANE_MODE);
+ }
+
+ @Test
+ public void onNotifyChange_viewAirplaneSafeNetworks_shouldSetWifiEnabled() {
+ mAirplaneModeEnabler.setAirplaneMode(true);
+ when(mWifiManager.isWifiEnabled()).thenReturn(false);
+ Intent intent = mAirplaneSafeNetworksSlice.getIntent();
+
+ mAirplaneSafeNetworksSlice.onNotifyChange(intent);
+
+ verify(mWifiManager).setWifiEnabled(true);
+ }
+
+ @Test
+ public void onNotifyChange_turnOffAirplaneMode_shouldSetAirplaneModeOff() {
+ mAirplaneModeEnabler.setAirplaneMode(true);
+ when(mWifiManager.isWifiEnabled()).thenReturn(true);
+ Intent intent = mAirplaneSafeNetworksSlice.getIntent();
+
+ mAirplaneSafeNetworksSlice.onNotifyChange(intent);
+
+ assertThat(mAirplaneModeEnabler.isAirplaneModeOn()).isFalse();
+ }
+}