Refactor airplane mode preference controller.

- Convert inheritance from AbstractPreferenceController to
TogglePreferenceController.
- Register AirplaneModePreferenceController in XML.
- Decouple AirplaneModeEnabler with preference, leave the UI be
controled by PreferenceController.
- Add RoboTests test cases for AirplaneModePreferenceController.

Fixes: 67997339
Test: RunSettingsRoboTests
Change-Id: I7398943ed51345e014ee7aa774bfae1ca28632f1
diff --git a/res/xml/network_and_internet.xml b/res/xml/network_and_internet.xml
index ee99998..064d625 100644
--- a/res/xml/network_and_internet.xml
+++ b/res/xml/network_and_internet.xml
@@ -73,6 +73,7 @@
         android:icon="@drawable/ic_airplanemode_active"
         android:disableDependentsState="true"
         android:order="5"
+        settings:controller="com.android.settings.network.AirplaneModePreferenceController"
         settings:userRestriction="no_airplane_mode"/>
 
     <Preference
diff --git a/src/com/android/settings/AirplaneModeEnabler.java b/src/com/android/settings/AirplaneModeEnabler.java
index 5f93589..11f1a28 100644
--- a/src/com/android/settings/AirplaneModeEnabler.java
+++ b/src/com/android/settings/AirplaneModeEnabler.java
@@ -24,8 +24,6 @@
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.provider.Settings;
-import android.support.v14.preference.SwitchPreference;
-import android.support.v7.preference.Preference;
 
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.telephony.PhoneStateIntentReceiver;
@@ -33,16 +31,26 @@
 import com.android.settingslib.WirelessUtils;
 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
 
-public class AirplaneModeEnabler implements Preference.OnPreferenceChangeListener {
+public class AirplaneModeEnabler {
 
     private static final int EVENT_SERVICE_STATE_CHANGED = 3;
 
     private final Context mContext;
-    private final SwitchPreference mSwitchPref;
     private final MetricsFeatureProvider mMetricsFeatureProvider;
 
     private PhoneStateIntentReceiver mPhoneStateReceiver;
 
+    private OnAirplaneModeChangedListener mOnAirplaneModeChangedListener;
+
+    public interface OnAirplaneModeChangedListener {
+        /**
+         * Called when airplane mode status is changed.
+         *
+         * @param isAirplaneModeOn the airplane mode is on
+         */
+        void onAirplaneModeChanged(boolean isAirplaneModeOn);
+    }
+
     private Handler mHandler = new Handler() {
         @Override
         public void handleMessage(Message msg) {
@@ -61,25 +69,19 @@
         }
     };
 
-    public AirplaneModeEnabler(Context context, SwitchPreference airplaneModeSwitchPreference,
-            MetricsFeatureProvider metricsFeatureProvider) {
+    public AirplaneModeEnabler(Context context, MetricsFeatureProvider metricsFeatureProvider,
+            OnAirplaneModeChangedListener listener) {
 
         mContext = context;
-        mSwitchPref = airplaneModeSwitchPreference;
         mMetricsFeatureProvider = metricsFeatureProvider;
-
-        airplaneModeSwitchPreference.setPersistent(false);
+        mOnAirplaneModeChangedListener = listener;
 
         mPhoneStateReceiver = new PhoneStateIntentReceiver(mContext, mHandler);
         mPhoneStateReceiver.notifyServiceState(EVENT_SERVICE_STATE_CHANGED);
     }
 
     public void resume() {
-
-        mSwitchPref.setChecked(WirelessUtils.isAirplaneModeOn(mContext));
-
         mPhoneStateReceiver.registerIntent();
-        mSwitchPref.setOnPreferenceChangeListener(this);
         mContext.getContentResolver().registerContentObserver(
                 Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON), true,
                 mAirplaneModeObserver);
@@ -87,7 +89,6 @@
 
     public void pause() {
         mPhoneStateReceiver.unregisterIntent();
-        mSwitchPref.setOnPreferenceChangeListener(null);
         mContext.getContentResolver().unregisterContentObserver(mAirplaneModeObserver);
     }
 
@@ -95,8 +96,11 @@
         // Change the system setting
         Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON,
                 enabling ? 1 : 0);
-        // Update the UI to reflect system setting
-        mSwitchPref.setChecked(enabling);
+
+        // Notify listener the system setting is changed.
+        if (mOnAirplaneModeChangedListener != null) {
+            mOnAirplaneModeChangedListener.onAirplaneModeChanged(enabling);
+        }
 
         // Post the intent
         Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
@@ -113,22 +117,20 @@
      * - mobile does not send failure notification, fail on timeout.
      */
     private void onAirplaneModeChanged() {
-        mSwitchPref.setChecked(WirelessUtils.isAirplaneModeOn(mContext));
+        if (mOnAirplaneModeChangedListener != null) {
+            mOnAirplaneModeChangedListener.onAirplaneModeChanged(isAirplaneModeOn());
+        }
     }
 
-    /**
-     * Called when someone clicks on the checkbox preference.
-     */
-    public boolean onPreferenceChange(Preference preference, Object newValue) {
+    public void setAirplaneMode(boolean isAirplaneModeOn) {
         if (Boolean.parseBoolean(
                 SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE))) {
             // In ECM mode, do not update database at this point
         } else {
-            Boolean value = (Boolean) newValue;
-            mMetricsFeatureProvider.action(mContext, MetricsEvent.ACTION_AIRPLANE_TOGGLE, value);
-            setAirplaneModeOn(value);
+            mMetricsFeatureProvider.action(mContext, MetricsEvent.ACTION_AIRPLANE_TOGGLE,
+                    isAirplaneModeOn);
+            setAirplaneModeOn(isAirplaneModeOn);
         }
-        return true;
     }
 
     public void setAirplaneModeInECM(boolean isECMExit, boolean isAirplaneModeOn) {
@@ -141,4 +143,7 @@
         }
     }
 
+    public boolean isAirplaneModeOn() {
+        return WirelessUtils.isAirplaneModeOn(mContext);
+    }
 }
