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

Commit 50361687 authored by Christophe Koessler's avatar Christophe Koessler
Browse files

Refactor CountryDetectorService

Clean-up the test to use Mockito instead of the deprecated
AndroidTestCase, also getting rid of 1.5s of Thread.sleep.
No logic change.

Bug: 141626568
Test: atest CountryDetectorServiceTest
Change-Id: Ic0782b1ffcd1dcb30b9f1d91b37521d1f9887f03
parent a2a4bd9c
Loading
Loading
Loading
Loading
+34 −42
Original line number Diff line number Diff line
@@ -16,14 +16,6 @@

package com.android.server;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.HashMap;

import com.android.internal.os.BackgroundThread;
import com.android.internal.util.DumpUtils;
import com.android.server.location.ComprehensiveCountryDetector;

import android.content.Context;
import android.location.Country;
import android.location.CountryListener;
@@ -36,17 +28,25 @@ import android.util.PrintWriterPrinter;
import android.util.Printer;
import android.util.Slog;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.DumpUtils;
import com.android.server.location.ComprehensiveCountryDetector;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.HashMap;

/**
 * This class detects the country that the user is in through
 * {@link ComprehensiveCountryDetector}.
 * This class detects the country that the user is in through {@link ComprehensiveCountryDetector}.
 *
 * @hide
 */
public class CountryDetectorService extends ICountryDetector.Stub implements Runnable {
public class CountryDetectorService extends ICountryDetector.Stub {

    /**
     * The class represents the remote listener, it will also removes itself
     * from listener list when the remote process was died.
     * The class represents the remote listener, it will also removes itself from listener list when
     * the remote process was died.
     */
    private final class Receiver implements IBinder.DeathRecipient {
        private final ICountryListener mListener;
@@ -79,9 +79,11 @@ public class CountryDetectorService extends ICountryDetector.Stub implements Run
        }
    }

    private final static String TAG = "CountryDetector";
    private static final String TAG = "CountryDetector";

    /** Whether to dump the state of the country detector service to bugreports */
    /**
     * Whether to dump the state of the country detector service to bugreports
     */
    private static final boolean DEBUG = false;

    private final HashMap<IBinder, Receiver> mReceivers;
@@ -92,9 +94,15 @@ public class CountryDetectorService extends ICountryDetector.Stub implements Run
    private CountryListener mLocationBasedDetectorListener;

    public CountryDetectorService(Context context) {
        this(context, BackgroundThread.getHandler());
    }

    @VisibleForTesting
    CountryDetectorService(Context context, Handler handler) {
        super();
        mReceivers = new HashMap<IBinder, Receiver>();
        mReceivers = new HashMap<>();
        mContext = context;
        mHandler = handler;
    }

    @Override
@@ -154,7 +162,6 @@ public class CountryDetectorService extends ICountryDetector.Stub implements Run
        }
    }


    protected void notifyReceivers(Country country) {
        synchronized (mReceivers) {
            for (Receiver receiver : mReceivers.values()) {
@@ -170,38 +177,23 @@ public class CountryDetectorService extends ICountryDetector.Stub implements Run

    void systemRunning() {
        // Shall we wait for the initialization finish.
        BackgroundThread.getHandler().post(this);
        mHandler.post(
                () -> {
                    initialize();
                    mSystemReady = true;
                });
    }

    private void initialize() {
        mCountryDetector = new ComprehensiveCountryDetector(mContext);
        mLocationBasedDetectorListener = new CountryListener() {
            public void onCountryDetected(final Country country) {
                mHandler.post(new Runnable() {
                    public void run() {
                        notifyReceivers(country);
                    }
                });
            }
        };
    }

    public void run() {
        mHandler = new Handler();
        initialize();
        mSystemReady = true;
        mLocationBasedDetectorListener = country -> mHandler.post(() -> notifyReceivers(country));
    }

    protected void setCountryListener(final CountryListener listener) {
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                mCountryDetector.setCountryListener(listener);
            }
        });
        mHandler.post(() -> mCountryDetector.setCountryListener(listener));
    }

    // For testing
    @VisibleForTesting
    boolean isSystemReady() {
        return mSystemReady;
    }
