Add activity for mobile network page

1. Extract the Tabhost to activity level, not fragment level. So
each mobile fragment has a constant subscriptionId
2. Use BottomNavigationView instead of TabHost

Bug: 114749736
Test: RunSettingsRoboTests
Change-Id: I43aebf2ec01a945697c6b2302ab750e715e4bfef
This commit is contained in:
jackqdyulei
2018-10-05 10:04:19 -07:00
parent 38f872fa1f
commit 106ae1d742
7 changed files with 334 additions and 156 deletions

View File

@@ -131,6 +131,13 @@
android:launchMode="singleTask">
</activity>
<activity android:name=".mobilenetwork.MobileSettingsActivity"
android:label="@string/network_settings_title"
android:theme="@style/Theme.Settings.Home"
android:launchMode="singleTask">
<!-- TODO(b/114749736): add intent filter here and disable the one in telephony -->
</activity>
<!-- Alias for launcher activity only, as this belongs to each profile. -->
<activity-alias android:name="Settings"
android:taskAffinity="com.android.settings.root"

View File

@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2018 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"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Toolbar
android:id="@+id/action_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:navigationContentDescription="@*android:string/action_bar_up_description"
android:theme="?android:attr/actionBarTheme"
style="?android:attr/actionBarStyle"/>
<FrameLayout
android:id="@+id/main_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"/>
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottom_nav"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_marginEnd="0dp"
android:layout_marginStart="0dp"
android:background="?android:attr/windowBackground"
app:itemIconTint="@color/bottom_navigation_colors"
app:itemTextColor="@color/bottom_navigation_colors"
app:menu="@menu/home_bottom_navigation"/>
</LinearLayout>

View File

@@ -33,14 +33,4 @@ public class AdvancedOptionsPreference extends Preference {
public AdvancedOptionsPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
setIcon(R.drawable.ic_expand_more);
setTitle(R.string.advanced_options_title);
TextView summary = (TextView) holder.findViewById(android.R.id.summary);
summary.setMaxLines(1);
}
}

View File

