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:
TreeHugger Robot
2016-06-08 06:35:31 +00:00
committed by Android (Google) Code Review
6 changed files with 295 additions and 19 deletions

View File

@@ -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();
}

View File

@@ -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);
}
/**

View File

@@ -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);

View File

@@ -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;
}
}

View File

@@ -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));
}
}

View File

@@ -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 };