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
diff --git a/src/com/android/settings/TetherService.java b/src/com/android/settings/TetherService.java
index ad0e41f..6d359f2 100644
--- a/src/com/android/settings/TetherService.java
+++ b/src/com/android/settings/TetherService.java
@@ -20,6 +20,7 @@
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.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 @@
private int mCurrentTypeIndex;
private boolean mInProvisionCheck;
+ private UsageStatsManagerWrapper mUsageManagerWrapper;
private ArrayList<Integer> mCurrentTethers;
private ArrayMap<Integer, List<ResultReceiver>> mPendingCallbacks;
@@ -87,6 +91,9 @@
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,20 +235,46 @@
private void startProvisioning(int index) {
if (index < mCurrentTethers.size()) {
- 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);
+ 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);
+ Intent intent = new Intent(provisionAction);
+ int type = mCurrentTethers.get(index);
+ intent.putExtra(TETHER_CHOICE, type);
+ intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+
+ 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);
+ }
+ }
+ }
+
private void scheduleAlarm() {
Intent intent = new Intent(this, TetherService.class);
intent.putExtra(ConnectivityManager.EXTRA_RUN_PROVISION, true);
@@ -335,4 +368,26 @@
}
};
+ @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);
+ }
+ }
}
diff --git a/tests/unit/src/com/android/settings/TetherServiceTest.java b/tests/unit/src/com/android/settings/TetherServiceTest.java
index 09c6119..e2bb5f8 100644
--- a/tests/unit/src/com/android/settings/TetherServiceTest.java
+++ b/tests/unit/src/com/android/settings/TetherServiceTest.java
@@ -35,11 +35,16 @@
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.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 @@
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 @@
@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 @@
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 @@
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 @@
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) {
@@ -308,6 +368,11 @@
}
@Override
+ public PackageManager getPackageManager() {
+ return mPackageManager;
+ }
+
+ @Override
public Object getSystemService(String name) {
if (ALARM_SERVICE.equals(name)) {
return mAlarmManager;
@@ -355,4 +420,27 @@
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);
+ }
+ }
}