diff --git a/src/com/android/settings/network/AirplaneModePreferenceController.java b/src/com/android/settings/network/AirplaneModePreferenceController.java
index 0b77179..b4851e6 100644
--- a/src/com/android/settings/network/AirplaneModePreferenceController.java
+++ b/src/com/android/settings/network/AirplaneModePreferenceController.java
@@ -27,17 +27,17 @@
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.telephony.TelephonyProperties;
 import com.android.settings.AirplaneModeEnabler;
-import com.android.settings.core.PreferenceControllerMixin;
+import com.android.settings.core.TogglePreferenceController;
 import com.android.settings.overlay.FeatureFactory;
 import com.android.settings.R;
-import com.android.settingslib.core.AbstractPreferenceController;
 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
 import com.android.settingslib.core.lifecycle.LifecycleObserver;
 import com.android.settingslib.core.lifecycle.events.OnPause;
 import com.android.settingslib.core.lifecycle.events.OnResume;
 
-public class AirplaneModePreferenceController extends AbstractPreferenceController
-        implements PreferenceControllerMixin, LifecycleObserver, OnResume, OnPause {
+public class AirplaneModePreferenceController extends TogglePreferenceController
+        implements LifecycleObserver, OnResume, OnPause,
+        AirplaneModeEnabler.OnAirplaneModeChangedListener {
 
     public static final int REQUEST_CODE_EXIT_ECM = 1;
 
@@ -45,18 +45,21 @@
 
     private static final String EXIT_ECM_RESULT = "exit_ecm_result";
 
-    private final Fragment mFragment;
+    private Fragment mFragment;
     private final MetricsFeatureProvider mMetricsFeatureProvider;
     private AirplaneModeEnabler mAirplaneModeEnabler;
     private SwitchPreference mAirplaneModePreference;
 
 
-    public AirplaneModePreferenceController(Context context, Fragment hostFragment) {
-        super(context);
-        mFragment = hostFragment;
+    public AirplaneModePreferenceController(Context context, String key) {
+        super(context, key);
         mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider();
     }
 
+    public void setFragment(Fragment hostFragment) {
+        mFragment = hostFragment;
+    }
+
     @Override
     public boolean handlePreferenceTreeClick(Preference preference) {
         if (KEY_TOGGLE_AIRPLANE.equals(preference.getKey()) && Boolean.parseBoolean(
@@ -75,30 +78,22 @@
 
     @Override
     public void displayPreference(PreferenceScreen screen) {
+        super.displayPreference(screen);
         if (isAvailable()) {
             mAirplaneModePreference = (SwitchPreference) screen.findPreference(getPreferenceKey());
-            if (mAirplaneModePreference != null) {
-                mAirplaneModeEnabler = new AirplaneModeEnabler(mContext, mAirplaneModePreference,
-                        mMetricsFeatureProvider);
-            }
-        } else {
-            setVisible(screen, getPreferenceKey(), false /* visible */);
+            mAirplaneModeEnabler = new AirplaneModeEnabler(mContext, mMetricsFeatureProvider, this);
         }
     }
 
-    @Override
-    public boolean isAvailable() {
-        return isAvailable(mContext);
-    }
-
     public static boolean isAvailable(Context context) {
         return context.getResources().getBoolean(R.bool.config_show_toggle_airplane)
                 && !context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK);
     }
 
     @Override
-    public String getPreferenceKey() {
-        return KEY_TOGGLE_AIRPLANE;
+    @AvailabilityStatus
+    public int getAvailabilityStatus() {
+        return isAvailable(mContext) ? AVAILABLE : DISABLED_UNSUPPORTED;
     }
 
     public void onResume() {
@@ -122,4 +117,23 @@
                     mAirplaneModePreference.isChecked());
         }
     }
+
+    @Override
+    public boolean isChecked() {
+        return mAirplaneModeEnabler.isAirplaneModeOn();
+    }
+
+    @Override
+    public boolean setChecked(boolean isChecked) {
+        if (isChecked() == isChecked) {
+            return false;
+        }
+        mAirplaneModeEnabler.setAirplaneMode(isChecked);
+        return true;
+    }
+
+    @Override
+    public void onAirplaneModeChanged(boolean isAirplaneModeOn) {
+        mAirplaneModePreference.setChecked(isAirplaneModeOn);
+    }
 }
diff --git a/src/com/android/settings/network/NetworkDashboardFragment.java b/src/com/android/settings/network/NetworkDashboardFragment.java
index 955c503..466fa01 100644
--- a/src/com/android/settings/network/NetworkDashboardFragment.java
+++ b/src/com/android/settings/network/NetworkDashboardFragment.java
@@ -71,6 +71,8 @@
     public void onAttach(Context context) {
         super.onAttach(context);
         mNetworkResetController = new NetworkResetActionMenuController(context, MENU_NETWORK_RESET);
+
+        use(AirplaneModePreferenceController.class).setFragment(this);
     }
 
     @Override
@@ -94,8 +96,6 @@
     private static List<AbstractPreferenceController> buildPreferenceControllers(Context context,
             Lifecycle lifecycle, MetricsFeatureProvider metricsFeatureProvider, Fragment fragment,
             MobilePlanPreferenceHost mobilePlanHost) {
-        final AirplaneModePreferenceController airplaneModePreferenceController =
-                new AirplaneModePreferenceController(context, fragment);
         final MobilePlanPreferenceController mobilePlanPreferenceController =
                 new MobilePlanPreferenceController(context, mobilePlanHost);
         final WifiMasterSwitchPreferenceController wifiPreferenceController =
@@ -108,7 +108,6 @@
                 new PrivateDnsPreferenceController(context);
 
         if (lifecycle != null) {
-            lifecycle.addObserver(airplaneModePreferenceController);
             lifecycle.addObserver(mobilePlanPreferenceController);
             lifecycle.addObserver(wifiPreferenceController);
             lifecycle.addObserver(mobileNetworkPreferenceController);
@@ -117,7 +116,6 @@
         }
 
         final List<AbstractPreferenceController> controllers = new ArrayList<>();
-        controllers.add(airplaneModePreferenceController);
         controllers.add(mobileNetworkPreferenceController);
         controllers.add(new TetherPreferenceController(context, lifecycle));
         controllers.add(vpnPreferenceController);
diff --git a/tests/robotests/src/com/android/settings/network/AirplaneModePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/network/AirplaneModePreferenceControllerTest.java
index 33eed3e..a255333 100644
--- a/tests/robotests/src/com/android/settings/network/AirplaneModePreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/network/AirplaneModePreferenceControllerTest.java
@@ -22,13 +22,18 @@
 import static org.mockito.Mockito.when;
 
 import android.arch.lifecycle.LifecycleOwner;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.provider.Settings;
+import android.support.v7.preference.PreferenceManager;
 import android.support.v7.preference.PreferenceScreen;
 
+import com.android.settings.core.BasePreferenceController;
 import com.android.settings.testutils.FakeFeatureFactory;
 import com.android.settings.testutils.SettingsRobolectricTestRunner;
 import com.android.settingslib.core.lifecycle.Lifecycle;
+import com.android.settingslib.RestrictedSwitchPreference;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -41,13 +46,17 @@
 @RunWith(SettingsRobolectricTestRunner.class)
 public class AirplaneModePreferenceControllerTest {
 
-    @Mock
-    private PreferenceScreen mScreen;
+    private static final int ON = 1;
+    private static final int OFF = 0;
 
     @Mock
     private PackageManager mPackageManager;
 
     private Context mContext;
+    private ContentResolver mResolver;
+    private PreferenceManager mPreferenceManager;
+    private PreferenceScreen mScreen;
+    private RestrictedSwitchPreference mPreference;
     private AirplaneModePreferenceController mController;
     private LifecycleOwner mLifecycleOwner;
     private Lifecycle mLifecycle;
@@ -57,8 +66,17 @@
         MockitoAnnotations.initMocks(this);
         FakeFeatureFactory.setupForTest();
         mContext = spy(RuntimeEnvironment.application);
+        mResolver = RuntimeEnvironment.application.getContentResolver();
         doReturn(mPackageManager).when(mContext).getPackageManager();
-        mController = spy(new AirplaneModePreferenceController(mContext, null));
+        mController = new AirplaneModePreferenceController(mContext,
+                AirplaneModePreferenceController.KEY_TOGGLE_AIRPLANE);
+
+        mPreferenceManager = new PreferenceManager(mContext);
+        mScreen = mPreferenceManager.createPreferenceScreen(mContext);
+        mPreference = new RestrictedSwitchPreference(mContext);
+        mPreference.setKey("toggle_airplane");
+        mScreen.addPreference(mPreference);
+        mController.setFragment(null);
         mLifecycleOwner = () -> mLifecycle;
         mLifecycle = new Lifecycle(mLifecycleOwner);
         mLifecycle.addObserver(mController);
@@ -67,7 +85,8 @@
     @Test
     @Config(qualifiers = "mcc999")
     public void airplaneModePreference_shouldNotBeAvailable_ifSetToNotVisible() {
-        assertThat(mController.isAvailable()).isFalse();
+        assertThat(mController.getAvailabilityStatus())
+                .isNotEqualTo(BasePreferenceController.AVAILABLE);
 
         mController.displayPreference(mScreen);
 
@@ -79,7 +98,8 @@
     @Test
     public void airplaneModePreference_shouldNotBeAvailable_ifHasLeanbackFeature() {
         when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)).thenReturn(true);
-        assertThat(mController.isAvailable()).isFalse();
+        assertThat(mController.getAvailabilityStatus())
+                .isNotEqualTo(BasePreferenceController.AVAILABLE);
 
         mController.displayPreference(mScreen);
 
@@ -91,6 +111,70 @@
     @Test
     public void airplaneModePreference_shouldBeAvailable_ifNoLeanbackFeature() {
         when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)).thenReturn(false);
-        assertThat(mController.isAvailable()).isTrue();
+        assertThat(mController.getAvailabilityStatus())
+                .isEqualTo(BasePreferenceController.AVAILABLE);
+    }
+
+    @Test
+    public void airplaneModePreference_testSetValue_updatesCorrectly() {
+        // Airplane mode default off
+        Settings.Global.putInt(mResolver, Settings.Global.AIRPLANE_MODE_ON, OFF);
+
+        mController.displayPreference(mScreen);
+        mController.onResume();
+
+        assertThat(mPreference.isChecked()).isFalse();
+
+        assertThat(mController.isChecked()).isFalse();
+
+        // Set airplane mode ON by setChecked
+        boolean updated = mController.setChecked(true);
+        assertThat(updated).isTrue();
+
+        // Check return value if set same status.
+        updated = mController.setChecked(true);
+        assertThat(updated).isFalse();
+
+        // UI is updated
+        assertThat(mPreference.isChecked()).isTrue();
+
+        // Settings status changed.
+        int updatedValue = Settings.Global.getInt(mResolver, Settings.Global.AIRPLANE_MODE_ON, OFF);
+        assertThat(updatedValue).isEqualTo(ON);
+
+        // Set to OFF
+        assertThat(mController.setChecked(false)).isTrue();
+        assertThat(mPreference.isChecked()).isFalse();
+        updatedValue = Settings.Global.getInt(mResolver, Settings.Global.AIRPLANE_MODE_ON, OFF);
+        assertThat(updatedValue).isEqualTo(OFF);
+    }
+
+    @Test
+    public void airplaneModePreference_testGetValue_correctValueReturned() {
+        // Set airplane mode ON
+        Settings.Global.putInt(mResolver, Settings.Global.AIRPLANE_MODE_ON, ON);
+
+        mController.displayPreference(mScreen);
+        mController.onResume();
+
+        assertThat(mController.isChecked()).isTrue();
+
+        Settings.Global.putInt(mResolver, Settings.Global.AIRPLANE_MODE_ON, OFF);
+        assertThat(mController.isChecked()).isFalse();
+    }
+
+    @Test
+    public void airplaneModePreference_testPreferenceUI_updatesCorrectly() {
+        // Airplane mode default off
+        Settings.Global.putInt(mResolver, Settings.Global.AIRPLANE_MODE_ON, OFF);
+
+        mController.displayPreference(mScreen);
+        mController.onResume();
+
+        assertThat(mPreference.isChecked()).isFalse();
+
+        mController.onAirplaneModeChanged(true);
+
+        assertThat(mPreference.isChecked()).isTrue();
     }
 }