Merge changes Ic98e3bcb,Idb0c55fc,Ia8649061,I5e7e5a87 into nyc-mr1-dev
* changes: Give WakeupMessage the ability to transport an object as well. Don't treat the lingerExpired broadcast specially. Add a test for mobile data always on. Add a FakeSettingsProvider and use it in ConnectivityServiceTest.
This commit is contained in:
committed by
Android (Google) Code Review
commit
3e2bfbd135
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<String, HashMap<String, String>> mTables = new HashMap<>();
|
||||
|
||||
public FakeSettingsProvider() {
|
||||
for (int i = 0; i < TABLES.length; i++) {
|
||||
mTables.put(TABLES[i], new HashMap<String, String>());
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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 };
|
||||
|
||||
Reference in New Issue
Block a user