Merge "[Setting] MobileNetwork UI performance"

This commit is contained in:
Bonian Chen
2019-11-04 14:18:50 +00:00
committed by Android (Google) Code Review
4 changed files with 420 additions and 19 deletions

View File

@@ -0,0 +1,115 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.network;
import static androidx.lifecycle.Lifecycle.Event.ON_DESTROY;
import static androidx.lifecycle.Lifecycle.Event.ON_START;
import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
import android.content.Context;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
import android.provider.Settings;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.OnLifecycleEvent;
/**
* A listener for Settings.Global configuration change, with support of Lifecycle
*/
abstract class GlobalSettingsChangeListener extends ContentObserver implements LifecycleObserver {
/**
* Constructor
*
* @param context of this listener
* @param field field of Global Settings
*/
GlobalSettingsChangeListener(Context context, String field) {
super(new Handler());
mContext = context;
mField = field;
monitorUri(true);
}
private Context mContext;
private String mField;
private Uri mUri;
private Lifecycle mLifecycle;
/**
* Observed Settings got changed
*/
public abstract void onChanged(String field);
/**
* Notify change of Globals.Setting based on given Lifecycle
*
* @param lifecycle life cycle to reference
*/
public void notifyChangeBasedOn(Lifecycle lifecycle) {
if (mLifecycle != null) {
mLifecycle.removeObserver(this);
}
if (lifecycle != null) {
lifecycle.addObserver(this);
}
mLifecycle = lifecycle;
}
public void onChange(boolean selfChange) {
if (!isMonitoring()) {
return;
}
onChanged(mField);
}
@OnLifecycleEvent(ON_START)
void onStart() {
monitorUri(true);
}
@OnLifecycleEvent(ON_STOP)
void onStop() {
monitorUri(false);
}
@OnLifecycleEvent(ON_DESTROY)
void onDestroy() {
monitorUri(false);
notifyChangeBasedOn(null);
}
private boolean isMonitoring() {
return (mUri != null);
}
private void monitorUri(boolean on) {
if (isMonitoring() == on) {
return;
}
if (mUri == null) {
mUri = Settings.Global.getUriFor(mField);
mContext.getContentResolver().registerContentObserver(mUri, false, this);
return;
}
mUri = null;
mContext.getContentResolver().unregisterContentObserver(this);
}
}

View File

