diff --git a/src/com/android/settings/TetherService.java b/src/com/android/settings/TetherService.java index ad0e41f44b9..6d359f2de17 100644 --- a/src/com/android/settings/TetherService.java +++ b/src/com/android/settings/TetherService.java @@ -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 mCurrentTethers; private ArrayMap> mPendingCallbacks; @@ -87,6 +91,9 @@ public class TetherService extends Service { mPendingCallbacks.put(ConnectivityManager.TETHERING_USB, new ArrayList()); mPendingCallbacks.put( ConnectivityManager.TETHERING_BLUETOOTH, new ArrayList()); + if (mUsageManagerWrapper == null) { + mUsageManagerWrapper = new UsageStatsManagerWrapper(this); + } } @Override @@ -228,20 +235,46 @@ public class TetherService extends Service { 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 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 @@ 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); + } + } } diff --git a/tests/unit/src/com/android/settings/TetherServiceTest.java b/tests/unit/src/com/android/settings/TetherServiceTest.java index 09c6119bec6..e2bb5f8824e 100644 --- a/tests/unit/src/com/android/settings/TetherServiceTest.java +++ b/tests/unit/src/com/android/settings/TetherServiceTest.java @@ -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 { 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 { 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 { @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 { 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 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 { 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 { 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 { 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 { responseIntent, android.Manifest.permission.CONNECTIVITY_INTERNAL); } } + + private static class FakeUsageStatsManagerWrapper + extends TetherService.UsageStatsManagerWrapper { + private final Set 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); + } + } }