@@ -46,11 +46,7 @@ import android.telephony.TelephonyManager;
import android.telephony.euicc.EuiccManager;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TabHost;
import com.android.ims.ImsConfig;
import com.android.ims.ImsManager;
@@ -67,7 +63,6 @@ import com.android.settingslib.RestrictedLockUtilsInternal;
import com.android.settingslib.RestrictedSwitchPreference;
import com.android.settingslib.search.SearchIndexable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
@@ -174,9 +169,6 @@ public class MobileNetworkFragment extends DashboardFragment implements
private boolean mOkClicked;
private boolean mExpandAdvancedFields;
// We assume the the value returned by mTabHost.getCurrentTab() == slotId
private TabHost mTabHost;
//GsmUmts options and Cdma options
GsmUmtsOptions mGsmUmtsOptions;
CdmaOptions mCdmaOptions;
@@ -226,13 +218,6 @@ public class MobileNetworkFragment extends DashboardFragment implements
private final PhoneCallStateListener
mPhoneStateListener = new PhoneCallStateListener();
//TODO(b/114749736): figure out a way to update this fragment from intent
public void onIntentUpdate(Intent intent) {
if (!mUnavailable) {
updateCurrentTab(intent);
}
}
@Override
public int getMetricsCategory() {
//TODO(b/114749736): add metrics id for it
@@ -356,78 +341,7 @@ public class MobileNetworkFragment extends DashboardFragment implements
// Process preferences in activity only if its not destroyed
return;
}
int currentTab = 0;
if (DBG) log("initializeSubscriptions:+");
// Before updating the the active subscription list check
// if tab updating is needed as the list is changing.
List<SubscriptionInfo> sil = mSubscriptionManager.getActiveSubscriptionInfoList();
TabState state = isUpdateTabsNeeded(sil);
// Update to the active subscription list
mActiveSubInfos.clear();
if (sil != null) {
mActiveSubInfos.addAll(sil);
// If there is only 1 sim then currenTab should represent slot no. of the sim.
if (sil.size() == 1) {
currentTab = sil.get(0).getSimSlotIndex();
}
}
switch (state) {
case UPDATE: {
if (DBG) log("initializeSubscriptions: UPDATE");
currentTab = mTabHost != null ? mTabHost.getCurrentTab() : 0;
mTabHost = (TabHost) getActivity().findViewById(android.R.id.tabhost);
mTabHost.setup();
// Update the tabName. Since the mActiveSubInfos are in slot order
// we can iterate though the tabs and subscription info in one loop. But
// we need to handle the case where a slot may be empty.
Iterator<SubscriptionInfo> siIterator = mActiveSubInfos.listIterator();
SubscriptionInfo si = siIterator.hasNext() ? siIterator.next() : null;
for (int simSlotIndex = 0; simSlotIndex < mActiveSubInfos.size();
simSlotIndex++) {
String tabName;
if (si != null && si.getSimSlotIndex() == simSlotIndex) {
// Slot is not empty and we match
tabName = String.valueOf(si.getDisplayName());
si = siIterator.hasNext() ? siIterator.next() : null;
} else {
// Slot is empty, set name to unknown
tabName = getResources().getString(R.string.unknown);
}
if (DBG) {
log("initializeSubscriptions:tab=" + simSlotIndex + " name=" + tabName);
}
mTabHost.addTab(buildTabSpec(String.valueOf(simSlotIndex), tabName));
}
mTabHost.setOnTabChangedListener(mTabListener);
mTabHost.setCurrentTab(currentTab);
break;
}
case NO_TABS: {
if (DBG) log("initializeSubscriptions: NO_TABS");
if (mTabHost != null) {
mTabHost.clearAllTabs();
mTabHost = null;
}
break;
}
case DO_NOTHING: {
if (DBG) log("initializeSubscriptions: DO_NOTHING");
if (mTabHost != null) {
currentTab = mTabHost.getCurrentTab();
}
break;
}
}
updatePhone(currentTab);
updatePhone();
updateBody();
if (DBG) log("initializeSubscriptions:-");
}
@@ -465,60 +379,16 @@ public class MobileNetworkFragment extends DashboardFragment implements
return state;
}
private TabHost.OnTabChangeListener mTabListener = new TabHost.OnTabChangeListener() {
@Override
public void onTabChanged(String tabId) {
if (DBG) log("onTabChanged:");
// The User has changed tab; update the body.
updatePhone(Integer.parseInt(tabId));
updateBody();
}
};
private void updatePhone(int slotId) {
final SubscriptionInfo sir = mSubscriptionManager
.getActiveSubscriptionInfoForSimSlotIndex(slotId);
if (sir != null) {
mSubId = sir.getSubscriptionId();
Log.i(LOG_TAG, "updatePhone:- slotId=" + slotId + " sir=" + sir);
private void updatePhone() {
if (mSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
mImsMgr = ImsManager.getInstance(getContext(),
SubscriptionManager.getPhoneId(mSubId));
mTelephonyManager = new TelephonyManager(getContext(), mSubId);
if (mImsMgr == null) {
log("updatePhone :: Could not get ImsManager instance!");
} else if (DBG) {
log("updatePhone :: mImsMgr=" + mImsMgr);
}
} else {
// There is no active subscription in the given slot.
mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
}
mPhoneStateListener.updateSubscriptionId(mSubId);
}
private TabHost.TabContentFactory mEmptyTabContent = new TabHost.TabContentFactory() {
@Override
public View createTabContent(String tag) {
return new View(mTabHost.getContext());
}
};
private TabHost.TabSpec buildTabSpec(String tag, String title) {
return mTabHost.newTabSpec(tag).setIndicator(title).setContent(
mEmptyTabContent);
}
private void updateCurrentTab(Intent intent) {
int slotId = getSlotIdFromIntent(intent);
if (slotId >= 0 && mTabHost != null && mTabHost.getCurrentTab() != slotId) {
mTabHost.setCurrentTab(slotId);
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
@@ -591,23 +461,17 @@ public class MobileNetworkFragment extends DashboardFragment implements
// Initialize mActiveSubInfo
int max = mSubscriptionManager.getActiveSubscriptionInfoCountMax();
mActiveSubInfos = new ArrayList<SubscriptionInfo>(max);
mActiveSubInfos = mSubscriptionManager.getActiveSubscriptionInfoList();
mSubId = getArguments().getInt(MobileSettingsActivity.KEY_SUBSCRIPTION_ID,
SubscriptionManager.INVALID_SUBSCRIPTION_ID);
int currentTab = mTabHost != null ? mTabHost.getCurrentTab() : 0;
updatePhone(currentTab);
updatePhone();
if (hasActiveSubscriptions()) {
updateEnabledNetworksEntries();
}
Log.i(LOG_TAG, "onCreate:-");
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(com.android.internal.R.layout.common_tab_settings,
container, false);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
@@ -617,7 +481,6 @@ public class MobileNetworkFragment extends DashboardFragment implements
//TODO(b/114749736): migrate telephony_disallowed_preference_screen.xml
} else {
initializeSubscriptions();
updateCurrentTab(getActivity().getIntent());
}
}
@@ -714,7 +577,7 @@ public class MobileNetworkFragment extends DashboardFragment implements
}
private boolean hasActiveSubscriptions() {
return mActiveSubInfos.size() > 0;
return mSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID;
}
private void updateBodyBasicFields(FragmentActivity activity, PreferenceScreen prefSet,

View File

@@ -0,0 +1,126 @@
/*
* Copyright (C) 2018 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.mobilenetwork;
import android.content.Intent;
import android.os.Bundle;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.view.Menu;
import android.view.View;
import com.android.internal.util.CollectionUtils;
import com.android.settings.R;
import com.android.settings.core.SettingsBaseActivity;
import com.google.android.material.bottomnavigation.BottomNavigationView;
import java.util.List;
import androidx.annotation.VisibleForTesting;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
public class MobileSettingsActivity extends SettingsBaseActivity {
@VisibleForTesting
static final String MOBILE_SETTINGS_TAG = "mobile_settings:";
public static final String KEY_SUBSCRIPTION_ID = "key_subscription_id";
private SubscriptionManager mSubscriptionManager;
@VisibleForTesting
int mPrevSubscriptionId;
@VisibleForTesting
List<SubscriptionInfo> mSubscriptionInfos;
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
//TODO(b/114749736): update fragment by new intent, or at least make sure this page shows
// current tab for sim card
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mSubscriptionManager = getSystemService(SubscriptionManager.class);
mSubscriptionInfos = mSubscriptionManager.getActiveSubscriptionInfoList();
mPrevSubscriptionId = CollectionUtils.isEmpty(mSubscriptionInfos)
? SubscriptionManager.INVALID_SUBSCRIPTION_ID
: mSubscriptionInfos.get(0).getSubscriptionId();
setContentView(R.layout.mobile_settings_container);
updateBottomNavigationView();
if (savedInstanceState == null) {
switchFragment(new MobileNetworkFragment(), mPrevSubscriptionId);
}
}
@VisibleForTesting
void updateBottomNavigationView() {
final BottomNavigationView navigation = findViewById(R.id.bottom_nav);
if (CollectionUtils.size(mSubscriptionInfos) <= 1) {
navigation.setVisibility(View.GONE);
} else {
final Menu menu = navigation.getMenu();
menu.clear();
for (int i = 0, size = mSubscriptionInfos.size(); i < size; i++) {
final SubscriptionInfo subscriptionInfo = mSubscriptionInfos.get(i);
menu.add(0, subscriptionInfo.getSubscriptionId(), i,
subscriptionInfo.getDisplayName());
}
navigation.setOnNavigationItemSelectedListener(item -> {
switchFragment(new MobileNetworkFragment(), item.getItemId());
mPrevSubscriptionId = item.getItemId();
return true;
});
}
}
@VisibleForTesting
void switchFragment(Fragment fragment, int subscriptionId) {
final FragmentManager fragmentManager = getSupportFragmentManager();
final FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
final Bundle bundle = new Bundle();
bundle.putInt(KEY_SUBSCRIPTION_ID, subscriptionId);
final Fragment hideFragment = fragmentManager.findFragmentByTag(
buildFragmentTag(mPrevSubscriptionId));
if (hideFragment != null) {
fragmentTransaction.hide(hideFragment);
}
Fragment showFragment = fragmentManager.findFragmentByTag(buildFragmentTag(subscriptionId));
if (showFragment == null) {
fragment.setArguments(bundle);
fragmentTransaction.add(R.id.main_content, fragment, buildFragmentTag(subscriptionId));
} else {
showFragment.setArguments(bundle);
fragmentTransaction.show(showFragment);
}
fragmentTransaction.commit();
}
private String buildFragmentTag(int subscriptionId) {
return MOBILE_SETTINGS_TAG + subscriptionId;
}
}

View File

@@ -36,6 +36,7 @@ import androidx.preference.PreferenceScreen;
import com.android.settings.core.FeatureFlags;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.mobilenetwork.MobileSettingsActivity;
import com.android.settingslib.RestrictedLockUtilsInternal;
import com.android.settingslib.RestrictedPreference;
import com.android.settingslib.Utils;
@@ -146,7 +147,8 @@ public class MobileNetworkPreferenceController extends AbstractPreferenceControl
public boolean handlePreferenceTreeClick(Preference preference) {
if (KEY_MOBILE_NETWORK_SETTINGS.equals(preference.getKey())) {
if (FeatureFlagUtils.isEnabled(mContext, FeatureFlags.MOBILE_NETWORK_V2)) {
//TODO(b/110260193): go to the mobile network page existed in settings
final Intent intent = new Intent(mContext, MobileSettingsActivity.class);
mContext.startActivity(intent);
} else {
final Intent intent = new Intent(Intent.ACTION_MAIN);
intent.setComponent(

View File

@@ -0,0 +1,140 @@
/*
* Copyright (C) 2018 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.mobilenetwork;
import static com.android.settings.mobilenetwork.MobileSettingsActivity.MOBILE_SETTINGS_TAG;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import android.content.Context;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.view.Menu;
import android.view.View;
import com.android.internal.view.menu.ContextMenuBuilder;
import com.android.settings.R;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.google.android.material.bottomnavigation.BottomNavigationView;
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 java.util.ArrayList;
import java.util.List;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
@RunWith(SettingsRobolectricTestRunner.class)
public class MobileSettingsActivityTest {
private static final int CURRENT_SUB_ID = 3;
private static final int PREV_SUB_ID = 1;
private Context mContext;
private MobileSettingsActivity mMobileSettingsActivity;
private List<SubscriptionInfo> mSubscriptionInfos;
private Fragment mShowFragment;
private Fragment mHideFragment;
@Mock
private SubscriptionManager mSubscriptionManager;
@Mock
private SubscriptionInfo mSubscriptionInfo;
@Mock
private FragmentManager mFragmentManager;
@Mock
private FragmentTransaction mFragmentTransaction;
@Mock
private BottomNavigationView mBottomNavigationView;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = spy(RuntimeEnvironment.application);
mMobileSettingsActivity = spy(new MobileSettingsActivity());
mSubscriptionInfos = new ArrayList<>();
mShowFragment = new Fragment();
mHideFragment = new Fragment();
mMobileSettingsActivity.mSubscriptionInfos = mSubscriptionInfos;
doReturn(mSubscriptionManager).when(mMobileSettingsActivity).getSystemService(
SubscriptionManager.class);
doReturn(mBottomNavigationView).when(mMobileSettingsActivity).findViewById(R.id.bottom_nav);
doReturn(mFragmentManager).when(mMobileSettingsActivity).getSupportFragmentManager();
doReturn(mFragmentTransaction).when(mFragmentManager).beginTransaction();
doReturn(mHideFragment).when(mFragmentManager).findFragmentByTag(
MOBILE_SETTINGS_TAG + PREV_SUB_ID);
doReturn(mShowFragment).when(mFragmentManager).findFragmentByTag(
MOBILE_SETTINGS_TAG + CURRENT_SUB_ID);
}
@Test
public void updateBottomNavigationView_oneSubscription_shouldBeGone() {
mSubscriptionInfos.add(mSubscriptionInfo);
doReturn(mSubscriptionInfos).when(mSubscriptionManager).getActiveSubscriptionInfoList();
mMobileSettingsActivity.updateBottomNavigationView();
verify(mBottomNavigationView).setVisibility(View.GONE);
}
@Test
public void updateBottomNavigationView_twoSubscription_updateMenu() {
final Menu menu = new ContextMenuBuilder(mContext);
mSubscriptionInfos.add(mSubscriptionInfo);
mSubscriptionInfos.add(mSubscriptionInfo);
doReturn(mSubscriptionInfos).when(mSubscriptionManager).getActiveSubscriptionInfoList();
doReturn(menu).when(mBottomNavigationView).getMenu();
mMobileSettingsActivity.updateBottomNavigationView();
assertThat(menu.size()).isEqualTo(2);
}
@Test
public void switchFragment_hidePreviousFragment() {
mMobileSettingsActivity.mPrevSubscriptionId = PREV_SUB_ID;
mMobileSettingsActivity.switchFragment(mShowFragment, CURRENT_SUB_ID);
verify(mFragmentTransaction).hide(mHideFragment);
}
@Test
public void switchFragment_fragmentExist_showItWithArguments() {
mMobileSettingsActivity.mPrevSubscriptionId = PREV_SUB_ID;
mMobileSettingsActivity.switchFragment(mShowFragment, CURRENT_SUB_ID);
assertThat(mShowFragment.getArguments().getInt(
MobileSettingsActivity.KEY_SUBSCRIPTION_ID)).isEqualTo(CURRENT_SUB_ID);
verify(mFragmentTransaction).show(mShowFragment);
}
}