+1 −0
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ android_test {
        "services.net",
        "services.usage",
        "guava",
        "androidx.test.core",
        "androidx.test.runner",
        "androidx.test.rules",
        "mockito-target-minus-junit4",
+94 −49
Original line number Diff line number Diff line
/*
 * Copyright (C) 2010 The Android Open Source Project
 * 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.
@@ -11,28 +11,48 @@
 * 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
 * limitations under the License.
 */

package com.android.server;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.doAnswer;

import android.content.Context;
import android.location.Country;
import android.location.CountryListener;
import android.location.ICountryListener;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.test.AndroidTestCase;

public class CountryDetectorServiceTest extends AndroidTestCase {
    private class CountryListenerTester extends ICountryListener.Stub {
import androidx.test.core.app.ApplicationProvider;

import com.google.common.truth.Expect;

import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnitRunner;

@RunWith(MockitoJUnitRunner.class)
public class CountryDetectorServiceTest {

    private static class CountryListenerTester extends ICountryListener.Stub {
        private Country mCountry;

        @Override
        public void onCountryDetected(Country country) throws RemoteException {
        public void onCountryDetected(Country country) {
            mCountry = country;
        }

        public Country getCountry() {
        Country getCountry() {
            return mCountry;
        }

@@ -41,12 +61,11 @@ public class CountryDetectorServiceTest extends AndroidTestCase {
        }
    }

    private class CountryDetectorServiceTester extends CountryDetectorService {

    private static class CountryDetectorServiceTester extends CountryDetectorService {
        private CountryListener mListener;

        public CountryDetectorServiceTester(Context context) {
            super(context);
        CountryDetectorServiceTester(Context context, Handler handler) {
            super(context, handler);
        }

        @Override
@@ -59,51 +78,77 @@ public class CountryDetectorServiceTest extends AndroidTestCase {
            mListener = listener;
        }

        public boolean isListenerSet() {
        boolean isListenerSet() {
            return mListener != null;
        }
    }

    public void testAddRemoveListener() throws RemoteException {
        CountryDetectorServiceTester serviceTester = new CountryDetectorServiceTester(getContext());
        serviceTester.systemRunning();
        waitForSystemReady(serviceTester);
        CountryListenerTester listenerTester = new CountryListenerTester();
        serviceTester.addCountryListener(listenerTester);
        assertTrue(serviceTester.isListenerSet());
        serviceTester.removeCountryListener(listenerTester);
        assertFalse(serviceTester.isListenerSet());
    @Rule
    public final Expect expect = Expect.create();
    @Spy
    private Context mContext = ApplicationProvider.getApplicationContext();
    @Spy
    private Handler mHandler = new Handler(Looper.myLooper());
    private CountryDetectorServiceTester mCountryDetectorService;

    @BeforeClass
    public static void oneTimeInitialization() {
        if (Looper.myLooper() == null) {
            Looper.prepare();
        }
    }

    @Before
    public void setUp() {
        mCountryDetectorService = new CountryDetectorServiceTester(mContext, mHandler);

        // Immediately invoke run on the Runnable posted to the handler
        doAnswer(invocation -> {
            Message message = invocation.getArgument(0);
            message.getCallback().run();
            return true;
        }).when(mHandler).sendMessageAtTime(any(Message.class), anyLong());
    }

    public void testNotifyListeners() throws RemoteException {
        CountryDetectorServiceTester serviceTester = new CountryDetectorServiceTester(getContext());
        CountryListenerTester listenerTesterA = new CountryListenerTester();
        CountryListenerTester listenerTesterB = new CountryListenerTester();
    @Test
    public void countryListener_add_successful() throws RemoteException {
        CountryListenerTester countryListener = new CountryListenerTester();

        mCountryDetectorService.systemRunning();
        expect.that(mCountryDetectorService.isListenerSet()).isFalse();
        mCountryDetectorService.addCountryListener(countryListener);

        expect.that(mCountryDetectorService.isListenerSet()).isTrue();
    }

    @Test
    public void countryListener_remove_successful() throws RemoteException {
        CountryListenerTester countryListener = new CountryListenerTester();

        mCountryDetectorService.systemRunning();
        mCountryDetectorService.addCountryListener(countryListener);
        expect.that(mCountryDetectorService.isListenerSet()).isTrue();
        mCountryDetectorService.removeCountryListener(countryListener);

        expect.that(mCountryDetectorService.isListenerSet()).isFalse();
    }

    @Test
    public void countryListener_notify_successful() throws RemoteException {
        CountryListenerTester countryListenerA = new CountryListenerTester();
        CountryListenerTester countryListenerB = new CountryListenerTester();
        Country country = new Country("US", Country.COUNTRY_SOURCE_NETWORK);
        serviceTester.systemRunning();
        waitForSystemReady(serviceTester);
        serviceTester.addCountryListener(listenerTesterA);
        serviceTester.addCountryListener(listenerTesterB);
        serviceTester.notifyReceivers(country);
        assertTrue(serviceTester.isListenerSet());
        assertTrue(listenerTesterA.isNotified());
        assertTrue(listenerTesterB.isNotified());
        serviceTester.removeCountryListener(listenerTesterA);
        serviceTester.removeCountryListener(listenerTesterB);
        assertFalse(serviceTester.isListenerSet());
    }

    private void waitForSystemReady(CountryDetectorService service) {
        int count = 5;
        while (count-- > 0) {
            try {
                Thread.sleep(500);
            } catch (Exception e) {
            }
            if (service.isSystemReady()) {
                return;
            }
        }
        throw new RuntimeException("Wait System Ready timeout");

        mCountryDetectorService.systemRunning();
        mCountryDetectorService.addCountryListener(countryListenerA);
        mCountryDetectorService.addCountryListener(countryListenerB);
        expect.that(countryListenerA.isNotified()).isFalse();
        expect.that(countryListenerB.isNotified()).isFalse();
        mCountryDetectorService.notifyReceivers(country);

        expect.that(countryListenerA.isNotified()).isTrue();
        expect.that(countryListenerB.isNotified()).isTrue();
        expect.that(countryListenerA.getCountry().equalsIgnoreSource(country)).isTrue();
        expect.that(countryListenerB.getCountry().equalsIgnoreSource(country)).isTrue();
    }
}