@@ -0,0 +1,197 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.network;
import static androidx.lifecycle.Lifecycle.Event.ON_DESTROY;
import static androidx.lifecycle.Lifecycle.Event.ON_START;
import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
import android.content.Context;
import android.provider.Settings;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.OnLifecycleEvent;
import java.util.ArrayList;
import java.util.List;
/**
* A proxy to the subscription manager
*/
public class ProxySubscriptionManager extends SubscriptionManager.OnSubscriptionsChangedListener
implements LifecycleObserver {
/**
* Interface for monitor active subscriptions list changing
*/
public interface OnActiveSubscriptionChangedListener {
/**
* When active subscriptions list get changed
*/
void onChanged();
}
/**
* Get proxy instance to subscription manager
*
* @return proxy to subscription manager
*/
public static ProxySubscriptionManager getInstance(Context context) {
if (sSingleton != null) {
return sSingleton;
}
sSingleton = new ProxySubscriptionManager(context);
return sSingleton;
}
private static ProxySubscriptionManager sSingleton;
private ProxySubscriptionManager(Context context) {
mContext = context;
mActiveSubscriptionsListeners =
new ArrayList<OnActiveSubscriptionChangedListener>();
mSubsciptionsMonitor = new ActiveSubsciptionsListener(context) {
public void onChanged() {
notifyAllListeners();
}
};
mAirplaneModeMonitor = new GlobalSettingsChangeListener(context,
Settings.Global.AIRPLANE_MODE_ON) {
public void onChanged(String field) {
mSubsciptionsMonitor.clearCache();
notifyAllListeners();
}
};
mKeepCacheWhenOnStart = true;
mSubsciptionsMonitor.start();
}
private Lifecycle mLifecycle;
private Context mContext;
private ActiveSubsciptionsListener mSubsciptionsMonitor;
private GlobalSettingsChangeListener mAirplaneModeMonitor;
private boolean mKeepCacheWhenOnStart;
private List<OnActiveSubscriptionChangedListener> mActiveSubscriptionsListeners;
private void notifyAllListeners() {
for (OnActiveSubscriptionChangedListener listener : mActiveSubscriptionsListeners) {
listener.onChanged();
}
}
/**
* Lifecycle for data within proxy
*
* @param lifecycle life cycle to reference
*/
public void setLifecycle(Lifecycle lifecycle) {
if (mLifecycle != null) {
mLifecycle.removeObserver(this);
}
if (lifecycle != null) {
lifecycle.addObserver(this);
}
mLifecycle = lifecycle;
mAirplaneModeMonitor.notifyChangeBasedOn(lifecycle);
}
@OnLifecycleEvent(ON_START)
void onStart() {
if (!mKeepCacheWhenOnStart) {
mSubsciptionsMonitor.clearCache();
}
mSubsciptionsMonitor.start();
}
@OnLifecycleEvent(ON_STOP)
void onStop() {
mKeepCacheWhenOnStart = false;
mSubsciptionsMonitor.stop();
}
@OnLifecycleEvent(ON_DESTROY)
void onDestroy() {
mSubsciptionsMonitor.stop();
if (mLifecycle != null) {
mLifecycle.removeObserver(this);
mLifecycle = null;
sSingleton = null;
}
}
/**
* Get SubscriptionManager
*
* @return a SubscriptionManager
*/
public SubscriptionManager get() {
return mSubsciptionsMonitor.getSubscriptionManager();
}
/**
* Get a list of active subscription info
*
* @return A list of active subscription info
*/
public List<SubscriptionInfo> getActiveSubscriptionsInfo() {
return mSubsciptionsMonitor.getActiveSubscriptionsInfo();
}
/**
* Get an active subscription info with given subscription ID
*
* @param subId target subscription ID
* @return A subscription info which is active list
*/
public SubscriptionInfo getActiveSubscriptionInfo(int subId) {
return mSubsciptionsMonitor.getActiveSubscriptionInfo(subId);
}
/**
* Clear data cached within proxy
*/
public void clearCache() {
mSubsciptionsMonitor.clearCache();
}
/**
* Add listener to active subscriptions monitor list
*
* @param listener listener to active subscriptions change
*/
public void addActiveSubscriptionsListener(OnActiveSubscriptionChangedListener listener) {
mActiveSubscriptionsListeners.add(listener);
}
/**
* Remove listener from active subscriptions monitor list
*
* @param listener listener to active subscriptions change
*/
public void removeActiveSubscriptionsListener(OnActiveSubscriptionChangedListener listener) {
mActiveSubscriptionsListeners.remove(listener);
}
}

View File

