Merge "Update activity titles for fragments without preference screen."
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 4bc2c6d..bc63d14 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -1031,10 +1031,6 @@
<intent-filter android:priority="3">
<action android:name="com.android.settings.action.SETTINGS" />
</intent-filter>
- <meta-data android:name="com.android.settings.category"
- android:value="com.android.settings.category.ia.development" />
- <meta-data android:name="com.android.settings.summary"
- android:resource="@string/summary_empty" />
<meta-data android:name="com.android.settings.FRAGMENT_CLASS"
android:value="com.android.settings.applications.ProcessStatsSummary" />
</activity>
diff --git a/res/color/sliding_tab_title_text_color.xml b/res/color/sliding_tab_title_text_color.xml
new file mode 100644
index 0000000..d6bfbcc
--- /dev/null
+++ b/res/color/sliding_tab_title_text_color.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_selected="false"
+ android:color="?android:attr/textColorSecondary"/>
+ <item android:color="?android:attr/textColorPrimary"/>
+</selector>
diff --git a/res/layout/sliding_tab_indicator_view.xml b/res/layout/sliding_tab_indicator_view.xml
new file mode 100644
index 0000000..b594c8b
--- /dev/null
+++ b/res/layout/sliding_tab_indicator_view.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+
+<View
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/sliding_tab_selected_indicator"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/pager_tabs_selected_indicator_height"
+ android:background="?android:attr/colorAccent"
+ android:theme="@*android:style/ThemeOverlay.DeviceDefault.Accent" />
diff --git a/res/layout/sliding_tab_title_view.xml b/res/layout/sliding_tab_title_view.xml
new file mode 100644
index 0000000..5dead02
--- /dev/null
+++ b/res/layout/sliding_tab_title_view.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 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.
+-->
+
+<TextView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"
+ android:background="?android:attr/selectableItemBackground"
+ android:ellipsize="end"
+ android:fontFamily="sans-serif-medium"
+ android:gravity="center"
+ android:maxLines="1"
+ android:padding="@dimen/pager_tabs_title_padding"
+ android:textColor="@color/sliding_tab_title_text_color"
+ android:textAllCaps="true"
+ android:theme="?android:attr/actionBarTheme"/>
diff --git a/res/layout/wifi_calling_settings_preferences.xml b/res/layout/wifi_calling_settings_preferences.xml
new file mode 100644
index 0000000..4e64f40
--- /dev/null
+++ b/res/layout/wifi_calling_settings_preferences.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/tabs_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <com.android.settings.widget.SwitchBar
+ android:id="@+id/switch_bar"
+ android:layout_height="?android:attr/actionBarSize"
+ android:layout_width="match_parent"
+ android:background="@drawable/switchbar_background"
+ android:theme="?attr/switchBarTheme" />
+
+ <FrameLayout
+ android:id="@android:id/tabcontent"
+ android:layout_width="0dip"
+ android:layout_height="0dip" />
+
+ <FrameLayout
+ android:id="@+id/prefs_container"
+ android:layout_width="match_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:smoothScrollbar="false" />
+
+</LinearLayout>
diff --git a/res/layout/wifi_calling_settings_tabs.xml b/res/layout/wifi_calling_settings_tabs.xml
new file mode 100644
index 0000000..1e27b47
--- /dev/null
+++ b/res/layout/wifi_calling_settings_tabs.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/tabs_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <com.android.settings.widget.SlidingTabLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/sliding_tabs"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:scrollbars="none"
+ android:fillViewport="true"/>
+
+ <com.android.settings.widget.RtlCompatibleViewPager
+ android:id="@+id/view_pager"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
+
+</LinearLayout>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 2d90e2f..fdb9c32 100755
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -51,6 +51,8 @@
<dimen name="appwidget_min_height">40dip</dimen>
<dimen name="pager_tabs_padding">0dp</dimen>
+ <dimen name="pager_tabs_title_padding">16dp</dimen>
+ <dimen name="pager_tabs_selected_indicator_height">3dp</dimen>
<!-- Minimum width for the popup for updating a user's photo. -->
<dimen name="update_user_photo_popup_min_width">300dip</dimen>
diff --git a/res/xml/development_prefs.xml b/res/xml/development_prefs.xml
index e60dbe3..20482b7 100644
--- a/res/xml/development_prefs.xml
+++ b/res/xml/development_prefs.xml
@@ -18,6 +18,14 @@
xmlns:settings="http://schemas.android.com/apk/res/com.android.settings"
android:key="development_prefs_screen"
android:title="@string/development_settings_title">
+
+ <Preference
+ android:key="memory"
+ android:icon="@drawable/ic_settings_memory"
+ android:title="@string/memory_settings_title"
+ android:summary="@string/summary_empty"
+ android:fragment="com.android.settings.applications.ProcessStatsSummary" />
+
<com.android.settings.BugreportPreference
android:key="bugreport"
android:title="@*android:string/bugreport_title"
diff --git a/res/xml/manage_assist.xml b/res/xml/manage_assist.xml
index a96fb6b..120309a 100644
--- a/res/xml/manage_assist.xml
+++ b/res/xml/manage_assist.xml
@@ -22,7 +22,7 @@
<com.android.settings.widget.GearPreference
android:key="default_assist"
android:title="@string/default_assist_title"
- android:summary="@string/app_list_preference_none"
+ android:summary="@string/summary_placeholder"
android:fragment="com.android.settings.applications.assist.DefaultAssistPicker"/>
<Preference
diff --git a/res/xml/pick_up_gesture_settings.xml b/res/xml/pick_up_gesture_settings.xml
index 0b4a1de..e1414cd 100644
--- a/res/xml/pick_up_gesture_settings.xml
+++ b/res/xml/pick_up_gesture_settings.xml
@@ -18,6 +18,7 @@
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:key="gesture_pick_up_screen"
android:title="@string/ambient_display_pickup_title">
<com.android.settings.widget.VideoPreference
diff --git a/res/xml/tts_engine_picker.xml b/res/xml/tts_engine_picker.xml
index c0a464c..92bfede 100644
--- a/res/xml/tts_engine_picker.xml
+++ b/res/xml/tts_engine_picker.xml
@@ -15,6 +15,7 @@
-->
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+ android:key="tts_engine_picker_screen"
android:title="@string/tts_engine_preference_title">
<PreferenceCategory android:key="tts_engine_preference_category"/>
diff --git a/src/com/android/settings/WifiCallingSettings.java b/src/com/android/settings/WifiCallingSettings.java
index cb661ed..e872fb8 100644
--- a/src/com/android/settings/WifiCallingSettings.java
+++ b/src/com/android/settings/WifiCallingSettings.java
@@ -16,190 +16,38 @@
package com.android.settings;
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
+import android.app.Fragment;
+import android.app.FragmentManager;
import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
import android.os.Bundle;
-import android.os.PersistableBundle;
-import android.support.v7.preference.ListPreference;
-import android.support.v7.preference.Preference;
-import android.support.v7.preference.Preference.OnPreferenceClickListener;
-import android.support.v7.preference.PreferenceScreen;
-import android.telephony.CarrierConfigManager;
-import android.telephony.PhoneStateListener;
-import android.telephony.TelephonyManager;
-import android.text.TextUtils;
+import android.support.v13.app.FragmentPagerAdapter;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
import android.util.Log;
-import android.widget.Switch;
-import android.widget.TextView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
import com.android.ims.ImsConfig;
import com.android.ims.ImsManager;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.internal.telephony.Phone;
-import com.android.settings.widget.SwitchBar;
+import com.android.settings.widget.RtlCompatibleViewPager;
+import com.android.settings.widget.SlidingTabLayout;
+
+import java.util.List;
/**
- * "Wi-Fi Calling settings" screen. This preference screen lets you
- * enable/disable Wi-Fi Calling and change Wi-Fi Calling mode.
+ * "Wi-Fi Calling settings" screen. This is the container fragment which holds
+ * {@link WifiCallingSettingsForSub} fragments.
*/
-public class WifiCallingSettings extends SettingsPreferenceFragment
- implements SwitchBar.OnSwitchChangeListener,
- Preference.OnPreferenceChangeListener {
-
+public class WifiCallingSettings extends SettingsPreferenceFragment {
private static final String TAG = "WifiCallingSettings";
-
- //String keys for preference lookup
- private static final String BUTTON_WFC_MODE = "wifi_calling_mode";
- private static final String BUTTON_WFC_ROAMING_MODE = "wifi_calling_roaming_mode";
- private static final String PREFERENCE_EMERGENCY_ADDRESS = "emergency_address_key";
-
- private static final int REQUEST_CHECK_WFC_EMERGENCY_ADDRESS = 1;
-
- public static final String EXTRA_LAUNCH_CARRIER_APP = "EXTRA_LAUNCH_CARRIER_APP";
-
- public static final int LAUCH_APP_ACTIVATE = 0;
- public static final int LAUCH_APP_UPDATE = 1;
+ private List<SubscriptionInfo> mSil;
//UI objects
- private SwitchBar mSwitchBar;
- private Switch mSwitch;
- private ListPreference mButtonWfcMode;
- private ListPreference mButtonWfcRoamingMode;
- private Preference mUpdateAddress;
- private TextView mEmptyView;
-
- private boolean mValidListener = false;
- private boolean mEditableWfcMode = true;
- private boolean mEditableWfcRoamingMode = true;
-
- private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
- /*
- * Enable/disable controls when in/out of a call and depending on
- * TTY mode and TTY support over VoLTE.
- * @see android.telephony.PhoneStateListener#onCallStateChanged(int,
- * java.lang.String)
- */
- @Override
- public void onCallStateChanged(int state, String incomingNumber) {
- final SettingsActivity activity = (SettingsActivity) getActivity();
- boolean isNonTtyOrTtyOnVolteEnabled = ImsManager
- .isNonTtyOrTtyOnVolteEnabled(activity);
- final SwitchBar switchBar = activity.getSwitchBar();
- boolean isWfcEnabled = switchBar.getSwitch().isChecked()
- && isNonTtyOrTtyOnVolteEnabled;
-
- switchBar.setEnabled((state == TelephonyManager.CALL_STATE_IDLE)
- && isNonTtyOrTtyOnVolteEnabled);
-
- boolean isWfcModeEditable = true;
- boolean isWfcRoamingModeEditable = false;
- final CarrierConfigManager configManager = (CarrierConfigManager)
- activity.getSystemService(Context.CARRIER_CONFIG_SERVICE);
- if (configManager != null) {
- PersistableBundle b = configManager.getConfig();
- if (b != null) {
- isWfcModeEditable = b.getBoolean(
- CarrierConfigManager.KEY_EDITABLE_WFC_MODE_BOOL);
- isWfcRoamingModeEditable = b.getBoolean(
- CarrierConfigManager.KEY_EDITABLE_WFC_ROAMING_MODE_BOOL);
- }
- }
-
- Preference pref = getPreferenceScreen().findPreference(BUTTON_WFC_MODE);
- if (pref != null) {
- pref.setEnabled(isWfcEnabled && isWfcModeEditable
- && (state == TelephonyManager.CALL_STATE_IDLE));
- }
- Preference pref_roam = getPreferenceScreen().findPreference(BUTTON_WFC_ROAMING_MODE);
- if (pref_roam != null) {
- pref_roam.setEnabled(isWfcEnabled && isWfcRoamingModeEditable
- && (state == TelephonyManager.CALL_STATE_IDLE));
- }
- }
- };
-
- private final OnPreferenceClickListener mUpdateAddressListener =
- new OnPreferenceClickListener() {
- /*
- * Launch carrier emergency address managemnent activity
- */
- @Override
- public boolean onPreferenceClick(Preference preference) {
- final Context context = getActivity();
- Intent carrierAppIntent = getCarrierActivityIntent(context);
- if (carrierAppIntent != null) {
- carrierAppIntent.putExtra(EXTRA_LAUNCH_CARRIER_APP, LAUCH_APP_UPDATE);
- startActivity(carrierAppIntent);
- }
- return true;
- }
- };
-
- @Override
- public void onActivityCreated(Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
-
- final SettingsActivity activity = (SettingsActivity) getActivity();
-
- mSwitchBar = activity.getSwitchBar();
- mSwitch = mSwitchBar.getSwitch();
- mSwitchBar.show();
-
- mEmptyView = (TextView) getView().findViewById(android.R.id.empty);
- setEmptyView(mEmptyView);
- String emptyViewText = activity.getString(R.string.wifi_calling_off_explanation)
- + activity.getString(R.string.wifi_calling_off_explanation_2);
- mEmptyView.setText(emptyViewText);
- }
-
- @Override
- public void onDestroyView() {
- super.onDestroyView();
- mSwitchBar.hide();
- }
-
- private void showAlert(Intent intent) {
- Context context = getActivity();
-
- CharSequence title = intent.getCharSequenceExtra(Phone.EXTRA_KEY_ALERT_TITLE);
- CharSequence message = intent.getCharSequenceExtra(Phone.EXTRA_KEY_ALERT_MESSAGE);
-
- AlertDialog.Builder builder = new AlertDialog.Builder(context);
- builder.setMessage(message)
- .setTitle(title)
- .setIcon(android.R.drawable.ic_dialog_alert)
- .setPositiveButton(android.R.string.ok, null);
- AlertDialog dialog = builder.create();
- dialog.show();
- }
-
- private IntentFilter mIntentFilter;
-
- private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- if (action.equals(ImsManager.ACTION_IMS_REGISTRATION_ERROR)) {
- // If this fragment is active then we are immediately
- // showing alert on screen. There is no need to add
- // notification in this case.
- //
- // In order to communicate to ImsPhone that it should
- // not show notification, we are changing result code here.
- setResultCode(Activity.RESULT_CANCELED);
-
- // UX requirement is to disable WFC in case of "permanent" registration failures.
- mSwitch.setChecked(false);
-
- showAlert(intent);
- }
- }
- };
+ private RtlCompatibleViewPager mViewPager;
+ private WifiCallingViewPagerAdapter mPagerAdapter;
+ private SlidingTabLayout mTabLayout;
@Override
public int getMetricsCategory() {
@@ -207,242 +55,81 @@
}
@Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ final View view = inflater.inflate(R.layout.wifi_calling_settings_tabs, container, false);
- addPreferencesFromResource(R.xml.wifi_calling_settings);
+ mTabLayout = view.findViewById(R.id.sliding_tabs);
+ mViewPager = (RtlCompatibleViewPager) view.findViewById(R.id.view_pager);
- mButtonWfcMode = (ListPreference) findPreference(BUTTON_WFC_MODE);
- mButtonWfcMode.setOnPreferenceChangeListener(this);
+ mPagerAdapter = new WifiCallingViewPagerAdapter(getChildFragmentManager(), mViewPager);
+ mViewPager.setAdapter(mPagerAdapter);
- mButtonWfcRoamingMode = (ListPreference) findPreference(BUTTON_WFC_ROAMING_MODE);
- mButtonWfcRoamingMode.setOnPreferenceChangeListener(this);
-
- mUpdateAddress = (Preference) findPreference(PREFERENCE_EMERGENCY_ADDRESS);
- mUpdateAddress.setOnPreferenceClickListener(mUpdateAddressListener);
-
- mIntentFilter = new IntentFilter();
- mIntentFilter.addAction(ImsManager.ACTION_IMS_REGISTRATION_ERROR);
-
- CarrierConfigManager configManager = (CarrierConfigManager)
- getSystemService(Context.CARRIER_CONFIG_SERVICE);
- boolean isWifiOnlySupported = true;
- if (configManager != null) {
- PersistableBundle b = configManager.getConfig();
- if (b != null) {
- mEditableWfcMode = b.getBoolean(CarrierConfigManager.KEY_EDITABLE_WFC_MODE_BOOL);
- mEditableWfcRoamingMode = b.getBoolean(
- CarrierConfigManager.KEY_EDITABLE_WFC_ROAMING_MODE_BOOL);
- isWifiOnlySupported = b.getBoolean(
- CarrierConfigManager.KEY_CARRIER_WFC_SUPPORTS_WIFI_ONLY_BOOL, true);
- }
- }
-
- if (!isWifiOnlySupported) {
- mButtonWfcMode.setEntries(R.array.wifi_calling_mode_choices_without_wifi_only);
- mButtonWfcMode.setEntryValues(R.array.wifi_calling_mode_values_without_wifi_only);
- mButtonWfcRoamingMode.setEntries(
- R.array.wifi_calling_mode_choices_v2_without_wifi_only);
- mButtonWfcRoamingMode.setEntryValues(
- R.array.wifi_calling_mode_values_without_wifi_only);
- }
+ return view;
}
@Override
- public void onResume() {
- super.onResume();
-
- final Context context = getActivity();
-
- // NOTE: Buttons will be enabled/disabled in mPhoneStateListener
- boolean wfcEnabled = ImsManager.isWfcEnabledByUser(context)
- && ImsManager.isNonTtyOrTtyOnVolteEnabled(context);
- mSwitch.setChecked(wfcEnabled);
- int wfcMode = ImsManager.getWfcMode(context, false);
- int wfcRoamingMode = ImsManager.getWfcMode(context, true);
- mButtonWfcMode.setValue(Integer.toString(wfcMode));
- mButtonWfcRoamingMode.setValue(Integer.toString(wfcRoamingMode));
- updateButtonWfcMode(context, wfcEnabled, wfcMode, wfcRoamingMode);
-
- if (ImsManager.isWfcEnabledByPlatform(context)) {
- TelephonyManager tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
- tm.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
-
- mSwitchBar.addOnSwitchChangeListener(this);
-
- mValidListener = true;
- }
-
- context.registerReceiver(mIntentReceiver, mIntentFilter);
-
- Intent intent = getActivity().getIntent();
- if (intent.getBooleanExtra(Phone.EXTRA_KEY_ALERT_SHOW, false)) {
- showAlert(intent);
- }
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ // TODO: besides in onCreate, we should also update subList when SIM / Sub status
+ // changes.
+ updateSubList();
}
@Override
- public void onPause() {
- super.onPause();
+ public void onStart() {
+ super.onStart();
- final Context context = getActivity();
-
- if (mValidListener) {
- mValidListener = false;
-
- TelephonyManager tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
- tm.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
-
- mSwitchBar.removeOnSwitchChangeListener(this);
- }
-
- context.unregisterReceiver(mIntentReceiver);
- }
-
- /**
- * Listens to the state change of the switch.
- */
- @Override
- public void onSwitchChanged(Switch switchView, boolean isChecked) {
- final Context context = getActivity();
- Log.d(TAG, "onSwitchChanged(" + isChecked + ")");
-
- if (!isChecked) {
- updateWfcMode(context, false);
- return;
- }
-
- // Call address management activity before turning on WFC
- Intent carrierAppIntent = getCarrierActivityIntent(context);
- if (carrierAppIntent != null) {
- carrierAppIntent.putExtra(EXTRA_LAUNCH_CARRIER_APP, LAUCH_APP_ACTIVATE);
- startActivityForResult(carrierAppIntent, REQUEST_CHECK_WFC_EMERGENCY_ADDRESS);
+ if (mSil != null && mSil.size() > 1) {
+ mTabLayout.setViewPager(mViewPager);
} else {
- updateWfcMode(context, true);
+ mTabLayout.setVisibility(View.GONE);
}
}
- /*
- * Get the Intent to launch carrier emergency address management activity.
- * Return null when no activity found.
- */
- private static Intent getCarrierActivityIntent(Context context) {
- // Retrive component name from carrirt config
- CarrierConfigManager configManager = context.getSystemService(CarrierConfigManager.class);
- if (configManager == null) return null;
+ private final class WifiCallingViewPagerAdapter extends FragmentPagerAdapter {
+ private final RtlCompatibleViewPager mViewPager;
- PersistableBundle bundle = configManager.getConfig();
- if (bundle == null) return null;
-
- String carrierApp = bundle.getString(
- CarrierConfigManager.KEY_WFC_EMERGENCY_ADDRESS_CARRIER_APP_STRING);
- if (TextUtils.isEmpty(carrierApp)) return null;
-
- ComponentName componentName = ComponentName.unflattenFromString(carrierApp);
- if (componentName == null) return null;
-
- // Build and return intent
- Intent intent = new Intent();
- intent.setComponent(componentName);
- return intent;
- }
-
- /*
- * Turn on/off WFC mode with ImsManager and update UI accordingly
- */
- private void updateWfcMode(Context context, boolean wfcEnabled) {
- Log.i(TAG, "updateWfcMode(" + wfcEnabled + ")");
- ImsManager.setWfcSetting(context, wfcEnabled);
-
- int wfcMode = ImsManager.getWfcMode(context, false);
- int wfcRoamingMode = ImsManager.getWfcMode(context, true);
- updateButtonWfcMode(context, wfcEnabled, wfcMode, wfcRoamingMode);
- if (wfcEnabled) {
- mMetricsFeatureProvider.action(getActivity(), getMetricsCategory(), wfcMode);
- } else {
- mMetricsFeatureProvider.action(getActivity(), getMetricsCategory(), -1);
+ public WifiCallingViewPagerAdapter(FragmentManager fragmentManager,
+ RtlCompatibleViewPager viewPager) {
+ super(fragmentManager);
+ mViewPager = viewPager;
}
- }
- @Override
- public void onActivityResult(int requestCode, int resultCode, Intent data) {
- super.onActivityResult(requestCode, resultCode, data);
-
- final Context context = getActivity();
-
- if (requestCode == REQUEST_CHECK_WFC_EMERGENCY_ADDRESS) {
- Log.d(TAG, "WFC emergency address activity result = " + resultCode);
-
- if (resultCode == Activity.RESULT_OK) {
- updateWfcMode(context, true);
- }
+ @Override
+ public CharSequence getPageTitle(int position) {
+ return String.valueOf(mSil.get(position).getDisplayName());
}
- }
- private void updateButtonWfcMode(Context context, boolean wfcEnabled,
- int wfcMode, int wfcRoamingMode) {
- mButtonWfcMode.setSummary(getWfcModeSummary(context, wfcMode));
- mButtonWfcMode.setEnabled(wfcEnabled && mEditableWfcMode);
- // mButtonWfcRoamingMode.setSummary is not needed; summary is just selected value.
- mButtonWfcRoamingMode.setEnabled(wfcEnabled && mEditableWfcRoamingMode);
+ @Override
+ public Fragment getItem(int position) {
+ Log.d(TAG, "Adapter getItem " + position);
+ final Bundle args = new Bundle();
+ args.putInt(WifiCallingSettingsForSub.FRAGMENT_BUNDLE_SUBID,
+ mSil.get(position).getSubscriptionId());
+ WifiCallingSettingsForSub fragment = new WifiCallingSettingsForSub();
+ fragment.setArguments(args);
- final PreferenceScreen preferenceScreen = getPreferenceScreen();
- boolean updateAddressEnabled = (getCarrierActivityIntent(context) != null);
- if (wfcEnabled) {
- if (mEditableWfcMode) {
- preferenceScreen.addPreference(mButtonWfcMode);
+ return fragment;
+ }
+
+ @Override
+ public Object instantiateItem(ViewGroup container, int position) {
+ Log.d(TAG, "Adapter instantiateItem " + position);
+ return super.instantiateItem(container,
+ mViewPager.getRtlAwareIndex(position));
+ }
+
+ @Override
+ public int getCount() {
+ if (mSil == null) {
+ Log.d(TAG, "Adapter getCount null mSil ");
+ return 0;
} else {
- // Don't show WFC (home) preference if it's not editable.
- preferenceScreen.removePreference(mButtonWfcMode);
- }
- if (mEditableWfcRoamingMode) {
- preferenceScreen.addPreference(mButtonWfcRoamingMode);
- } else {
- // Don't show WFC roaming preference if it's not editable.
- preferenceScreen.removePreference(mButtonWfcRoamingMode);
- }
- if (updateAddressEnabled) {
- preferenceScreen.addPreference(mUpdateAddress);
- } else {
- preferenceScreen.removePreference(mUpdateAddress);
- }
- } else {
- preferenceScreen.removePreference(mButtonWfcMode);
- preferenceScreen.removePreference(mButtonWfcRoamingMode);
- preferenceScreen.removePreference(mUpdateAddress);
- }
- }
-
- @Override
- public boolean onPreferenceChange(Preference preference, Object newValue) {
- final Context context = getActivity();
- if (preference == mButtonWfcMode) {
- mButtonWfcMode.setValue((String) newValue);
- int buttonMode = Integer.valueOf((String) newValue);
- int currentWfcMode = ImsManager.getWfcMode(context, false);
- if (buttonMode != currentWfcMode) {
- ImsManager.setWfcMode(context, buttonMode, false);
- mButtonWfcMode.setSummary(getWfcModeSummary(context, buttonMode));
- mMetricsFeatureProvider.action(getActivity(), getMetricsCategory(), buttonMode);
- }
- if (!mEditableWfcRoamingMode) {
- int currentWfcRoamingMode = ImsManager.getWfcMode(context, true);
- if (buttonMode != currentWfcRoamingMode) {
- ImsManager.setWfcMode(context, buttonMode, true);
- // mButtonWfcRoamingMode.setSummary is not needed; summary is selected value
- }
- }
- } else if (preference == mButtonWfcRoamingMode) {
- mButtonWfcRoamingMode.setValue((String) newValue);
- int buttonMode = Integer.valueOf((String) newValue);
- int currentMode = ImsManager.getWfcMode(context, true);
- if (buttonMode != currentMode) {
- ImsManager.setWfcMode(context, buttonMode, true);
- // mButtonWfcRoamingMode.setSummary is not needed; summary is just selected value.
- mMetricsFeatureProvider.action(getActivity(), getMetricsCategory(), buttonMode);
+ Log.d(TAG, "Adapter getCount " + mSil.size());
+ return mSil.size();
}
}
- return true;
}
public static int getWfcModeSummary(Context context, int wfcMode) {
@@ -464,4 +151,22 @@
}
return resId;
}
+
+ private void updateSubList() {
+ mSil = SubscriptionManager.from(getActivity()).getActiveSubscriptionInfoList();
+
+ // Only config Wfc if it's enabled by platform.
+ if (mSil == null) {
+ return;
+ }
+ for (int i = 0; i < mSil.size();) {
+ ImsManager imsManager = ImsManager.getInstance(getActivity(),
+ mSil.get(i).getSimSlotIndex());
+ if (!imsManager.isWfcEnabledByPlatform()) {
+ mSil.remove(i);
+ } else {
+ i++;
+ }
+ }
+ }
}
diff --git a/src/com/android/settings/WifiCallingSettingsForSub.java b/src/com/android/settings/WifiCallingSettingsForSub.java
new file mode 100644
index 0000000..57a4ab2
--- /dev/null
+++ b/src/com/android/settings/WifiCallingSettingsForSub.java
@@ -0,0 +1,521 @@
+/*
+ * Copyright (C) 2017 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;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.os.PersistableBundle;
+import android.support.v7.preference.ListPreference;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.Preference.OnPreferenceClickListener;
+import android.support.v7.preference.PreferenceScreen;
+import android.telephony.CarrierConfigManager;
+import android.telephony.PhoneStateListener;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Switch;
+import android.widget.TextView;
+
+import com.android.ims.ImsConfig;
+import com.android.ims.ImsManager;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.telephony.Phone;
+import com.android.settings.widget.SwitchBar;
+
+/**
+ * This is the inner class of {@link WifiCallingSettings} fragment.
+ * The preference screen lets you enable/disable Wi-Fi Calling and change Wi-Fi Calling mode.
+ */
+public class WifiCallingSettingsForSub extends SettingsPreferenceFragment
+ implements SwitchBar.OnSwitchChangeListener,
+ Preference.OnPreferenceChangeListener {
+ private static final String TAG = "WifiCallingSettingsForSub";
+
+ //String keys for preference lookup
+ private static final String BUTTON_WFC_MODE = "wifi_calling_mode";
+ private static final String BUTTON_WFC_ROAMING_MODE = "wifi_calling_roaming_mode";
+ private static final String PREFERENCE_EMERGENCY_ADDRESS = "emergency_address_key";
+
+ private static final int REQUEST_CHECK_WFC_EMERGENCY_ADDRESS = 1;
+
+ public static final String EXTRA_LAUNCH_CARRIER_APP = "EXTRA_LAUNCH_CARRIER_APP";
+
+ protected static final String FRAGMENT_BUNDLE_SUBID = "subId";
+
+ public static final int LAUCH_APP_ACTIVATE = 0;
+ public static final int LAUCH_APP_UPDATE = 1;
+
+ //UI objects
+ private SwitchBar mSwitchBar;
+ private Switch mSwitch;
+ private ListPreference mButtonWfcMode;
+ private ListPreference mButtonWfcRoamingMode;
+ private Preference mUpdateAddress;
+ private TextView mEmptyView;
+
+ private boolean mValidListener = false;
+ private boolean mEditableWfcMode = true;
+ private boolean mEditableWfcRoamingMode = true;
+
+ private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+ private ImsManager mImsManager;
+
+ private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
+ /*
+ * Enable/disable controls when in/out of a call and depending on
+ * TTY mode and TTY support over VoLTE.
+ * @see android.telephony.PhoneStateListener#onCallStateChanged(int,
+ * java.lang.String)
+ */
+ @Override
+ public void onCallStateChanged(int state, String incomingNumber) {
+ final SettingsActivity activity = (SettingsActivity) getActivity();
+ boolean isNonTtyOrTtyOnVolteEnabled = mImsManager.isNonTtyOrTtyOnVolteEnabled();
+ boolean isWfcEnabled = mSwitchBar.isChecked()
+ && isNonTtyOrTtyOnVolteEnabled;
+
+ mSwitchBar.setEnabled((state == TelephonyManager.CALL_STATE_IDLE)
+ && isNonTtyOrTtyOnVolteEnabled);
+
+ boolean isWfcModeEditable = true;
+ boolean isWfcRoamingModeEditable = false;
+ final CarrierConfigManager configManager = (CarrierConfigManager)
+ activity.getSystemService(Context.CARRIER_CONFIG_SERVICE);
+ if (configManager != null) {
+ PersistableBundle b = configManager.getConfigForSubId(mSubId);
+ if (b != null) {
+ isWfcModeEditable = b.getBoolean(
+ CarrierConfigManager.KEY_EDITABLE_WFC_MODE_BOOL);
+ isWfcRoamingModeEditable = b.getBoolean(
+ CarrierConfigManager.KEY_EDITABLE_WFC_ROAMING_MODE_BOOL);
+ }
+ }
+
+ Preference pref = getPreferenceScreen().findPreference(BUTTON_WFC_MODE);
+ if (pref != null) {
+ pref.setEnabled(isWfcEnabled && isWfcModeEditable
+ && (state == TelephonyManager.CALL_STATE_IDLE));
+ }
+ Preference pref_roam =
+ getPreferenceScreen().findPreference(BUTTON_WFC_ROAMING_MODE);
+ if (pref_roam != null) {
+ pref_roam.setEnabled(isWfcEnabled && isWfcRoamingModeEditable
+ && (state == TelephonyManager.CALL_STATE_IDLE));
+ }
+ }
+ };
+
+ @Override
+ protected int getHelpResource() {
+ // Helper resource is already defined in the container fragment.
+ return 0;
+ }
+
+ private final OnPreferenceClickListener mUpdateAddressListener =
+ new OnPreferenceClickListener() {
+ /*
+ * Launch carrier emergency address managemnent activity
+ */
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ Intent carrierAppIntent = getCarrierActivityIntent();
+ if (carrierAppIntent != null) {
+ carrierAppIntent.putExtra(EXTRA_LAUNCH_CARRIER_APP, LAUCH_APP_UPDATE);
+ startActivity(carrierAppIntent);
+ }
+ return true;
+ }
+ };
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ final SettingsActivity activity = (SettingsActivity) getActivity();
+
+ mEmptyView = (TextView) getView().findViewById(android.R.id.empty);
+ setEmptyView(mEmptyView);
+ String emptyViewText = activity.getString(R.string.wifi_calling_off_explanation)
+ + activity.getString(R.string.wifi_calling_off_explanation_2);
+ mEmptyView.setText(emptyViewText);
+
+ mSwitchBar = getView().findViewById(R.id.switch_bar);
+ mSwitchBar.show();
+ mSwitch = mSwitchBar.getSwitch();
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ mSwitchBar.hide();
+ }
+
+ private void showAlert(Intent intent) {
+ Context context = getActivity();
+
+ CharSequence title = intent.getCharSequenceExtra(Phone.EXTRA_KEY_ALERT_TITLE);
+ CharSequence message = intent.getCharSequenceExtra(Phone.EXTRA_KEY_ALERT_MESSAGE);
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ builder.setMessage(message)
+ .setTitle(title)
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .setPositiveButton(android.R.string.ok, null);
+ AlertDialog dialog = builder.create();
+ dialog.show();
+ }
+
+ private IntentFilter mIntentFilter;
+
+ private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action.equals(ImsManager.ACTION_IMS_REGISTRATION_ERROR)) {
+ // If this fragment is active then we are immediately
+ // showing alert on screen. There is no need to add
+ // notification in this case.
+ //
+ // In order to communicate to ImsPhone that it should
+ // not show notification, we are changing result code here.
+ setResultCode(Activity.RESULT_CANCELED);
+
+ // UX requirement is to disable WFC in case of "permanent" registration failures.
+ mSwitch.setChecked(false);
+
+ showAlert(intent);
+ }
+ }
+ };
+
+ @Override
+ public int getMetricsCategory() {
+ return MetricsEvent.WIFI_CALLING_FOR_SUB;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ addPreferencesFromResource(R.xml.wifi_calling_settings);
+
+ // SubId should always be specified when creating this fragment. Either through
+ // fragment.setArguments() or through savedInstanceState.
+ if (getArguments() != null && getArguments().containsKey(FRAGMENT_BUNDLE_SUBID))
+ {
+ mSubId = getArguments().getInt(FRAGMENT_BUNDLE_SUBID);
+ } else if (savedInstanceState != null) {
+ mSubId = savedInstanceState.getInt(
+ FRAGMENT_BUNDLE_SUBID, SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+ }
+
+ mImsManager = ImsManager.getInstance(
+ getActivity(), SubscriptionManager.getPhoneId(mSubId));
+
+ mButtonWfcMode = (ListPreference) findPreference(BUTTON_WFC_MODE);
+ mButtonWfcMode.setOnPreferenceChangeListener(this);
+
+ mButtonWfcRoamingMode = (ListPreference) findPreference(BUTTON_WFC_ROAMING_MODE);
+ mButtonWfcRoamingMode.setOnPreferenceChangeListener(this);
+
+ mUpdateAddress = (Preference) findPreference(PREFERENCE_EMERGENCY_ADDRESS);
+ mUpdateAddress.setOnPreferenceClickListener(mUpdateAddressListener);
+
+ mIntentFilter = new IntentFilter();
+ mIntentFilter.addAction(ImsManager.ACTION_IMS_REGISTRATION_ERROR);
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ outState.putInt(FRAGMENT_BUNDLE_SUBID, mSubId);
+ super.onSaveInstanceState(outState);
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+
+ View view = inflater.inflate(
+ R.layout.wifi_calling_settings_preferences, container, false);
+
+ final ViewGroup prefs_container = view.findViewById(R.id.prefs_container);
+ Utils.prepareCustomPreferencesList(container, view, prefs_container, false);
+ View prefs = super.onCreateView(inflater, prefs_container, savedInstanceState);
+ prefs_container.addView(prefs);
+
+ return view;
+ }
+
+ private void updateBody() {
+ CarrierConfigManager configManager = (CarrierConfigManager)
+ getSystemService(Context.CARRIER_CONFIG_SERVICE);
+ boolean isWifiOnlySupported = true;
+
+ if (configManager != null) {
+ PersistableBundle b = configManager.getConfigForSubId(mSubId);
+ if (b != null) {
+ mEditableWfcMode = b.getBoolean(
+ CarrierConfigManager.KEY_EDITABLE_WFC_MODE_BOOL);
+ mEditableWfcRoamingMode = b.getBoolean(
+ CarrierConfigManager.KEY_EDITABLE_WFC_ROAMING_MODE_BOOL);
+ isWifiOnlySupported = b.getBoolean(
+ CarrierConfigManager.KEY_CARRIER_WFC_SUPPORTS_WIFI_ONLY_BOOL, true);
+ }
+ }
+
+ if (!isWifiOnlySupported) {
+ mButtonWfcMode.setEntries(R.array.wifi_calling_mode_choices_without_wifi_only);
+ mButtonWfcMode.setEntryValues(R.array.wifi_calling_mode_values_without_wifi_only);
+ mButtonWfcRoamingMode.setEntries(
+ R.array.wifi_calling_mode_choices_v2_without_wifi_only);
+ mButtonWfcRoamingMode.setEntryValues(
+ R.array.wifi_calling_mode_values_without_wifi_only);
+ }
+
+
+ // NOTE: Buttons will be enabled/disabled in mPhoneStateListener
+ boolean wfcEnabled = mImsManager.isWfcEnabledByUser()
+ && mImsManager.isNonTtyOrTtyOnVolteEnabled();
+ mSwitch.setChecked(wfcEnabled);
+ int wfcMode = mImsManager.getWfcMode(false);
+ int wfcRoamingMode = mImsManager.getWfcMode(true);
+ mButtonWfcMode.setValue(Integer.toString(wfcMode));
+ mButtonWfcRoamingMode.setValue(Integer.toString(wfcRoamingMode));
+ updateButtonWfcMode(wfcEnabled, wfcMode, wfcRoamingMode);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ final Context context = getActivity();
+
+ updateBody();
+
+ if (mImsManager.isWfcEnabledByPlatform()) {
+ TelephonyManager tm =
+ (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
+ tm.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
+
+ mSwitchBar.addOnSwitchChangeListener(this);
+
+ mValidListener = true;
+ }
+
+ context.registerReceiver(mIntentReceiver, mIntentFilter);
+
+ Intent intent = getActivity().getIntent();
+ if (intent.getBooleanExtra(Phone.EXTRA_KEY_ALERT_SHOW, false)) {
+ showAlert(intent);
+ }
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+
+ final Context context = getActivity();
+
+ if (mValidListener) {
+ mValidListener = false;
+
+ TelephonyManager tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
+ tm.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
+
+ mSwitchBar.removeOnSwitchChangeListener(this);
+ }
+
+ context.unregisterReceiver(mIntentReceiver);
+ }
+
+ /**
+ * Listens to the state change of the switch.
+ */
+ @Override
+ public void onSwitchChanged(Switch switchView, boolean isChecked) {
+ Log.d(TAG, "onSwitchChanged(" + isChecked + ")");
+
+ if (!isChecked) {
+ updateWfcMode(false);
+ return;
+ }
+
+ // Call address management activity before turning on WFC
+ Intent carrierAppIntent = getCarrierActivityIntent();
+ if (carrierAppIntent != null) {
+ carrierAppIntent.putExtra(EXTRA_LAUNCH_CARRIER_APP, LAUCH_APP_ACTIVATE);
+ startActivityForResult(carrierAppIntent, REQUEST_CHECK_WFC_EMERGENCY_ADDRESS);
+ } else {
+ updateWfcMode(true);
+ }
+ }
+
+ /*
+ * Get the Intent to launch carrier emergency address management activity.
+ * Return null when no activity found.
+ */
+ private Intent getCarrierActivityIntent() {
+ // Retrive component name from carrier config
+ CarrierConfigManager configManager =
+ getActivity().getSystemService(CarrierConfigManager.class);
+ if (configManager == null) return null;
+
+ PersistableBundle bundle = configManager.getConfigForSubId(mSubId);
+ if (bundle == null) return null;
+
+ String carrierApp = bundle.getString(
+ CarrierConfigManager.KEY_WFC_EMERGENCY_ADDRESS_CARRIER_APP_STRING);
+ if (TextUtils.isEmpty(carrierApp)) return null;
+
+ ComponentName componentName = ComponentName.unflattenFromString(carrierApp);
+ if (componentName == null) return null;
+
+ // Build and return intent
+ Intent intent = new Intent();
+ intent.setComponent(componentName);
+ return intent;
+ }
+
+ /*
+ * Turn on/off WFC mode with ImsManager and update UI accordingly
+ */
+ private void updateWfcMode(boolean wfcEnabled) {
+ Log.i(TAG, "updateWfcMode(" + wfcEnabled + ")");
+ mImsManager.setWfcSetting(wfcEnabled);
+
+ int wfcMode = mImsManager.getWfcMode(false);
+ int wfcRoamingMode = mImsManager.getWfcMode(true);
+ updateButtonWfcMode(wfcEnabled, wfcMode, wfcRoamingMode);
+ if (wfcEnabled) {
+ mMetricsFeatureProvider.action(getActivity(), getMetricsCategory(), wfcMode);
+ } else {
+ mMetricsFeatureProvider.action(getActivity(), getMetricsCategory(), -1);
+ }
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+
+ final Context context = getActivity();
+
+ if (requestCode == REQUEST_CHECK_WFC_EMERGENCY_ADDRESS) {
+ Log.d(TAG, "WFC emergency address activity result = " + resultCode);
+
+ if (resultCode == Activity.RESULT_OK) {
+ updateWfcMode(true);
+ }
+ }
+ }
+
+ private void updateButtonWfcMode(boolean wfcEnabled,
+ int wfcMode, int wfcRoamingMode) {
+ mButtonWfcMode.setSummary(getWfcModeSummary(wfcMode));
+ mButtonWfcMode.setEnabled(wfcEnabled && mEditableWfcMode);
+ // mButtonWfcRoamingMode.setSummary is not needed; summary is just selected value.
+ mButtonWfcRoamingMode.setEnabled(wfcEnabled && mEditableWfcRoamingMode);
+
+ final PreferenceScreen preferenceScreen = getPreferenceScreen();
+ boolean updateAddressEnabled = (getCarrierActivityIntent() != null);
+ if (wfcEnabled) {
+ if (mEditableWfcMode) {
+ preferenceScreen.addPreference(mButtonWfcMode);
+ } else {
+ // Don't show WFC (home) preference if it's not editable.
+ preferenceScreen.removePreference(mButtonWfcMode);
+ }
+ if (mEditableWfcRoamingMode) {
+ preferenceScreen.addPreference(mButtonWfcRoamingMode);
+ } else {
+ // Don't show WFC roaming preference if it's not editable.
+ preferenceScreen.removePreference(mButtonWfcRoamingMode);
+ }
+ if (updateAddressEnabled) {
+ preferenceScreen.addPreference(mUpdateAddress);
+ } else {
+ preferenceScreen.removePreference(mUpdateAddress);
+ }
+ } else {
+ preferenceScreen.removePreference(mButtonWfcMode);
+ preferenceScreen.removePreference(mButtonWfcRoamingMode);
+ preferenceScreen.removePreference(mUpdateAddress);
+ }
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ if (preference == mButtonWfcMode) {
+ Log.d(TAG, "onPreferenceChange mButtonWfcMode " + newValue);
+ mButtonWfcMode.setValue((String) newValue);
+ int buttonMode = Integer.valueOf((String) newValue);
+ int currentWfcMode = mImsManager.getWfcMode(false);
+ if (buttonMode != currentWfcMode) {
+ mImsManager.setWfcMode(buttonMode, false);
+ mButtonWfcMode.setSummary(getWfcModeSummary(buttonMode));
+ mMetricsFeatureProvider.action(getActivity(), getMetricsCategory(), buttonMode);
+ }
+ if (!mEditableWfcRoamingMode) {
+ int currentWfcRoamingMode = mImsManager.getWfcMode(true);
+ if (buttonMode != currentWfcRoamingMode) {
+ mImsManager.setWfcMode(buttonMode, true);
+ // mButtonWfcRoamingMode.setSummary is not needed; summary is selected value
+ }
+ }
+ } else if (preference == mButtonWfcRoamingMode) {
+ mButtonWfcRoamingMode.setValue((String) newValue);
+ int buttonMode = Integer.valueOf((String) newValue);
+ int currentMode = mImsManager.getWfcMode(true);
+ if (buttonMode != currentMode) {
+ mImsManager.setWfcMode(buttonMode, true);
+ // mButtonWfcRoamingMode.setSummary is not needed; summary is just selected value.
+ mMetricsFeatureProvider.action(getActivity(), getMetricsCategory(), buttonMode);
+ }
+ }
+ return true;
+ }
+
+ private int getWfcModeSummary(int wfcMode) {
+ int resId = com.android.internal.R.string.wifi_calling_off_summary;
+ if (mImsManager.isWfcEnabledByUser()) {
+ switch (wfcMode) {
+ case ImsConfig.WfcModeFeatureValueConstants.WIFI_ONLY:
+ resId = com.android.internal.R.string.wfc_mode_wifi_only_summary;
+ break;
+ case ImsConfig.WfcModeFeatureValueConstants.CELLULAR_PREFERRED:
+ resId = com.android.internal.R.string.wfc_mode_cellular_preferred_summary;
+ break;
+ case ImsConfig.WfcModeFeatureValueConstants.WIFI_PREFERRED:
+ resId = com.android.internal.R.string.wfc_mode_wifi_preferred_summary;
+ break;
+ default:
+ Log.e(TAG, "Unexpected WFC mode value: " + wfcMode);
+ }
+ }
+ return resId;
+ }
+}
diff --git a/src/com/android/settings/applications/ProcessStatsBase.java b/src/com/android/settings/applications/ProcessStatsBase.java
index 3f66789..b98d0ba 100644
--- a/src/com/android/settings/applications/ProcessStatsBase.java
+++ b/src/com/android/settings/applications/ProcessStatsBase.java
@@ -44,7 +44,7 @@
// smaller than the actual time selected instead of bumping up to 3 hours
// beyond it.
private static final long DURATION_QUANTUM = ProcessStats.COMMIT_PERIOD;
- protected static long[] sDurations = new long[] {
+ public static long[] sDurations = new long[] {
3 * 60 * 60 * 1000 - DURATION_QUANTUM / 2, 6 * 60 *60 * 1000 - DURATION_QUANTUM / 2,
12 * 60 * 60 * 1000 - DURATION_QUANTUM / 2, 24 * 60 * 60 * 1000 - DURATION_QUANTUM / 2
};
diff --git a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
index d532136..83f395f 100644
--- a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
+++ b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
@@ -352,6 +352,7 @@
Activity activity, Lifecycle lifecycle, DevelopmentSettingsDashboardFragment fragment,
BluetoothA2dpConfigStore bluetoothA2dpConfigStore) {
final List<AbstractPreferenceController> controllers = new ArrayList<>();
+ controllers.add(new MemoryUsagePreferenceController(context));
controllers.add(new BugReportPreferenceControllerV2(context));
controllers.add(new LocalBackupPasswordPreferenceController(context));
controllers.add(new StayAwakePreferenceController(context, lifecycle));
diff --git a/src/com/android/settings/development/MemoryUsagePreferenceController.java b/src/com/android/settings/development/MemoryUsagePreferenceController.java
new file mode 100644
index 0000000..1b589fd
--- /dev/null
+++ b/src/com/android/settings/development/MemoryUsagePreferenceController.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2017 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.development;
+
+import android.content.Context;
+import android.support.annotation.VisibleForTesting;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+import android.text.format.Formatter;
+
+import com.android.settings.R;
+import com.android.settings.applications.ProcStatsData;
+import com.android.settings.applications.ProcessStatsBase;
+import com.android.settings.core.PreferenceControllerMixin;
+import com.android.settingslib.development.DeveloperOptionsPreferenceController;
+
+public class MemoryUsagePreferenceController extends DeveloperOptionsPreferenceController implements
+ PreferenceControllerMixin {
+
+ private static final String MEMORY_USAGE_KEY = "memory";
+
+ private Preference mPreference;
+ private ProcStatsData mProcStatsData;
+
+ public MemoryUsagePreferenceController(Context context) {
+ super(context);
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return MEMORY_USAGE_KEY;
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+
+ mPreference = screen.findPreference(getPreferenceKey());
+ mProcStatsData = getProcStatsData();
+ setDuration();
+ }
+
+ @Override
+ public void updateState(Preference preference) {
+ mProcStatsData.refreshStats(true);
+ final ProcStatsData.MemInfo memInfo = mProcStatsData.getMemInfo();
+ final String usedResult = Formatter.formatShortFileSize(mContext,
+ (long) memInfo.realUsedRam);
+ final String totalResult = Formatter.formatShortFileSize(mContext,
+ (long) memInfo.realTotalRam);
+ mPreference.setSummary(mContext.getString(R.string.memory_summary,
+ usedResult, totalResult));
+ }
+
+ @VisibleForTesting
+ void setDuration() {
+ mProcStatsData.setDuration(ProcessStatsBase.sDurations[0] /* 3 hours */);
+ }
+
+ @VisibleForTesting
+ ProcStatsData getProcStatsData() {
+ return new ProcStatsData(mContext, false);
+ }
+}
diff --git a/src/com/android/settings/search/SearchIndexableResources.java b/src/com/android/settings/search/SearchIndexableResources.java
index 7252e2d..89924bb 100644
--- a/src/com/android/settings/search/SearchIndexableResources.java
+++ b/src/com/android/settings/search/SearchIndexableResources.java
@@ -86,7 +86,6 @@
import com.android.settings.users.UserSettings;
import com.android.settings.wallpaper.WallpaperTypeSettings;
import com.android.settings.wifi.ConfigureWifiSettings;
-import com.android.settings.wifi.SavedAccessPointsWifiSettings;
import com.android.settings.wifi.WifiSettings;
import java.util.Collection;
@@ -129,7 +128,6 @@
addIndex(WifiSettings.class);
addIndex(NetworkDashboardFragment.class);
addIndex(ConfigureWifiSettings.class);
- addIndex(SavedAccessPointsWifiSettings.class);
addIndex(BluetoothSettings.class);
addIndex(SimSettings.class);
addIndex(DataUsageSummary.class);
diff --git a/src/com/android/settings/widget/SlidingTabLayout.java b/src/com/android/settings/widget/SlidingTabLayout.java
new file mode 100644
index 0000000..7099646
--- /dev/null
+++ b/src/com/android/settings/widget/SlidingTabLayout.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2016 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.widget;
+
+import android.content.Context;
+import android.support.v4.view.PagerAdapter;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.settings.R;
+
+/**
+ * To be used with ViewPager to provide a tab indicator component which give constant feedback as
+ * to the user's scroll progress.
+ */
+public final class SlidingTabLayout extends FrameLayout implements View.OnClickListener {
+
+ private final LinearLayout mTitleView;
+ private final View mIndicatorView;
+ private final LayoutInflater mLayoutInflater;
+
+ private RtlCompatibleViewPager mViewPager;
+ private int mSelectedPosition;
+ private float mSelectionOffset;
+
+ public SlidingTabLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mLayoutInflater = LayoutInflater.from(context);
+ mTitleView = new LinearLayout(context);
+ mTitleView.setGravity(Gravity.CENTER_HORIZONTAL);
+ mIndicatorView = mLayoutInflater.inflate(R.layout.sliding_tab_indicator_view, this, false);
+
+ addView(mTitleView, LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
+ addView(mIndicatorView, mIndicatorView.getLayoutParams());
+ }
+
+ /**
+ * Sets the associated view pager. Note that the assumption here is that the pager content
+ * (number of tabs and tab titles) does not change after this call has been made.
+ */
+ public void setViewPager(RtlCompatibleViewPager viewPager) {
+ mTitleView.removeAllViews();
+
+ mViewPager = viewPager;
+ if (viewPager != null) {
+ viewPager.addOnPageChangeListener(new InternalViewPagerListener());
+ populateTabStrip();
+ }
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ final int titleCount = mTitleView.getChildCount();
+ if (titleCount > 0) {
+ final int width = MeasureSpec.makeMeasureSpec(
+ mTitleView.getMeasuredWidth() / titleCount, MeasureSpec.EXACTLY);
+ final int height = MeasureSpec.makeMeasureSpec(
+ mIndicatorView.getMeasuredHeight(), MeasureSpec.EXACTLY);
+ mIndicatorView.measure(width, height);
+ }
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ if (mTitleView.getChildCount() > 0) {
+ final int indicatorBottom = getMeasuredHeight();
+ final int indicatorHeight = mIndicatorView.getMeasuredHeight();
+ final int indicatorWidth = mIndicatorView.getMeasuredWidth();
+ final int totalWidth = getMeasuredWidth();
+ final int leftPadding = getPaddingLeft();
+ final int rightPadding = getPaddingRight();
+
+ mTitleView.layout(leftPadding, 0, mTitleView.getMeasuredWidth() + rightPadding,
+ mTitleView.getMeasuredHeight());
+ // IndicatorView should start on the right when RTL mode is enabled
+ if (isRtlMode()) {
+ mIndicatorView.layout(totalWidth - indicatorWidth,
+ indicatorBottom - indicatorHeight, totalWidth,
+ indicatorBottom);
+ } else {
+ mIndicatorView.layout(0, indicatorBottom - indicatorHeight,
+ indicatorWidth, indicatorBottom);
+ }
+ }
+ }
+
+ @Override
+ public void onClick(View v) {
+ final int titleCount = mTitleView.getChildCount();
+ for (int i = 0; i < titleCount; i++) {
+ if (v == mTitleView.getChildAt(i)) {
+ mViewPager.setCurrentItem(i);
+ return;
+ }
+ }
+ }
+
+ private void onViewPagerPageChanged(int position, float positionOffset) {
+ mSelectedPosition = position;
+ mSelectionOffset = positionOffset;
+ // Translation should be reversed in RTL mode
+ final int leftIndicator = isRtlMode() ? -getIndicatorLeft() : getIndicatorLeft();
+ mIndicatorView.setTranslationX(leftIndicator);
+ }
+
+ private void populateTabStrip() {
+ final PagerAdapter adapter = mViewPager.getAdapter();
+
+ for (int i = 0; i < adapter.getCount(); i++) {
+ final TextView tabTitleView = (TextView) mLayoutInflater.inflate(
+ R.layout.sliding_tab_title_view, mTitleView, false);
+
+ tabTitleView.setText(adapter.getPageTitle(i));
+ tabTitleView.setOnClickListener(this);
+
+ mTitleView.addView(tabTitleView);
+ tabTitleView.setSelected(i == mViewPager.getCurrentItem());
+ }
+ }
+
+ private int getIndicatorLeft() {
+ View selectedTitle = mTitleView.getChildAt(mSelectedPosition);
+ int left = selectedTitle.getLeft();
+ if (mSelectionOffset > 0f && mSelectedPosition < (getChildCount() - 1)) {
+ View nextTitle = mTitleView.getChildAt(mSelectedPosition + 1);
+ left = (int) (mSelectionOffset * nextTitle.getLeft()
+ + (1.0f - mSelectionOffset) * left);
+ }
+ return left;
+ }
+
+ private boolean isRtlMode() {
+ return getLayoutDirection() == LAYOUT_DIRECTION_RTL;
+ }
+
+ private final class InternalViewPagerListener implements
+ RtlCompatibleViewPager.OnPageChangeListener {
+ private int mScrollState;
+
+ @Override
+ public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
+ final int titleCount = mTitleView.getChildCount();
+ if ((titleCount == 0) || (position < 0) || (position >= titleCount)) {
+ return;
+ }
+ onViewPagerPageChanged(position, positionOffset);
+ }
+
+ @Override
+ public void onPageScrollStateChanged(int state) {
+ mScrollState = state;
+ }
+
+ @Override
+ public void onPageSelected(int position) {
+ position = mViewPager.getRtlAwareIndex(position);
+ if (mScrollState == RtlCompatibleViewPager.SCROLL_STATE_IDLE) {
+ onViewPagerPageChanged(position, 0f);
+ }
+ final int titleCount = mTitleView.getChildCount();
+ for (int i = 0; i < titleCount; i++) {
+ mTitleView.getChildAt(i).setSelected(position == i);
+ }
+ }
+ }
+}
diff --git a/tests/robotests/assets/grandfather_not_implementing_indexable b/tests/robotests/assets/grandfather_not_implementing_indexable
index 091ba12..91a6649 100644
--- a/tests/robotests/assets/grandfather_not_implementing_indexable
+++ b/tests/robotests/assets/grandfather_not_implementing_indexable
@@ -74,6 +74,7 @@
com.android.settings.SecuritySettings$SecuritySubSettings
com.android.settings.PrivacySettings
com.android.settings.WifiCallingSettings
+com.android.settings.WifiCallingSettingsForSub
com.android.settings.password.SetupChooseLockGeneric$SetupChooseLockGenericFragment
com.android.settings.SetupRedactionInterstitial$SetupRedactionInterstitialFragment
com.android.settings.TrustAgentSettings
diff --git a/tests/robotests/src/com/android/settings/development/MemoryUsagePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/MemoryUsagePreferenceControllerTest.java
new file mode 100644
index 0000000..a949eef
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/development/MemoryUsagePreferenceControllerTest.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2017 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.development;
+
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+
+import com.android.settings.TestConfig;
+import com.android.settings.applications.ProcStatsData;
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class MemoryUsagePreferenceControllerTest {
+
+ @Mock
+ private Preference mPreference;
+ @Mock
+ private PreferenceScreen mScreen;
+ @Mock
+ private ProcStatsData mProcStatsData;
+ @Mock
+ private ProcStatsData.MemInfo mMemInfo;
+
+ private Context mContext;
+ private MemoryUsagePreferenceController mController;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mContext = RuntimeEnvironment.application;
+ mController = spy(new MemoryUsagePreferenceController(mContext));
+ doReturn(mProcStatsData).when(mController).getProcStatsData();
+ doNothing().when(mController).setDuration();
+ when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference);
+ when(mProcStatsData.getMemInfo()).thenReturn(mMemInfo);
+ mController.displayPreference(mScreen);
+ }
+
+ @Test
+ public void updateState_shouldUpdatePreferenceSummary() {
+ mController.updateState(mPreference);
+
+ verify(mPreference).setSummary(anyString());
+ }
+}
diff --git a/tests/unit/Android.mk b/tests/unit/Android.mk
index a91dcb1..ec0f0b6 100644
--- a/tests/unit/Android.mk
+++ b/tests/unit/Android.mk
@@ -5,7 +5,7 @@
LOCAL_MODULE_TAGS := tests
LOCAL_CERTIFICATE := platform
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner telephony-common ims-common
LOCAL_STATIC_JAVA_LIBRARIES := \
android-support-test \
diff --git a/tests/unit/src/com/android/settings/UniquePreferenceTest.java b/tests/unit/src/com/android/settings/UniquePreferenceTest.java
new file mode 100644
index 0000000..2236b94
--- /dev/null
+++ b/tests/unit/src/com/android/settings/UniquePreferenceTest.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2017 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;
+
+import static junit.framework.Assert.fail;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.provider.SearchIndexableResource;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Xml;
+
+import com.android.settings.search.DatabaseIndexingUtils;
+import com.android.settings.search.Indexable;
+import com.android.settings.search.SearchIndexableResources;
+import com.android.settings.search.XmlParserUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+@RunWith(AndroidJUnit4.class)
+@MediumTest
+public class UniquePreferenceTest {
+
+ private static final String TAG = "UniquePreferenceTest";
+ private static final List<String> SUPPORTED_PREF_TYPES = Arrays.asList(
+ "Preference", "PreferenceCategory", "PreferenceScreen");
+
+ private Context mContext;
+
+
+ @Before
+ public void setUp() {
+ mContext = InstrumentationRegistry.getTargetContext();
+ }
+
+ /**
+ * All preferences should have their unique key. It's especially important for many parts of
+ * Settings to work properly: we assume pref keys are unique in displaying, search ranking,\
+ * search result suppression, and many other areas.
+ * <p/>
+ * So in this test we are checking preferences participating in search.
+ * <p/>
+ * Note: Preference is not limited to just <Preference/> object. Everything in preference xml
+ * should have a key.
+ */
+ @Test
+ public void allPreferencesShouldHaveUniqueKey()
+ throws IOException, XmlPullParserException, Resources.NotFoundException {
+ final Set<String> uniqueKeys = new HashSet<>();
+ final Set<String> nullKeyClasses = new HashSet<>();
+ final Set<String> duplicatedKeys = new HashSet<>();
+ for (SearchIndexableResource sir : SearchIndexableResources.values()) {
+ verifyPreferenceIdInXml(uniqueKeys, duplicatedKeys, nullKeyClasses, sir);
+ }
+
+ if (!nullKeyClasses.isEmpty()) {
+ final StringBuilder nullKeyErrors = new StringBuilder()
+ .append("Each preference must have a key, ")
+ .append("the following classes have pref without keys:\n");
+ for (String c : nullKeyClasses) {
+ nullKeyErrors.append(c).append("\n");
+ }
+ fail(nullKeyErrors.toString());
+ }
+
+ if (!duplicatedKeys.isEmpty()) {
+ final StringBuilder dupeKeysError = new StringBuilder(
+ "The following keys are not unique\n");
+ for (String c : duplicatedKeys) {
+ dupeKeysError.append(c).append("\n");
+ }
+ fail(dupeKeysError.toString());
+ }
+ }
+
+ private void verifyPreferenceIdInXml(Set<String> uniqueKeys, Set<String> duplicatedKeys,
+ Set<String> nullKeyClasses, SearchIndexableResource page)
+ throws IOException, XmlPullParserException, Resources.NotFoundException {
+ final Class<?> clazz = DatabaseIndexingUtils.getIndexableClass(page.className);
+
+ final Indexable.SearchIndexProvider provider =
+ DatabaseIndexingUtils.getSearchIndexProvider(clazz);
+ final List<SearchIndexableResource> resourcesToIndex =
+ provider.getXmlResourcesToIndex(mContext, true);
+ if (resourcesToIndex == null) {
+ Log.d(TAG, page.className + "is not providing SearchIndexableResource, skipping");
+ return;
+ }
+
+ for (SearchIndexableResource sir : resourcesToIndex) {
+ if (sir.xmlResId <= 0) {
+ Log.d(TAG, page.className + " doesn't have a valid xml to index.");
+ continue;
+ }
+ final XmlResourceParser parser = mContext.getResources().getXml(sir.xmlResId);
+
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && type != XmlPullParser.START_TAG) {
+ // Parse next until start tag is found
+ }
+ final int outerDepth = parser.getDepth();
+
+ do {
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+ final String nodeName = parser.getName();
+ if (!SUPPORTED_PREF_TYPES.contains(nodeName) && !nodeName.endsWith("Preference")) {
+ continue;
+ }
+ final AttributeSet attrs = Xml.asAttributeSet(parser);
+ final String key = XmlParserUtils.getDataKey(mContext, attrs);
+ if (TextUtils.isEmpty(key)) {
+ Log.e(TAG, "Every preference must have an key; found null key"
+ + " in " + page.className
+ + " at " + parser.getPositionDescription());
+ nullKeyClasses.add(page.className);
+ continue;
+ }
+ if (uniqueKeys.contains(key)) {
+ Log.e(TAG, "Every preference key must unique; found " + nodeName
+ + " in " + page.className
+ + " at " + parser.getPositionDescription());
+ duplicatedKeys.add(key);
+ }
+ uniqueKeys.add(key);
+ } while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth));
+ }
+ }
+}
diff --git a/tests/unit/src/com/android/settings/utils/MockedServiceManager.java b/tests/unit/src/com/android/settings/utils/MockedServiceManager.java
new file mode 100644
index 0000000..ea04974
--- /dev/null
+++ b/tests/unit/src/com/android/settings/utils/MockedServiceManager.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2017 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.testutils;
+
+import android.os.IBinder;
+import android.os.ServiceManager;
+import android.util.Log;
+
+import java.lang.reflect.Field;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+
+// This class is for replacing existing system service with the mocked service.
+// Copied from CellBroadcastReceiver app.
+public final class MockedServiceManager {
+
+ private final String TAG = MockedServiceManager.class.getSimpleName();
+
+ private final HashMap<String, IBinder> mServiceManagerMockedServices = new HashMap<>();
+
+ private final HashMap<InstanceKey, Object> mOldInstances = new HashMap<>();
+
+ private final LinkedList<InstanceKey> mInstanceKeys = new LinkedList<>();
+
+ private static class InstanceKey {
+ final Class mClass;
+ final String mInstName;
+ final Object mObj;
+
+ InstanceKey(final Class c, final String instName, final Object obj) {
+ mClass = c;
+ mInstName = instName;
+ mObj = obj;
+ }
+
+ @Override
+ public int hashCode() {
+ return (mClass.getName().hashCode() * 31 + mInstName.hashCode()) * 31;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null || obj.getClass() != getClass()) {
+ return false;
+ }
+
+ InstanceKey other = (InstanceKey) obj;
+ return (other.mClass == mClass && other.mInstName.equals(mInstName)
+ && other.mObj == mObj);
+ }
+ }
+
+ public MockedServiceManager() throws Exception {
+ replaceInstance(ServiceManager.class, "sCache", null, mServiceManagerMockedServices);
+ }
+
+ public void replaceService(String key, IBinder binder) {
+ mServiceManagerMockedServices.put(key, binder);
+ }
+
+ public void restoreAllServices() throws Exception {
+ restoreInstances();
+ }
+
+ public synchronized void replaceInstance(final Class c, final String instanceName,
+ final Object obj, final Object newValue)
+ throws Exception {
+ Field field = c.getDeclaredField(instanceName);
+ field.setAccessible(true);
+
+ InstanceKey key = new InstanceKey(c, instanceName, obj);
+ if (!mOldInstances.containsKey(key)) {
+ mOldInstances.put(key, field.get(obj));
+ mInstanceKeys.add(key);
+ }
+ field.set(obj, newValue);
+ }
+
+ public synchronized void restoreInstances() throws Exception {
+ Iterator<InstanceKey> it = mInstanceKeys.descendingIterator();
+
+ while (it.hasNext()) {
+ InstanceKey key = it.next();
+ Field field = key.mClass.getDeclaredField(key.mInstName);
+ field.setAccessible(true);
+ field.set(key.mObj, mOldInstances.get(key));
+ }
+
+ mInstanceKeys.clear();
+ mOldInstances.clear();
+ }
+}
diff --git a/tests/unit/src/com/android/settings/wifi/WifiCallingSettingUiTest.java b/tests/unit/src/com/android/settings/wifi/WifiCallingSettingUiTest.java
new file mode 100644
index 0000000..16617d0
--- /dev/null
+++ b/tests/unit/src/com/android/settings/wifi/WifiCallingSettingUiTest.java
@@ -0,0 +1,299 @@
+/**
+ * Copyright (C) 2017 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.wifi;
+
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.action.ViewActions.click;
+import static android.support.test.espresso.assertion.ViewAssertions.doesNotExist;
+import static android.support.test.espresso.assertion.ViewAssertions.matches;
+import static android.support.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed;
+import static android.support.test.espresso.matcher.ViewMatchers.isSelected;
+import static android.support.test.espresso.matcher.ViewMatchers.withResourceName;
+import static android.support.test.espresso.matcher.ViewMatchers.withText;
+
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.anything;
+import static org.junit.Assert.assertEquals;
+import static org.junit.matchers.JUnitMatchers.containsString;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doReturn;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.Context;
+import android.content.Intent;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.espresso.NoMatchingViewException;
+import android.support.test.espresso.ViewInteraction;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.uiautomator.UiDevice;
+import android.telephony.SubscriptionInfo;
+
+import com.android.ims.ImsConfig;
+import com.android.ims.ImsManager;
+import com.android.internal.telephony.SubscriptionController;
+import com.android.settings.testutils.MockedServiceManager;
+
+import junit.framework.Assert;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class WifiCallingSettingUiTest {
+ private static final String SUBSCRIPTION0_NAME = "SUB0";
+ private static final String SUBSCRIPTION1_NAME = "SUB1";
+ private static final String WFC_MODE_TITLE = "Calling preference";
+ private static final String WFC_MODE_WIFI_ONLY = "Wi-Fi only";
+ private static final String WFC_MODE_WIFI_PREFERRED = "Wi-Fi preferred";
+ private static final String WFC_MODE_CELLULAR_PREFERRED = "Mobile preferred";
+
+ private Instrumentation mInstrumentation;
+ private Context mContext;
+ private UiDevice mDevice;
+ @Mock
+ SubscriptionController mSubscriptionController;
+ MockedServiceManager mMockedServiceManager;
+ protected HashMap<Integer, ImsManager> mImsManagerInstances = new HashMap<>();
+ List<SubscriptionInfo> mSils = new ArrayList();
+ @Mock
+ SubscriptionInfo mSubscriptionInfo0;
+ @Mock
+ SubscriptionInfo mSubscriptionInfo1;
+ @Mock
+ ImsManager mImsManager0;
+ @Mock
+ ImsManager mImsManager1;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ mContext = mInstrumentation.getTargetContext();
+ mDevice = UiDevice.getInstance(mInstrumentation);
+
+ mMockedServiceManager = new MockedServiceManager();
+ mMockedServiceManager.replaceService("isub", mSubscriptionController);
+
+ mMockedServiceManager.replaceInstance(
+ ImsManager.class, "sImsManagerInstances", null, mImsManagerInstances);
+ mMockedServiceManager.replaceInstance(
+ SubscriptionController.class, "sInstance", null, mSubscriptionController);
+ doReturn(mSubscriptionController)
+ .when(mSubscriptionController).queryLocalInterface(anyString());
+ mImsManagerInstances.put(0, mImsManager0);
+ mImsManagerInstances.put(1, mImsManager1);
+ doReturn(mSils).when(mSubscriptionController).getActiveSubscriptionInfoList(anyString());
+ doReturn(0).when(mSubscriptionController).getPhoneId(0);
+ doReturn(1).when(mSubscriptionController).getPhoneId(1);
+ doReturn(0).when(mSubscriptionInfo0).getSubscriptionId();
+ doReturn(1).when(mSubscriptionInfo1).getSubscriptionId();
+ doReturn(0).when(mSubscriptionInfo0).getSimSlotIndex();
+ doReturn(1).when(mSubscriptionInfo1).getSimSlotIndex();
+ doReturn(SUBSCRIPTION0_NAME).when(mSubscriptionInfo0).getDisplayName();
+ doReturn(SUBSCRIPTION1_NAME).when(mSubscriptionInfo1).getDisplayName();
+
+ doReturn(true).when(mImsManager0).isWfcEnabledByPlatform();
+ doReturn(true).when(mImsManager0).isNonTtyOrTtyOnVolteEnabled();
+ doReturn(true).when(mImsManager1).isWfcEnabledByPlatform();
+ doReturn(true).when(mImsManager1).isNonTtyOrTtyOnVolteEnabled();
+
+ mDevice.wakeUp();
+ mDevice.pressMenu();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mMockedServiceManager.restoreAllServices();
+ }
+
+ @Test
+ public void testSingleSimUi() throws InterruptedException {
+ configureSingleSim();
+ doReturn(true).when(mImsManager0).isWfcEnabledByUser();
+ doReturn(ImsConfig.WfcModeFeatureValueConstants.WIFI_PREFERRED)
+ .when(mImsManager0).getWfcMode();
+ doReturn(ImsConfig.WfcModeFeatureValueConstants.WIFI_PREFERRED)
+ .when(mImsManager0).getWfcMode(anyBoolean());
+
+ mInstrumentation.startActivitySync(createActivityIntent());
+
+ checkSingleSimUi();
+
+ try {
+ mDevice.setOrientationLeft();
+ } catch (Exception e) {
+ Assert.fail("Exception " + e);
+ }
+
+ // Re-check after rotation. Fragment should be recreated properly.
+ checkSingleSimUi();
+
+ try {
+ mDevice.setOrientationNatural();
+ } catch (Exception e) {
+ Assert.fail("Exception " + e);
+ }
+
+ // Re-check after rotation. Fragment should be resumed properly.
+ checkSingleSimUi();
+ }
+
+ private void checkSingleSimUi() {
+ assertEquals(false, checkExists(onView(withText(SUBSCRIPTION0_NAME))));
+ assertEquals(false, checkExists(onView(withText(SUBSCRIPTION1_NAME))));
+ assertEquals(true, checkExists(onView(withText(WFC_MODE_TITLE))));
+ assertEquals(true, checkExists(onView(withText(WFC_MODE_WIFI_PREFERRED))));
+ checkSwitchBarStatus(true, true);
+ checkEmptyViewStatus(false);
+ }
+
+ @Test
+ public void testNoValidSub() throws InterruptedException {
+ configureDualSim();
+ doReturn(false).when(mImsManager0).isWfcEnabledByPlatform();
+ doReturn(false).when(mImsManager0).isNonTtyOrTtyOnVolteEnabled();
+ doReturn(false).when(mImsManager1).isWfcEnabledByPlatform();
+ doReturn(false).when(mImsManager1).isNonTtyOrTtyOnVolteEnabled();
+ doReturn(false).when(mImsManager0).isWfcEnabledByUser();
+ doReturn(ImsConfig.WfcModeFeatureValueConstants.WIFI_PREFERRED)
+ .when(mImsManager0).getWfcMode();
+ doReturn(ImsConfig.WfcModeFeatureValueConstants.WIFI_PREFERRED)
+ .when(mImsManager0).getWfcMode(anyBoolean());
+
+ Activity activity = mInstrumentation.startActivitySync(createActivityIntent());
+
+ assertEquals(false, checkExists(onView(withText(SUBSCRIPTION0_NAME))));
+ assertEquals(false, checkExists(onView(withText(SUBSCRIPTION1_NAME))));
+ assertEquals(false, checkExists(onView(withText(WFC_MODE_TITLE))));
+
+ checkSwitchBarStatus(false, false);
+ checkEmptyViewStatus(false);
+ }
+
+ @Test
+ public void testWfcDisabled() throws InterruptedException {
+ configureSingleSim();
+ doReturn(false).when(mImsManager0).isWfcEnabledByUser();
+ doReturn(ImsConfig.WfcModeFeatureValueConstants.WIFI_PREFERRED)
+ .when(mImsManager0).getWfcMode();
+ doReturn(ImsConfig.WfcModeFeatureValueConstants.WIFI_PREFERRED)
+ .when(mImsManager0).getWfcMode(anyBoolean());
+
+ Activity activity = mInstrumentation.startActivitySync(createActivityIntent());
+
+ assertEquals(false, checkExists(onView(withText(SUBSCRIPTION0_NAME))));
+ assertEquals(false, checkExists(onView(withText(SUBSCRIPTION1_NAME))));
+ assertEquals(false, checkExists(onView(withText(WFC_MODE_TITLE))));
+
+ checkSwitchBarStatus(true, false);
+ checkEmptyViewStatus(true);
+ }
+
+ @Test
+ public void testDualSimUi() throws InterruptedException {
+ configureDualSim();
+ doReturn(true).when(mImsManager0).isWfcEnabledByUser();
+ doReturn(false).when(mImsManager1).isWfcEnabledByUser();
+ doReturn(ImsConfig.WfcModeFeatureValueConstants.CELLULAR_PREFERRED)
+ .when(mImsManager0).getWfcMode();
+ doReturn(ImsConfig.WfcModeFeatureValueConstants.CELLULAR_PREFERRED)
+ .when(mImsManager0).getWfcMode(anyBoolean());
+
+ mInstrumentation.startActivitySync(createActivityIntent());
+
+ assertEquals(true, checkExists(onView(withText(SUBSCRIPTION0_NAME))));
+ assertEquals(true, checkExists(onView(withText(SUBSCRIPTION1_NAME))));
+ assertEquals(true, checkExists(onView(withText(WFC_MODE_TITLE))));
+ assertEquals(true, checkExists(onView(withText(WFC_MODE_CELLULAR_PREFERRED))));
+
+ onView(withText(SUBSCRIPTION0_NAME)).check(matches(isSelected()));
+ checkSwitchBarStatus(true, true);
+ checkEmptyViewStatus(false);
+
+ // Switch to SUB1.
+ onView(withText(SUBSCRIPTION1_NAME)).perform(click());
+
+ checkSwitchBarStatus(true, false);
+ checkEmptyViewStatus(true);
+ onView(withText(SUBSCRIPTION1_NAME)).check(matches(isSelected()));
+ }
+
+ private boolean checkExists(ViewInteraction v) {
+ try {
+ v.check(matches(isCompletelyDisplayed()));
+ return true;
+ } catch (NoMatchingViewException e) {
+ return false;
+ }
+ }
+
+ private Intent createActivityIntent() {
+ Intent intent = new Intent(mContext,
+ com.android.settings.Settings.WifiCallingSettingsActivity.class);
+ intent.setPackage("com.android.settings");
+ intent.setAction("android.intent.action.MAIN");
+ return intent;
+ }
+
+ private void configureSingleSim() {
+ mSils.clear();
+ mSils.add(mSubscriptionInfo0);
+ }
+
+ private void configureDualSim() {
+ mSils.clear();
+ mSils.add(mSubscriptionInfo0);
+ mSils.add(mSubscriptionInfo1);
+ }
+
+ private void checkSwitchBarStatus(boolean shouldDisplay, boolean statusOn) {
+ if (shouldDisplay) {
+ try {
+ onView(allOf(withResourceName("switch_text"), isCompletelyDisplayed()))
+ .check(matches(withText(containsString(statusOn ? "On" : "Off"))));
+ } catch (Exception e) {
+ Assert.fail("Exception " + e);
+ }
+ } else {
+ onView(allOf(withResourceName("switch_text"), isCompletelyDisplayed()))
+ .check(doesNotExist());
+ }
+ }
+
+ private void checkEmptyViewStatus(boolean shouldDisplay) {
+ try {
+ if (!shouldDisplay) {
+ onView(allOf(withResourceName("empty"), isCompletelyDisplayed()))
+ .check(doesNotExist());
+ } else {
+ onView(allOf(withResourceName("empty"), isCompletelyDisplayed()))
+ .check(matches(anything()));
+ }
+ } catch (Exception e) {
+ Assert.fail("Exception " + e);
+ }
+ }
+}