diff --git a/core/java/com/android/internal/util/WakeupMessage.java b/core/java/com/android/internal/util/WakeupMessage.java index 26537451992c6..7d222c74ac3be 100644 --- a/core/java/com/android/internal/util/WakeupMessage.java +++ b/core/java/com/android/internal/util/WakeupMessage.java @@ -45,24 +45,32 @@ public class WakeupMessage implements AlarmManager.OnAlarmListener { protected final String mCmdName; @VisibleForTesting protected final int mCmd, mArg1, mArg2; + @VisibleForTesting + protected final Object mObj; private boolean mScheduled; public WakeupMessage(Context context, Handler handler, - String cmdName, int cmd, int arg1, int arg2) { + String cmdName, int cmd, int arg1, int arg2, Object obj) { mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); mHandler = handler; mCmdName = cmdName; mCmd = cmd; mArg1 = arg1; mArg2 = arg2; + mObj = obj; } public WakeupMessage(Context context, Handler handler, String cmdName, int cmd, int arg1) { - this(context, handler, cmdName, cmd, arg1, 0); + this(context, handler, cmdName, cmd, arg1, 0, null); + } + + public WakeupMessage(Context context, Handler handler, + String cmdName, int cmd, int arg1, int arg2) { + this(context, handler, cmdName, cmd, arg1, arg2, null); } public WakeupMessage(Context context, Handler handler, String cmdName, int cmd) { - this(context, handler, cmdName, cmd, 0, 0); + this(context, handler, cmdName, cmd, 0, 0, null); } /** @@ -99,7 +107,7 @@ public class WakeupMessage implements AlarmManager.OnAlarmListener { mScheduled = false; } if (stillScheduled) { - Message msg = mHandler.obtainMessage(mCmd, mArg1, mArg2); + Message msg = mHandler.obtainMessage(mCmd, mArg1, mArg2, mObj); mHandler.handleMessage(msg); msg.recycle(); } diff --git a/core/tests/utiltests/src/com/android/internal/util/WakeupMessageTest.java b/core/tests/utiltests/src/com/android/internal/util/WakeupMessageTest.java index da8bc1d0945d9..7935880d12a91 100644 --- a/core/tests/utiltests/src/com/android/internal/util/WakeupMessageTest.java +++ b/core/tests/utiltests/src/com/android/internal/util/WakeupMessageTest.java @@ -45,6 +45,7 @@ public class WakeupMessageTest { private static final int TEST_CMD = 18; private static final int TEST_ARG1 = 33; private static final int TEST_ARG2 = 182; + private static final Object TEST_OBJ = "hello"; @Mock AlarmManager mAlarmManager; WakeupMessage mMessage; @@ -92,7 +93,7 @@ public class WakeupMessageTest { mListenerCaptor.capture(), any(Handler.class)); mMessage = new WakeupMessage(context, mHandler, TEST_CMD_NAME, TEST_CMD, TEST_ARG1, - TEST_ARG2); + TEST_ARG2, TEST_OBJ); } /** @@ -114,6 +115,7 @@ public class WakeupMessageTest { assertEquals("what", TEST_CMD, mHandler.getLastMessage().what); assertEquals("arg1", TEST_ARG1, mHandler.getLastMessage().arg1); assertEquals("arg2", TEST_ARG2, mHandler.getLastMessage().arg2); + assertEquals("obj", TEST_OBJ, mHandler.getLastMessage().obj); } /** diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 578bc8de0e5ab..edd95ff2eebb2 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -797,6 +797,17 @@ public class ConnectivityService extends IConnectivityManager.Stub return new NetworkRequest(netCap, TYPE_NONE, nextNetworkRequestId()); } + // Used only for testing. + // TODO: Delete this and either: + // 1. Give Fake SettingsProvider the ability to send settings change notifications (requires + // changing ContentResolver to make registerContentObserver non-final). + // 2. Give FakeSettingsProvider an alternative notification mechanism and have the test use it + // by subclassing SettingsObserver. + @VisibleForTesting + void updateMobileDataAlwaysOn() { + mHandler.sendEmptyMessage(EVENT_CONFIGURE_MOBILE_DATA_ALWAYS_ON); + } + private void handleMobileDataAlwaysOn() { final boolean enable = (Settings.Global.getInt( mContext.getContentResolver(), Settings.Global.MOBILE_DATA_ALWAYS_ON, 0) == 1); diff --git a/services/tests/servicestests/src/com/android/internal/util/FakeSettingsProvider.java b/services/tests/servicestests/src/com/android/internal/util/FakeSettingsProvider.java new file mode 100644 index 0000000000000..808f4dd7b84ef --- /dev/null +++ b/services/tests/servicestests/src/com/android/internal/util/FakeSettingsProvider.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2016 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.internal.util; + +import android.net.Uri; +import android.os.Bundle; +import android.provider.Settings; +import android.test.mock.MockContentProvider; +import android.util.Log; + +import java.util.HashMap; + +/** + * Fake for system settings. + * + * To use, ensure that the Context used by the test code returns a ContentResolver that uses this + * provider for the Settings authority: + * + * class MyTestContext extends MockContext { + * ... + * private final MockContentResolver mContentResolver; + * public MyTestContext(...) { + * ... + * mContentResolver = new MockContentResolver(); + * mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); + * } + * ... + * @Override + * public ContentResolver getContentResolver() { + * return mContentResolver; + * } + * + * As long as the code under test is using the test Context, the actual code under test does not + * need to be modified, and can access Settings using the normal static methods: + * + * Settings.Global.getInt(cr, "my_setting", 0); // Returns 0. + * Settings.Global.putInt(cr, "my_setting", 5); + * Settings.Global.getInt(cr, "my_setting", 0); // Returns 5. + * + * Note that this class cannot be used in the same process as real settings. This is because it + * works by passing an alternate ContentResolver to Settings operations. Unfortunately, the Settings + * class only fetches the content provider from the passed-in ContentResolver the first time it's + * used, and after that stores it in a per-process static. + * + * TODO: evaluate implementing settings change notifications. This would require: + * + * 1. Making ContentResolver#registerContentObserver non-final and overriding it in + * MockContentResolver. + * 2. Making FakeSettingsProvider take a ContentResolver argument. + * 3. Calling ContentResolver#notifyChange(getUriFor(table, arg), ...) on every settings change. + */ +public class FakeSettingsProvider extends MockContentProvider { + + private static final String TAG = FakeSettingsProvider.class.getSimpleName(); + private static final boolean DBG = false; + private static final String[] TABLES = { "system", "secure", "global" }; + + private final HashMap> mTables = new HashMap<>(); + + public FakeSettingsProvider() { + for (int i = 0; i < TABLES.length; i++) { + mTables.put(TABLES[i], new HashMap()); + } + } + + private Uri getUriFor(String table, String key) { + switch (table) { + case "system": + return Settings.System.getUriFor(key); + case "secure": + return Settings.Secure.getUriFor(key); + case "global": + return Settings.Global.getUriFor(key); + default: + throw new UnsupportedOperationException("Unknown settings table " + table); + } + } + + public Bundle call(String method, String arg, Bundle extras) { + // Methods are "GET_system", "GET_global", "PUT_secure", etc. + String[] commands = method.split("_", 2); + String op = commands[0]; + String table = commands[1]; + + Bundle out = new Bundle(); + String value; + switch (op) { + case "GET": + value = mTables.get(table).get(arg); + if (value != null) { + if (DBG) { + Log.d(TAG, String.format("Returning fake setting %s.%s = %s", + table, arg, value)); + } + out.putString(Settings.NameValueTable.VALUE, value); + } + break; + case "PUT": + value = extras.getString(Settings.NameValueTable.VALUE, null); + if (DBG) { + Log.d(TAG, String.format("Inserting fake setting %s.%s = %s", + table, arg, value)); + } + if (value != null) { + mTables.get(table).put(arg, value); + } else { + mTables.get(table).remove(arg); + } + break; + default: + throw new UnsupportedOperationException("Unknown command " + method); + } + + return out; + } +} diff --git a/services/tests/servicestests/src/com/android/internal/util/FakeSettingsProviderTest.java b/services/tests/servicestests/src/com/android/internal/util/FakeSettingsProviderTest.java new file mode 100644 index 0000000000000..a18737c42e529 --- /dev/null +++ b/services/tests/servicestests/src/com/android/internal/util/FakeSettingsProviderTest.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2016 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.internal.util; + +import android.content.ContentResolver; +import android.database.ContentObserver; +import android.net.Uri; +import android.provider.Settings; +import android.test.AndroidTestCase; +import android.test.mock.MockContentResolver; +import android.test.mock.MockContext; +import android.test.suitebuilder.annotation.SmallTest; +import android.util.Log; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/** + * Unit tests for FakeSettingsProvider. + */ +public class FakeSettingsProviderTest extends AndroidTestCase { + + private MockContentResolver mCr; + + @Override + public void setUp() throws Exception { + mCr = new MockContentResolver(); + mCr.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); + } + + @SmallTest + public void testBasicOperation() throws Exception { + String settingName = Settings.System.SCREEN_BRIGHTNESS; + + try { + Settings.System.getInt(mCr, settingName); + fail("FakeSettingsProvider should start off empty."); + } catch (Settings.SettingNotFoundException expected) {} + + // Check that fake settings can be written and read back. + Settings.System.putInt(mCr, settingName, 123); + assertEquals(123, Settings.System.getInt(mCr, settingName)); + } +} diff --git a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java index 4fae4a732aad3..68a35f1b9140c 100644 --- a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java @@ -26,6 +26,7 @@ import static org.mockito.Mockito.mock; import android.app.PendingIntent; import android.content.BroadcastReceiver; +import android.content.ContentResolver; import android.content.Context; import android.content.ContextWrapper; import android.content.Intent; @@ -61,12 +62,15 @@ import android.os.Messenger; import android.os.MessageQueue.IdleHandler; import android.os.Process; import android.os.SystemClock; +import android.provider.Settings; import android.test.AndroidTestCase; +import android.test.mock.MockContentResolver; import android.test.suitebuilder.annotation.LargeTest; import android.test.suitebuilder.annotation.SmallTest; import android.util.Log; import android.util.LogPrinter; +import com.android.internal.util.FakeSettingsProvider; import com.android.internal.util.WakeupMessage; import com.android.server.connectivity.NetworkAgentInfo; import com.android.server.connectivity.NetworkMonitor; @@ -118,27 +122,24 @@ public class ConnectivityServiceTest extends AndroidTestCase { } private class MockContext extends BroadcastInterceptingContext { + private final MockContentResolver mContentResolver; + MockContext(Context base) { super(base); + mContentResolver = new MockContentResolver(); + mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); } @Override - public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) { - // PendingIntents sent by the AlarmManager are not intercepted by - // BroadcastInterceptingContext so we must really register the receiver. - // This shouldn't effect the real NetworkMonitors as the action contains a random token. - if (filter.getAction(0).startsWith("android.net.netmon.lingerExpired")) { - return getBaseContext().registerReceiver(receiver, filter); - } else { - return super.registerReceiver(receiver, filter); - } - } - - @Override - public Object getSystemService (String name) { + public Object getSystemService(String name) { if (name == Context.CONNECTIVITY_SERVICE) return mCm; return super.getSystemService(name); } + + @Override + public ContentResolver getContentResolver() { + return mContentResolver; + } } /** @@ -641,7 +642,6 @@ public class ConnectivityServiceTest extends AndroidTestCase { public void waitForIdle() { waitForIdle(TIMEOUT_MS); } - } private interface Criteria { @@ -1477,6 +1477,73 @@ public class ConnectivityServiceTest extends AndroidTestCase { defaultNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent); } + @SmallTest + public void testMobileDataAlwaysOn() throws Exception { + final TestNetworkCallback cellNetworkCallback = new TestNetworkCallback(); + final NetworkRequest cellRequest = new NetworkRequest.Builder() + .addTransportType(TRANSPORT_CELLULAR).build(); + mCm.registerNetworkCallback(cellRequest, cellNetworkCallback); + + final HandlerThread handlerThread = new HandlerThread("MobileDataAlwaysOnFactory"); + handlerThread.start(); + NetworkCapabilities filter = new NetworkCapabilities() + .addTransportType(TRANSPORT_CELLULAR) + .addCapability(NET_CAPABILITY_INTERNET); + final MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(), + mServiceContext, "testFactory", filter); + testFactory.setScoreFilter(40); + + // Register the factory and expect it to start looking for a network. + testFactory.expectAddRequests(1); + testFactory.register(); + testFactory.waitForNetworkRequests(1); + assertTrue(testFactory.getMyStartRequested()); + + // Bring up wifi. The factory stops looking for a network. + mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + testFactory.expectAddRequests(2); // Because the default request changes score twice. + mWiFiNetworkAgent.connect(true); + testFactory.waitForNetworkRequests(1); + assertFalse(testFactory.getMyStartRequested()); + + ContentResolver cr = mServiceContext.getContentResolver(); + + // Turn on mobile data always on. The factory starts looking again. + testFactory.expectAddRequests(1); + Settings.Global.putInt(cr, Settings.Global.MOBILE_DATA_ALWAYS_ON, 1); + mService.updateMobileDataAlwaysOn(); + testFactory.waitForNetworkRequests(2); + assertTrue(testFactory.getMyStartRequested()); + + // Bring up cell data and check that the factory stops looking. + assertEquals(1, mCm.getAllNetworks().length); + mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); + testFactory.expectAddRequests(2); // Because the cell request changes score twice. + mCellNetworkAgent.connect(true); + cellNetworkCallback.expectCallback(CallbackState.AVAILABLE); + testFactory.waitForNetworkRequests(2); + assertFalse(testFactory.getMyStartRequested()); // Because the cell network outscores us. + + // Check that cell data stays up. + mService.waitForIdle(); + verifyActiveNetwork(TRANSPORT_WIFI); + assertEquals(2, mCm.getAllNetworks().length); + + // Turn off mobile data always on and expect the request to disappear... + testFactory.expectRemoveRequests(1); + Settings.Global.putInt(cr, Settings.Global.MOBILE_DATA_ALWAYS_ON, 0); + mService.updateMobileDataAlwaysOn(); + testFactory.waitForNetworkRequests(1); + + // ... and cell data to be torn down. + cellNetworkCallback.expectCallback(CallbackState.LOST); + assertEquals(1, mCm.getAllNetworks().length); + + testFactory.unregister(); + mCm.unregisterNetworkCallback(cellNetworkCallback); + handlerThread.quit(); + } + private static class TestKeepaliveCallback extends PacketKeepaliveCallback { public static enum CallbackType { ON_STARTED, ON_STOPPED, ON_ERROR };