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

Commit 2462e490 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Refine TetherSettings with ViewModel"

parents d54c9cab a396a4d8
Loading
Loading
Loading
Loading
+6 −7
Original line number Diff line number Diff line
@@ -50,6 +50,7 @@ import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.lifecycle.ViewModelProvider;
import androidx.preference.Preference;
import androidx.preference.SwitchPreference;

@@ -77,7 +78,7 @@ import java.util.concurrent.atomic.AtomicReference;
 */
@SearchIndexable
public class TetherSettings extends RestrictedSettingsFragment
        implements DataSaverBackend.Listener, TetheringManager.TetheringEventCallback {
        implements DataSaverBackend.Listener {

    @VisibleForTesting
    static final String KEY_TETHER_PREFS_SCREEN = "tether_prefs_screen";
@@ -132,7 +133,6 @@ public class TetherSettings extends RestrictedSettingsFragment
    Context mContext;
    @VisibleForTesting
    TetheringManager mTm;
    private TetheringHelper mTetheringHelper;

    @Override
    public int getMetricsCategory() {
@@ -148,8 +148,9 @@ public class TetherSettings extends RestrictedSettingsFragment
        super.onAttach(context);
        mWifiTetherPreferenceController =
                new WifiTetherPreferenceController(context, getSettingsLifecycle());
        mTetheringHelper = TetheringHelper.getInstance(context, this /* TetheringEventCallback */,
                getSettingsLifecycle());
        TetheringManagerModel model = new ViewModelProvider(this).get(TetheringManagerModel.class);
        mTm = model.mTetheringManager;
        model.getTetheredInterfaces().observe(this, this::onTetheredInterfacesChanged);
    }

    @Override
@@ -188,7 +189,6 @@ public class TetherSettings extends RestrictedSettingsFragment
        mDataSaverBackend.addListener(this);

        mCm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
        mTm = mTetheringHelper.getTetheringManager();
        // Some devices do not have available EthernetManager. In that case getSystemService will
        // return null.
        mEm = mContext.getSystemService(EthernetManager.class);
@@ -687,8 +687,7 @@ public class TetherSettings extends RestrictedSettingsFragment
        }
    }

    @Override
    public void onTetheredInterfacesChanged(List<String> interfaces) {
    protected void onTetheredInterfacesChanged(List<String> interfaces) {
        Log.d(TAG, "onTetheredInterfacesChanged() interfaces : " + interfaces.toString());
        final String[] tethered = interfaces.toArray(new String[interfaces.size()]);
        updateUsbState(tethered);
+0 −157
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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.tether;

import android.annotation.NonNull;
import android.annotation.TestApi;
import android.content.Context;
import android.net.TetheringManager;
import android.util.Log;

import androidx.annotation.VisibleForTesting;
import androidx.lifecycle.DefaultLifecycleObserver;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleOwner;

import com.android.internal.annotations.GuardedBy;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Helper class for TetheringManager.
 */
public class TetheringHelper implements DefaultLifecycleObserver {
    private static final String TAG = "TetheringHelper";

    private static final Object sInstanceLock = new Object();
    @TestApi
    @GuardedBy("sInstanceLock")
    private static Map<Context, TetheringHelper> sTestInstances;

    protected static Context sAppContext;
    protected static TetheringManager sTetheringManager;
    protected static TetheringEventCallback sTetheringEventCallback = new TetheringEventCallback();
    protected static ArrayList<TetheringManager.TetheringEventCallback> sEventCallbacks =
            new ArrayList<>();

    protected TetheringManager.TetheringEventCallback mCallback;

    /**
     * Static method to create a singleton class for TetheringHelper.
     *
     * @param context The Context this is associated with.
     * @return an instance of {@link TetheringHelper} object.
     */
    @NonNull
    public static TetheringHelper getInstance(@NonNull Context context,
            @NonNull TetheringManager.TetheringEventCallback callback,
            @NonNull Lifecycle lifecycle) {
        synchronized (sInstanceLock) {
            if (sTestInstances != null && sTestInstances.containsKey(context)) {
                TetheringHelper testInstance = sTestInstances.get(context);
                Log.w(TAG, "The context owner use a test instance:" + testInstance);
                return testInstance;
            }

            if (sAppContext == null) {
                sAppContext = context.getApplicationContext();
                sTetheringManager = sAppContext.getSystemService(TetheringManager.class);
            }
            TetheringHelper helper = new TetheringHelper();
            helper.mCallback = callback;
            lifecycle.addObserver(helper);
            return helper;
        }
    }

    /**
     * A convenience method to set pre-prepared instance or mock class for testing.
     *
     * @param context  The Context this is associated with.
     * @param instance of {@link TetheringHelper} object.
     * @hide
     */
    @TestApi
    @VisibleForTesting
    public static void setTestInstance(@NonNull Context context, TetheringHelper instance) {
        synchronized (sInstanceLock) {
            if (sTestInstances == null) sTestInstances = new ConcurrentHashMap<>();
            Log.w(TAG, "Set a test instance by context:" + context);
            sTestInstances.put(context, instance);
        }
    }

    /**
     * The constructor can only be accessed from static method inside the class itself, this is
     * to avoid creating a class by adding a private constructor.
     */
    private TetheringHelper() {
        // Do nothing.
    }

    /**
     * Returns the TetheringManager If the system service is successfully obtained.
     */
    public TetheringManager getTetheringManager() {
        return sTetheringManager;
    }

    @Override
    public void onStart(@NonNull LifecycleOwner owner) {
        if (sEventCallbacks.contains(mCallback)) {
            Log.w(TAG, "The callback already contains, don't register callback:" + mCallback);
            return;
        }
        sEventCallbacks.add(mCallback);
        if (sEventCallbacks.size() == 1) {
            sTetheringManager.registerTetheringEventCallback(sAppContext.getMainExecutor(),
                    sTetheringEventCallback);
        }
    }

    @Override
    public void onStop(@NonNull LifecycleOwner owner) {
        if (!sEventCallbacks.remove(mCallback)) {
            Log.w(TAG, "The callback does not contain, don't unregister callback:" + mCallback);
            return;
        }
        if (sEventCallbacks.size() == 0) {
            sTetheringManager.unregisterTetheringEventCallback(sTetheringEventCallback);
        }
    }

    @Override
    public void onDestroy(@NonNull LifecycleOwner owner) {
        mCallback = null;
    }

    protected static final class TetheringEventCallback implements
            TetheringManager.TetheringEventCallback {
        @Override
        public void onTetheredInterfacesChanged(List<String> interfaces) {
            for (TetheringManager.TetheringEventCallback callback : sEventCallbacks) {
                if (callback == null) continue;
                callback.onTetheredInterfacesChanged(interfaces);
            }
        }
    }
}

+75 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.tether;

import android.app.Application;
import android.net.TetheringManager;

import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Transformations;

import java.util.List;

/**
 * TetheringManager view model implementation
 */
public class TetheringManagerModel extends AndroidViewModel {
    protected TetheringManager mTetheringManager;
    protected EventCallback mEventCallback = new EventCallback();
    protected MutableLiveData<List<String>> mTetheredInterfaces = new MutableLiveData<>();

    public TetheringManagerModel(@NonNull Application application) {
        super(application);
        mTetheringManager = application.getSystemService(TetheringManager.class);
        mTetheringManager
                .registerTetheringEventCallback(application.getMainExecutor(), mEventCallback);
    }

    @Override
    protected void onCleared() {
        mTetheringManager.unregisterTetheringEventCallback(mEventCallback);
    }

    /**
     * Gets the TetheringManager If the system service is successfully obtained.
     */
    public TetheringManager getTetheringManager() {
        return mTetheringManager;
    }

    /**
     * Gets the TetheredInterfaces wrapped by LiveData.
     */
    @NonNull
    public LiveData<List<String>> getTetheredInterfaces() {
        return Transformations.distinctUntilChanged(mTetheredInterfaces);
    }

    /**
     * Callback for use with {@link TetheringManager#registerTetheringEventCallback} to find out
     * tethering upstream status.
     */
    protected class EventCallback implements TetheringManager.TetheringEventCallback {
        @Override
        public void onTetheredInterfacesChanged(List<String> interfaces) {
            mTetheredInterfaces.setValue(interfaces);
        }
    }
}
+95 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 The Android Open Source Project
 * Copyright (C) 2023 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.
@@ -20,15 +20,12 @@ import static com.google.common.truth.Truth.assertThat;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.content.Context;
import android.app.Application;
import android.net.TetheringManager;

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

import org.junit.Before;
@@ -36,7 +33,6 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;

@@ -44,107 +40,56 @@ import java.util.List;
import java.util.concurrent.Executor;

@RunWith(AndroidJUnit4.class)
public class TetheringHelperTest {
public class TetheringManagerModelTest {
    @Rule
    public final MockitoRule mMockitoRule = MockitoJUnit.rule();
    @Spy
    Context mContext = ApplicationProvider.getApplicationContext();
    @Mock
    Lifecycle mLifecycle;
    Application mApplication;
    @Mock
    LifecycleOwner mLifecycleOwner;
    Executor mExecutor;
    @Mock
    TetheringManager mTetheringManager;
    @Mock
    TetheringManager.TetheringEventCallback mTetheringEventCallback;
    @Mock
    List<String> mInterfaces;

    TetheringHelper mHelper;
    TetheringManagerModel mModel;

    @Before
    public void setUp() {
        mHelper = TetheringHelper.getInstance(mContext, mTetheringEventCallback, mLifecycle);
        mHelper.sTetheringManager = mTetheringManager;
    }

    @Test
    public void constructor_propertiesShouldBeReady() {
        assertThat(mHelper.sAppContext).isNotNull();
        assertThat(mHelper.sTetheringManager).isNotNull();
        assertThat(mHelper.mCallback).isNotNull();
        verify(mLifecycle).addObserver(any());
    }

    @Test
    public void getTetheringManager_isNotNull() {
        assertThat(mHelper.getTetheringManager()).isNotNull();
    }

    @Test
    public void onStart_eventCallbacksClear_addAndRegisterCallback() {
        mHelper.sEventCallbacks.clear();

        mHelper.onStart(mLifecycleOwner);
        when(mApplication.getMainExecutor()).thenReturn(mExecutor);
        when(mApplication.getSystemService(TetheringManager.class)).thenReturn(mTetheringManager);

        assertThat(mHelper.sEventCallbacks.contains(mTetheringEventCallback)).isTrue();
        assertThat(mHelper.sEventCallbacks.size()).isEqualTo(1);
        verify(mTetheringManager).registerTetheringEventCallback(any(Executor.class),
                eq(TetheringHelper.sTetheringEventCallback));
        mModel = new TetheringManagerModel(mApplication);
    }

    @Test
    public void onStart_eventCallbacksContains_doNotRegisterCallback() {
        mHelper.sEventCallbacks.clear();
        mHelper.sEventCallbacks.add(mTetheringEventCallback);

        mHelper.onStart(mLifecycleOwner);

        verify(mTetheringManager, never()).registerTetheringEventCallback(any(Executor.class),
                eq(TetheringHelper.sTetheringEventCallback));
    public void constructor_registerCallback() {
        verify(mTetheringManager).registerTetheringEventCallback(any(), eq(mModel.mEventCallback));
    }

    @Test
    public void onStop_eventCallbacksContains_removeAndUnregisterCallback() {
        mHelper.sEventCallbacks.clear();
        mHelper.sEventCallbacks.add(mTetheringEventCallback);

        mHelper.onStop(mLifecycleOwner);
    public void onCleared_unregisterCallback() {
        mModel.onCleared();

        assertThat(mHelper.sEventCallbacks.contains(mTetheringEventCallback)).isFalse();
        assertThat(mHelper.sEventCallbacks.size()).isEqualTo(0);
        verify(mTetheringManager).unregisterTetheringEventCallback(
                eq(TetheringHelper.sTetheringEventCallback));
        verify(mTetheringManager).unregisterTetheringEventCallback(eq(mModel.mEventCallback));
    }

    @Test
    public void onStop_eventCallbacksClear_doNotUnregisterCallback() {
        mHelper.sEventCallbacks.clear();

        mHelper.onStop(mLifecycleOwner);

        assertThat(mHelper.sEventCallbacks.contains(mTetheringEventCallback)).isFalse();
        assertThat(mHelper.sEventCallbacks.size()).isEqualTo(0);
        verify(mTetheringManager, never()).unregisterTetheringEventCallback(
                eq(TetheringHelper.sTetheringEventCallback));
    public void getTetheringManager_isNotNull() {
        assertThat(mModel.getTetheringManager()).isNotNull();
    }

    @Test
    public void onTetheredInterfacesChanged_eventCallbacksContains_doCallback() {
        mHelper.sEventCallbacks.clear();
        mHelper.sEventCallbacks.add(mTetheringEventCallback);

        mHelper.sTetheringEventCallback.onTetheredInterfacesChanged(mInterfaces);

        verify(mTetheringEventCallback).onTetheredInterfacesChanged(eq(mInterfaces));
    public void getTetheredInterfaces_isNotNull() {
        assertThat(mModel.getTetheredInterfaces()).isNotNull();
    }

    @Test
    public void onTetheredInterfacesChanged_eventCallbacksClear_doNotCallback() {
        mHelper.sEventCallbacks.clear();
    public void onTetheredInterfacesChanged_updateTetheredInterfaces() {
        mModel.mTetheredInterfaces.setValue(null);

        mHelper.sTetheringEventCallback.onTetheredInterfacesChanged(mInterfaces);
        mModel.mEventCallback.onTetheredInterfacesChanged(mInterfaces);

        verify(mTetheringEventCallback, never()).onTetheredInterfacesChanged(eq(mInterfaces));
        assertThat(mModel.mTetheredInterfaces.getValue()).isEqualTo(mInterfaces);
    }
}