Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0adbf366 authored by Bonian Chen's avatar Bonian Chen Committed by Android (Google) Code Review
Browse files

Merge "[Setting] MobileNetwork UI performance"

parents 4d8e9806 7dcfb7ba
Loading
Loading
Loading
Loading
+115 −0
Original line number Diff line number Diff line
/*
 * 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);
    }
}
+197 −0
Original line number Diff line number Diff line
/*
 * 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);
    }
}
+21 −16
Original line number Diff line number Diff line
@@ -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);
    }

    /**
     * Implementation of ProxySubscriptionManager.OnActiveSubscriptionChangedListener
     */
    public void onChanged() {
        updateSubscriptions(getSubscription());
    }

    @Override
    protected void onStart() {
        super.onStart();
        mSubscriptionAccess.start();
        updateSubscriptions(getSubscription());
    }

    @Override
    protected void onStop() {
        super.onStop();
        mSubscriptionAccess.stop();
    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;
+84 −0
Original line number Diff line number Diff line
/*
 * 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);
    }
}