@@ -36,7 +36,7 @@ import com.android.settings.R;
import com.android.settings.core.FeatureFlags;
import com.android.settings.core.SettingsBaseActivity;
import com.android.settings.development.featureflags.FeatureFlagPersistent;
import com.android.settings.network.ActiveSubsciptionsListener;
import com.android.settings.network.ProxySubscriptionManager;
import com.google.android.material.bottomnavigation.BottomNavigationView;
@@ -45,7 +45,8 @@ import java.util.List;
/**
* Activity for displaying MobileNetworkSettings
*/
public class MobileNetworkActivity extends SettingsBaseActivity {
public class MobileNetworkActivity extends SettingsBaseActivity
implements ProxySubscriptionManager.OnActiveSubscriptionChangedListener {
private static final String TAG = "MobileNetworkActivity";
@VisibleForTesting
@@ -53,7 +54,7 @@ public class MobileNetworkActivity extends SettingsBaseActivity {
@VisibleForTesting
static final int SUB_ID_NULL = Integer.MIN_VALUE;
private ActiveSubsciptionsListener mSubscriptionAccess;
private ProxySubscriptionManager mProxySubscriptionMgr;
private int mCurSubscriptionId;
@Override
@@ -86,11 +87,9 @@ public class MobileNetworkActivity extends SettingsBaseActivity {
}
setActionBar(findViewById(R.id.mobile_action_bar));
mSubscriptionAccess = new ActiveSubsciptionsListener(this) {
public void onChanged() {
updateSubscriptions(getSubscription());
}
};
mProxySubscriptionMgr = ProxySubscriptionManager.getInstance(this);
mProxySubscriptionMgr.setLifecycle(getLifecycle());
mProxySubscriptionMgr.addActiveSubscriptionsListener(this);
mCurSubscriptionId = savedInstanceState != null
? savedInstanceState.getInt(Settings.EXTRA_SUB_ID, SUB_ID_NULL)
@@ -105,17 +104,23 @@ public class MobileNetworkActivity extends SettingsBaseActivity {
updateTitleAndNavigation(subscription);
}
@Override
protected void onStart() {
super.onStart();
mSubscriptionAccess.start();
/**
* Implementation of ProxySubscriptionManager.OnActiveSubscriptionChangedListener
*/
public void onChanged() {
updateSubscriptions(getSubscription());
}
@Override
protected void onStop() {
super.onStop();
mSubscriptionAccess.stop();
protected void onStart() {
super.onStart();
updateSubscriptions(getSubscription());
}
@Override
protected void onDestroy() {
super.onDestroy();
mProxySubscriptionMgr.removeActiveSubscriptionsListener(this);
}
@Override
@@ -164,12 +169,12 @@ public class MobileNetworkActivity extends SettingsBaseActivity {
SubscriptionInfo getSubscription() {
if (mCurSubscriptionId != SUB_ID_NULL) {
final SubscriptionInfo subInfo =
mSubscriptionAccess.getActiveSubscriptionInfo(mCurSubscriptionId);
mProxySubscriptionMgr.getActiveSubscriptionInfo(mCurSubscriptionId);
if (subInfo != null) {
return subInfo;
}
}
final List<SubscriptionInfo> subInfos = mSubscriptionAccess.getActiveSubscriptionsInfo();
final List<SubscriptionInfo> subInfos = mProxySubscriptionMgr.getActiveSubscriptionsInfo();
if (CollectionUtils.isEmpty(subInfos)) {
return null;
}
@@ -179,7 +184,7 @@ public class MobileNetworkActivity extends SettingsBaseActivity {
private void updateBottomNavigationView() {
final BottomNavigationView navigation = findViewById(R.id.bottom_nav);
final List<SubscriptionInfo> subInfos = mSubscriptionAccess.getActiveSubscriptionsInfo();
final List<SubscriptionInfo> subInfos = mProxySubscriptionMgr.getActiveSubscriptionsInfo();
if (CollectionUtils.size(subInfos) <= 1) {
navigation.setVisibility(View.GONE);
} else {
@@ -196,7 +201,7 @@ public class MobileNetworkActivity extends SettingsBaseActivity {
if (!isSubscriptionChanged(subId)) {
return true;
}
final SubscriptionInfo subscriptionInfo = mSubscriptionAccess
final SubscriptionInfo subscriptionInfo = mProxySubscriptionMgr
.getActiveSubscriptionInfo(subId);
if (subscriptionInfo == null) {
return true;

View File

@@ -0,0 +1,84 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.network;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.content.Context;
import android.provider.Settings;
import androidx.lifecycle.Lifecycle;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
@RunWith(RobolectricTestRunner.class)
public class GlobalSettingsChangeListenerTest {
@Mock
private Lifecycle mLifecycle;
private Context mContext;
private GlobalSettingsChangeListener mListener;
private static final String SETTINGS_FIELD = Settings.Global.AIRPLANE_MODE_ON;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = spy(RuntimeEnvironment.application);
mListener = spy(new GlobalSettingsChangeListener(mContext, SETTINGS_FIELD) {
public void onChanged(String field) {}
});
doNothing().when(mLifecycle).addObserver(mListener);
doNothing().when(mLifecycle).removeObserver(mListener);
}
@Test
public void whenChanged_onChangedBeenCalled() {
mListener.onChange(false);
verify(mListener, times(1)).onChanged(SETTINGS_FIELD);
}
@Test
public void whenNotifyChangeBasedOnLifecycle_onStopEvent_onChangedNotCalled() {
mListener.notifyChangeBasedOn(mLifecycle);
mListener.onStart();
mListener.onChange(false);
verify(mListener, times(1)).onChanged(SETTINGS_FIELD);
mListener.onStop();
mListener.onChange(false);
verify(mListener, times(1)).onChanged(SETTINGS_FIELD);
mListener.onStart();
mListener.onChange(false);
verify(mListener, times(2)).onChanged(SETTINGS_FIELD);
}
}