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

Commit b04fae20 authored by Jeremy Klein's avatar Jeremy Klein
Browse files

Set the tether Entitlement app as active when enabling tethering.

This fixes tether entitlement timeouts when tethering is enabled
via the QS tile.

Bug: 29514913
Change-Id: I975bf3e52d2df49541544d1b7a6bdcdec1b61d8c
parent 10327d07
Loading
Loading
Loading
Loading
+63 −8
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import android.app.Activity;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.Service;
import android.app.usage.UsageStatsManager;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothPan;
import android.bluetooth.BluetoothProfile;
@@ -29,6 +30,8 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.ConnectivityManager;
import android.os.IBinder;
import android.os.ResultReceiver;
@@ -63,6 +66,7 @@ public class TetherService extends Service {

    private int mCurrentTypeIndex;
    private boolean mInProvisionCheck;
    private UsageStatsManagerWrapper mUsageManagerWrapper;
    private ArrayList<Integer> mCurrentTethers;
    private ArrayMap<Integer, List<ResultReceiver>> mPendingCallbacks;

@@ -87,6 +91,9 @@ public class TetherService extends Service {
        mPendingCallbacks.put(ConnectivityManager.TETHERING_USB, new ArrayList<ResultReceiver>());
        mPendingCallbacks.put(
                ConnectivityManager.TETHERING_BLUETOOTH, new ArrayList<ResultReceiver>());
        if (mUsageManagerWrapper == null) {
            mUsageManagerWrapper = new UsageStatsManagerWrapper(this);
        }
    }

    @Override
@@ -228,17 +235,43 @@ public class TetherService extends Service {

    private void startProvisioning(int index) {
        if (index < mCurrentTethers.size()) {
            Intent intent = getProvisionBroadcastIntent(index);
            setEntitlementAppActive(index);

            if (DEBUG) Log.d(TAG, "Sending provisioning broadcast: " + intent.getAction()
                    + " type: " + mCurrentTethers.get(index));

            sendBroadcast(intent);
            mInProvisionCheck = true;
        }
    }

    private Intent getProvisionBroadcastIntent(int index) {
        String provisionAction = getResources().getString(
                com.android.internal.R.string.config_mobile_hotspot_provision_app_no_ui);
            if (DEBUG) Log.d(TAG, "Sending provisioning broadcast: " + provisionAction + " type: "
                    + mCurrentTethers.get(index));
        Intent intent = new Intent(provisionAction);
        int type = mCurrentTethers.get(index);
        intent.putExtra(TETHER_CHOICE, type);
        intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);

            sendBroadcast(intent);
            mInProvisionCheck = true;
        return intent;
    }

    private void setEntitlementAppActive(int index) {
        final PackageManager packageManager = getPackageManager();
        Intent intent = getProvisionBroadcastIntent(index);
        List<ResolveInfo> resolvers =
                packageManager.queryBroadcastReceivers(intent, PackageManager.MATCH_ALL);
        if (resolvers.isEmpty()) {
            Log.e(TAG, "No found BroadcastReceivers for provision intent.");
            return;
        }

        for (ResolveInfo resolver : resolvers) {
            if (resolver.activityInfo.applicationInfo.isSystemApp()) {
                String packageName = resolver.activityInfo.packageName;
                mUsageManagerWrapper.setAppInactive(packageName, false);
            }
        }
    }

@@ -335,4 +368,26 @@ public class TetherService extends Service {
        }
    };

    @VisibleForTesting
    void setUsageStatsManagerWrapper(UsageStatsManagerWrapper wrapper) {
        mUsageManagerWrapper = wrapper;
    }

    /**
     * A static helper class used for tests. UsageStatsManager cannot be mocked out becasue
     * it's marked final. This class can be mocked out instead.
     */
    @VisibleForTesting
    public static class UsageStatsManagerWrapper {
        private final UsageStatsManager mUsageStatsManager;

        UsageStatsManagerWrapper(Context context) {
            mUsageStatsManager = (UsageStatsManager)
                    context.getSystemService(Context.USAGE_STATS_SERVICE);
        }

        void setAppInactive(String packageName, boolean isInactive) {
            mUsageStatsManager.setAppInactive(packageName, isInactive);
        }
    }
}
+88 −0
Original line number Diff line number Diff line
@@ -35,11 +35,16 @@ import static android.net.ConnectivityManager.TETHER_ERROR_PROVISION_FAILED;
import android.app.Activity;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.usage.UsageStatsManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.ResolveInfo;
import android.content.pm.PackageManager;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.res.Resources;
@@ -61,10 +66,16 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class TetherServiceTest extends ServiceTestCase<TetherService> {

    private static final String TAG = "TetherServiceTest";
    private static final String FAKE_PACKAGE_NAME = "com.some.package.name";
    private static final String ENTITLEMENT_PACKAGE_NAME = "com.some.entitlement.name";
    private static final String TEST_RESPONSE_ACTION = "testProvisioningResponseAction";
    private static final String TEST_NO_UI_ACTION = "testNoUiProvisioningRequestAction";
    private static final int BOGUS_RECEIVER_RESULT = -5;
@@ -75,6 +86,7 @@ public class TetherServiceTest extends ServiceTestCase<TetherService> {

    private TetherService mService;
    private MockResources mResources;
    private FakeUsageStatsManagerWrapper mUsageStatsManagerWrapper;
    int mLastReceiverResultCode = BOGUS_RECEIVER_RESULT;
    private int mLastTetherRequestType = TETHERING_INVALID;
    private int mProvisionResponse = BOGUS_RECEIVER_RESULT;
@@ -83,6 +95,7 @@ public class TetherServiceTest extends ServiceTestCase<TetherService> {

    @Mock private AlarmManager mAlarmManager;
    @Mock private ConnectivityManager mConnectivityManager;
    @Mock private PackageManager mPackageManager;
    @Mock private WifiManager mWifiManager;
    @Mock private SharedPreferences mPrefs;
    @Mock private Editor mPrefEditor;
@@ -115,6 +128,27 @@ public class TetherServiceTest extends ServiceTestCase<TetherService> {
        when(mPrefs.edit()).thenReturn(mPrefEditor);
        when(mPrefEditor.putString(eq(CURRENT_TYPES), mStoredTypes.capture())).thenReturn(
                mPrefEditor);
        mUsageStatsManagerWrapper = new FakeUsageStatsManagerWrapper(mContext);

        ResolveInfo systemAppResolveInfo = new ResolveInfo();
        ActivityInfo systemActivityInfo = new ActivityInfo();
        systemActivityInfo.packageName = ENTITLEMENT_PACKAGE_NAME;
        ApplicationInfo systemAppInfo = new ApplicationInfo();
        systemAppInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
        systemActivityInfo.applicationInfo = systemAppInfo;
        systemAppResolveInfo.activityInfo = systemActivityInfo;

        ResolveInfo nonSystemResolveInfo = new ResolveInfo();
        ActivityInfo nonSystemActivityInfo = new ActivityInfo();
        nonSystemActivityInfo.packageName = FAKE_PACKAGE_NAME;
        nonSystemActivityInfo.applicationInfo = new ApplicationInfo();
        nonSystemResolveInfo.activityInfo = nonSystemActivityInfo;

        List<ResolveInfo> resolvers = new ArrayList();
        resolvers.add(nonSystemResolveInfo);
        resolvers.add(systemAppResolveInfo);
        when(mPackageManager.queryBroadcastReceivers(
                any(Intent.class), eq(PackageManager.MATCH_ALL))).thenReturn(resolvers);
    }

    @Override
@@ -139,6 +173,19 @@ public class TetherServiceTest extends ServiceTestCase<TetherService> {
        assertTrue(waitForProvisionResponse(TETHER_ERROR_NO_ERROR));
    }

    public void testStartKeepsProvisionAppActive() {
        setupService();
        getService().setUsageStatsManagerWrapper(mUsageStatsManagerWrapper);

        runProvisioningForType(TETHERING_WIFI);

        assertTrue(waitForProvisionRequest(TETHERING_WIFI));
        assertTrue(waitForProvisionResponse(TETHER_ERROR_NO_ERROR));
        assertFalse(mUsageStatsManagerWrapper.isAppInactive(ENTITLEMENT_PACKAGE_NAME));
        // Non-system handler of the intent action should stay idle.
        assertTrue(mUsageStatsManagerWrapper.isAppInactive(FAKE_PACKAGE_NAME));
    }

    public void testScheduleRechecks() {
        Intent intent = new Intent();
        intent.putExtra(EXTRA_ADD_TETHER_TYPE, TETHERING_WIFI);
@@ -229,6 +276,19 @@ public class TetherServiceTest extends ServiceTestCase<TetherService> {
        startService(intent);
    }

    private boolean waitForAppInactive(UsageStatsManager usageStatsManager, String packageName) {
        long startTime = SystemClock.uptimeMillis();
        while (true) {
            if (usageStatsManager.isAppInactive(packageName)) {
                return true;
            }
            if ((SystemClock.uptimeMillis() - startTime) > PROVISION_TIMEOUT) {
                return false;
            }
            SystemClock.sleep(SHORT_TIMEOUT);
        }
    }

    private boolean waitForProvisionRequest(int expectedType) {
        long startTime = SystemClock.uptimeMillis();
        while (true) {
@@ -307,6 +367,11 @@ public class TetherServiceTest extends ServiceTestCase<TetherService> {
            return super.getSharedPreferences(name, mode);
        }

        @Override
        public PackageManager getPackageManager() {
            return mPackageManager;
        }

        @Override
        public Object getSystemService(String name) {
            if (ALARM_SERVICE.equals(name)) {
@@ -355,4 +420,27 @@ public class TetherServiceTest extends ServiceTestCase<TetherService> {
                    responseIntent, android.Manifest.permission.CONNECTIVITY_INTERNAL);
        }
    }

    private static class FakeUsageStatsManagerWrapper
            extends TetherService.UsageStatsManagerWrapper {
        private final Set<String> mActivePackages;

        FakeUsageStatsManagerWrapper(Context context) {
            super(context);
            mActivePackages = new HashSet<>();
        }

        @Override
        void setAppInactive(String packageName, boolean isInactive) {
            if (!isInactive) {
                mActivePackages.add(packageName);
            } else {
                mActivePackages.remove(packageName);
            }
        }

        boolean isAppInactive(String packageName) {
            return !mActivePackages.contains(packageName);
        }
    }
}