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

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

Merge "[Settings] Settings within each SIM not been displayed to the user" into sc-dev

parents 31a2875c 83b22530
Loading
Loading
Loading
Loading
+103 −20
Original line number Diff line number Diff line
@@ -25,19 +25,30 @@ import android.os.Looper;
import android.provider.Settings;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.util.Log;

import androidx.annotation.Keep;
import androidx.annotation.VisibleForTesting;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.OnLifecycleEvent;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * A proxy to the subscription manager
 */
public class ProxySubscriptionManager implements LifecycleObserver {

    private static final String LOG_TAG = "ProxySubscriptionManager";

    private static final int LISTENER_END_OF_LIFE = -1;
    private static final int LISTENER_IS_INACTIVE = 0;
    private static final int LISTENER_IS_ACTIVE = 1;

    /**
     * Interface for monitor active subscriptions list changing
     */
@@ -74,22 +85,36 @@ public class ProxySubscriptionManager implements LifecycleObserver {
    private ProxySubscriptionManager(Context context) {
        final Looper looper = context.getMainLooper();

        mActiveSubscriptionsListeners =
                new ArrayList<OnActiveSubscriptionChangedListener>();

        mSubscriptionMonitor = new ActiveSubscriptionsListener(looper, context) {
        ActiveSubscriptionsListener subscriptionMonitor = new ActiveSubscriptionsListener(
                looper, context) {
            public void onChanged() {
                notifyAllListeners();
                notifySubscriptionInfoMightChanged();
            }
        };
        mAirplaneModeMonitor = new GlobalSettingsChangeListener(looper,
                context, Settings.Global.AIRPLANE_MODE_ON) {
        GlobalSettingsChangeListener airplaneModeMonitor = new GlobalSettingsChangeListener(
                looper, context, Settings.Global.AIRPLANE_MODE_ON) {
            public void onChanged(String field) {
                mSubscriptionMonitor.clearCache();
                notifyAllListeners();
                subscriptionMonitor.clearCache();
                notifySubscriptionInfoMightChanged();
            }
        };

        init(context, subscriptionMonitor, airplaneModeMonitor);
    }

    @Keep
    @VisibleForTesting
    protected void init(Context context, ActiveSubscriptionsListener activeSubscriptionsListener,
            GlobalSettingsChangeListener airplaneModeOnSettingsChangeListener) {

        mActiveSubscriptionsListeners =
                new ArrayList<OnActiveSubscriptionChangedListener>();
        mPendingNotifyListeners =
                new ArrayList<OnActiveSubscriptionChangedListener>();

        mSubscriptionMonitor = activeSubscriptionsListener;
        mAirplaneModeMonitor = airplaneModeOnSettingsChangeListener;

        mSubscriptionMonitor.start();
    }

@@ -98,15 +123,19 @@ public class ProxySubscriptionManager implements LifecycleObserver {
    private GlobalSettingsChangeListener mAirplaneModeMonitor;

    private List<OnActiveSubscriptionChangedListener> mActiveSubscriptionsListeners;
    private List<OnActiveSubscriptionChangedListener> mPendingNotifyListeners;

    private void notifyAllListeners() {
        for (OnActiveSubscriptionChangedListener listener : mActiveSubscriptionsListeners) {
            final Lifecycle lifecycle = listener.getLifecycle();
            if ((lifecycle == null)
                    || (lifecycle.getCurrentState().isAtLeast(Lifecycle.State.STARTED))) {
                listener.onChanged();
            }
        }
    @Keep
    @VisibleForTesting
    protected void notifySubscriptionInfoMightChanged() {
        // create a merged list for processing all listeners
        List<OnActiveSubscriptionChangedListener> listeners =
                new ArrayList<OnActiveSubscriptionChangedListener>(mPendingNotifyListeners);
        listeners.addAll(mActiveSubscriptionsListeners);

        mActiveSubscriptionsListeners.clear();
        mPendingNotifyListeners.clear();
        processStatusChangeOnListeners(listeners);
    }

    /**
@@ -131,6 +160,11 @@ public class ProxySubscriptionManager implements LifecycleObserver {
    @OnLifecycleEvent(ON_START)
    void onStart() {
        mSubscriptionMonitor.start();

        // callback notify those listener(s) which back to active state
        List<OnActiveSubscriptionChangedListener> listeners = mPendingNotifyListeners;
        mPendingNotifyListeners = new ArrayList<OnActiveSubscriptionChangedListener>();
        processStatusChangeOnListeners(listeners);
    }

    @OnLifecycleEvent(ON_STOP)
@@ -215,12 +249,17 @@ public class ProxySubscriptionManager implements LifecycleObserver {
    }

    /**
     * Add listener to active subscriptions monitor list
     * Add listener to active subscriptions monitor list.
     * Note: listener only take place when change happens.
     *       No immediate callback performed after the invoke of this method.
     *
     * @param listener listener to active subscriptions change
     */
    @Keep
    public void addActiveSubscriptionsListener(OnActiveSubscriptionChangedListener listener) {
        if (mActiveSubscriptionsListeners.contains(listener)) {
        removeSpecificListenerAndCleanList(listener, mPendingNotifyListeners);
        removeSpecificListenerAndCleanList(listener, mActiveSubscriptionsListeners);
        if ((listener == null) || (getListenerState(listener) == LISTENER_END_OF_LIFE)) {
            return;
        }
        mActiveSubscriptionsListeners.add(listener);
@@ -231,7 +270,51 @@ public class ProxySubscriptionManager implements LifecycleObserver {
     *
     * @param listener listener to active subscriptions change
     */
    @Keep
    public void removeActiveSubscriptionsListener(OnActiveSubscriptionChangedListener listener) {
        mActiveSubscriptionsListeners.remove(listener);
        removeSpecificListenerAndCleanList(listener, mPendingNotifyListeners);
        removeSpecificListenerAndCleanList(listener, mActiveSubscriptionsListeners);
    }

    private int getListenerState(OnActiveSubscriptionChangedListener listener) {
        Lifecycle lifecycle = listener.getLifecycle();
        if (lifecycle == null) {
            return LISTENER_IS_ACTIVE;
        }
        Lifecycle.State lifecycleState = lifecycle.getCurrentState();
        if (lifecycleState == Lifecycle.State.DESTROYED) {
            Log.d(LOG_TAG, "Listener dead detected - " + listener);
            return LISTENER_END_OF_LIFE;
        }
        return lifecycleState.isAtLeast(Lifecycle.State.STARTED) ?
                LISTENER_IS_ACTIVE : LISTENER_IS_INACTIVE;
    }

    private void removeSpecificListenerAndCleanList(OnActiveSubscriptionChangedListener listener,
            List<OnActiveSubscriptionChangedListener> list) {
        // also drop listener(s) which is end of life
        list.removeIf(it -> (it == listener) || (getListenerState(it) == LISTENER_END_OF_LIFE));
    }

    private void processStatusChangeOnListeners(
            List<OnActiveSubscriptionChangedListener> listeners) {
        // categorize listener(s), and end of life listener(s) been ignored
        Map<Integer, List<OnActiveSubscriptionChangedListener>> categorizedListeners =
                listeners.stream()
                .collect(Collectors.groupingBy(it -> getListenerState(it)));

        // have inactive listener(s) in pending list
        categorizedListeners.computeIfPresent(LISTENER_IS_INACTIVE, (category, list) -> {
            mPendingNotifyListeners.addAll(list);
            return list;
        });

        // get active listener(s)
        categorizedListeners.computeIfPresent(LISTENER_IS_ACTIVE, (category, list) -> {
            mActiveSubscriptionsListeners.addAll(list);
            // notify each one of them
            list.stream().forEach(it -> it.onChanged());
            return list;
        });
    }
}
+4 −6
Original line number Diff line number Diff line
@@ -132,15 +132,13 @@ public class MobileNetworkActivity extends SettingsBaseActivity
                : ((startIntent != null)
                ? startIntent.getIntExtra(Settings.EXTRA_SUB_ID, SUB_ID_NULL)
                : SUB_ID_NULL);
        // perform registration after mCurSubscriptionId been configured.
        registerActiveSubscriptionsListener();

        final SubscriptionInfo subscription = getSubscription();
        maybeShowContactDiscoveryDialog(subscription);

        // Since onChanged() will take place immediately when addActiveSubscriptionsListener(),
        // perform registration after mCurSubscriptionId been configured.
        registerActiveSubscriptionsListener();

        updateSubscriptions(subscription, savedInstanceState);
        updateSubscriptions(subscription, null);
    }

    @VisibleForTesting
@@ -296,7 +294,7 @@ public class MobileNetworkActivity extends SettingsBaseActivity
        final Fragment fragment = new MobileNetworkSettings();
        fragment.setArguments(bundle);
        fragmentTransaction.replace(R.id.content_frame, fragment, fragmentTag);
        fragmentTransaction.commit();
        fragmentTransaction.commitAllowingStateLoss();
    }

    private void removeContactDiscoveryDialog(int subId) {
+228 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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 com.google.common.truth.Truth.assertThat;

import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;

import android.content.Context;

import androidx.lifecycle.Lifecycle;
import androidx.test.annotation.UiThreadTest;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;

@RunWith(AndroidJUnit4.class)
public class ProxySubscriptionManagerTest {

    private Context mContext;
    @Mock
    private ActiveSubscriptionsListener mActiveSubscriptionsListener;
    @Mock
    private GlobalSettingsChangeListener mAirplaneModeOnSettingsChangeListener;

    @Mock
    private Lifecycle mLifecycle_ON_PAUSE;
    @Mock
    private Lifecycle mLifecycle_ON_RESUME;
    @Mock
    private Lifecycle mLifecycle_ON_DESTROY;

    private Client mClient1;
    private Client mClient2;

    @Before
    @UiThreadTest
    public void setUp() {
        MockitoAnnotations.initMocks(this);

        mContext = spy(ApplicationProvider.getApplicationContext());

        doReturn(Lifecycle.State.CREATED).when(mLifecycle_ON_PAUSE).getCurrentState();
        doReturn(Lifecycle.State.STARTED).when(mLifecycle_ON_RESUME).getCurrentState();
        doReturn(Lifecycle.State.DESTROYED).when(mLifecycle_ON_DESTROY).getCurrentState();

        mClient1 = new Client();
        mClient1.setLifecycle(mLifecycle_ON_RESUME);
        mClient2 = new Client();
        mClient2.setLifecycle(mLifecycle_ON_RESUME);
    }

    private ProxySubscriptionManager getInstance(Context context) {
        ProxySubscriptionManager proxy =
                Mockito.mock(ProxySubscriptionManager.class, Mockito.CALLS_REAL_METHODS);
        proxy.init(context, mActiveSubscriptionsListener, mAirplaneModeOnSettingsChangeListener);
        proxy.notifySubscriptionInfoMightChanged();
        return proxy;
    }

    public class Client implements ProxySubscriptionManager.OnActiveSubscriptionChangedListener {
        private Lifecycle lifeCycle;
        private int numberOfCallback;

        public void onChanged() {
            numberOfCallback++;
        }

        public Lifecycle getLifecycle() {
            return lifeCycle;
        }

        public int getCallbackCount() {
            return numberOfCallback;
        }

        public void setLifecycle(Lifecycle lifecycle) {
            lifeCycle = lifecycle;
        }
    }

    @Test
    @UiThreadTest
    public void addActiveSubscriptionsListener_addOneClient_getNoCallback() {
        ProxySubscriptionManager proxy = getInstance(mContext);

        proxy.addActiveSubscriptionsListener(mClient1);
        assertThat(mClient1.getCallbackCount()).isEqualTo(0);
    }

    @Test
    @UiThreadTest
    public void addActiveSubscriptionsListener_addOneClient_changeOnSimGetCallback() {
        ProxySubscriptionManager proxy = getInstance(mContext);

        proxy.addActiveSubscriptionsListener(mClient1);
        assertThat(mClient1.getCallbackCount()).isEqualTo(0);

        proxy.notifySubscriptionInfoMightChanged();
        assertThat(mClient1.getCallbackCount()).isEqualTo(1);
    }

    @Test
    @UiThreadTest
    public void addActiveSubscriptionsListener_addOneClient_noCallbackUntilUiResume() {
        ProxySubscriptionManager proxy = getInstance(mContext);

        mClient1.setLifecycle(mLifecycle_ON_PAUSE);

        proxy.addActiveSubscriptionsListener(mClient1);
        assertThat(mClient1.getCallbackCount()).isEqualTo(0);

        proxy.notifySubscriptionInfoMightChanged();
        assertThat(mClient1.getCallbackCount()).isEqualTo(0);

        mClient1.setLifecycle(mLifecycle_ON_RESUME);
        proxy.onStart();
        Assert.assertTrue(mClient1.getCallbackCount() > 0);

        mClient1.setLifecycle(mLifecycle_ON_PAUSE);
        proxy.onStop();
        int latestCallbackCount = mClient1.getCallbackCount();

        proxy.notifySubscriptionInfoMightChanged();
        assertThat(mClient1.getCallbackCount()).isEqualTo(latestCallbackCount);
    }

    @Test
    @UiThreadTest
    public void addActiveSubscriptionsListener_addTwoClient_eachClientGetNoCallback() {
        ProxySubscriptionManager proxy = getInstance(mContext);

        proxy.addActiveSubscriptionsListener(mClient1);
        assertThat(mClient1.getCallbackCount()).isEqualTo(0);

        proxy.addActiveSubscriptionsListener(mClient2);
        assertThat(mClient1.getCallbackCount()).isEqualTo(0);
        assertThat(mClient2.getCallbackCount()).isEqualTo(0);
    }

    @Test
    @UiThreadTest
    public void addActiveSubscriptionsListener_addTwoClient_callbackOnlyWhenResume() {
        ProxySubscriptionManager proxy = getInstance(mContext);

        proxy.addActiveSubscriptionsListener(mClient1);
        assertThat(mClient1.getCallbackCount()).isEqualTo(0);

        proxy.addActiveSubscriptionsListener(mClient2);
        assertThat(mClient1.getCallbackCount()).isEqualTo(0);
        assertThat(mClient2.getCallbackCount()).isEqualTo(0);

        mClient1.setLifecycle(mLifecycle_ON_PAUSE);
        proxy.onStop();
        assertThat(mClient1.getCallbackCount()).isEqualTo(0);
        assertThat(mClient2.getCallbackCount()).isEqualTo(0);

        proxy.notifySubscriptionInfoMightChanged();
        assertThat(mClient1.getCallbackCount()).isEqualTo(0);
        assertThat(mClient2.getCallbackCount()).isEqualTo(1);

        mClient1.setLifecycle(mLifecycle_ON_RESUME);
        proxy.onStart();
        Assert.assertTrue(mClient1.getCallbackCount() > 0);
        assertThat(mClient2.getCallbackCount()).isEqualTo(1);
    }

    @Test
    @UiThreadTest
    public void removeActiveSubscriptionsListener_removedClient_noCallback() {
        ProxySubscriptionManager proxy = getInstance(mContext);

        proxy.addActiveSubscriptionsListener(mClient1);
        assertThat(mClient1.getCallbackCount()).isEqualTo(0);

        proxy.notifySubscriptionInfoMightChanged();
        assertThat(mClient1.getCallbackCount()).isEqualTo(1);

        proxy.removeActiveSubscriptionsListener(mClient1);
        assertThat(mClient1.getCallbackCount()).isEqualTo(1);

        proxy.notifySubscriptionInfoMightChanged();
        assertThat(mClient1.getCallbackCount()).isEqualTo(1);
    }

    @Test
    @UiThreadTest
    public void notifySubscriptionInfoMightChanged_destroyedClient_autoRemove() {
        ProxySubscriptionManager proxy = getInstance(mContext);

        proxy.addActiveSubscriptionsListener(mClient1);
        assertThat(mClient1.getCallbackCount()).isEqualTo(0);

        proxy.notifySubscriptionInfoMightChanged();
        assertThat(mClient1.getCallbackCount()).isEqualTo(1);

        mClient1.setLifecycle(mLifecycle_ON_DESTROY);
        proxy.notifySubscriptionInfoMightChanged();
        assertThat(mClient1.getCallbackCount()).isEqualTo(1);

        mClient1.setLifecycle(mLifecycle_ON_RESUME);
        proxy.notifySubscriptionInfoMightChanged();
        assertThat(mClient1.getCallbackCount()).isEqualTo(1);
    }
}