Merge "Instrument captive portal login activity"
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 6faded53..204ef80 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -116,7 +116,6 @@
import com.android.internal.content.ReferrerIntent;
import com.android.internal.os.BinderInternal;
import com.android.internal.os.RuntimeInit;
-import com.android.internal.os.SamplingProfilerIntegration;
import com.android.internal.os.SomeArgs;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastPrintWriter;
@@ -1500,7 +1499,6 @@
handlePauseActivity((IBinder) args.arg1, false,
(args.argi1 & USER_LEAVING) != 0, args.argi2,
(args.argi1 & DONT_REPORT) != 0, args.argi3);
- maybeSnapshot();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
} break;
case PAUSE_ACTIVITY_FINISHING: {
@@ -1570,7 +1568,6 @@
case RECEIVER:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "broadcastReceiveComp");
handleReceiver((ReceiverData)msg.obj);
- maybeSnapshot();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case CREATE_SERVICE:
@@ -1596,7 +1593,6 @@
case STOP_SERVICE:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceStop");
handleStopService((IBinder)msg.obj);
- maybeSnapshot();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case CONFIGURATION_CHANGED:
@@ -1737,32 +1733,6 @@
}
if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + codeToString(msg.what));
}
-
- private void maybeSnapshot() {
- if (mBoundApplication != null && SamplingProfilerIntegration.isEnabled()) {
- // convert the *private* ActivityThread.PackageInfo to *public* known
- // android.content.pm.PackageInfo
- String packageName = mBoundApplication.info.mPackageName;
- android.content.pm.PackageInfo packageInfo = null;
- try {
- Context context = getSystemContext();
- if(context == null) {
- Log.e(TAG, "cannot get a valid context");
- return;
- }
- PackageManager pm = context.getPackageManager();
- if(pm == null) {
- Log.e(TAG, "cannot get a valid PackageManager");
- return;
- }
- packageInfo = pm.getPackageInfo(
- packageName, PackageManager.GET_ACTIVITIES);
- } catch (NameNotFoundException e) {
- Log.e(TAG, "cannot get package info for " + packageName, e);
- }
- SamplingProfilerIntegration.writeSnapshot(mBoundApplication.processName, packageInfo);
- }
- }
}
private class Idler implements MessageQueue.IdleHandler {
@@ -6123,7 +6093,6 @@
public static void main(String[] args) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
- SamplingProfilerIntegration.start();
// CloseGuard defaults to true and can be quite spammy. We
// disable it here, but selectively enable it later (via
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index f158f5f..6bc88b0 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -1669,7 +1669,7 @@
*/
public BluetoothGatt connectGatt(Context context, boolean autoConnect,
BluetoothGattCallback callback, int transport) {
- return (connectGatt(context, autoConnect,callback, TRANSPORT_AUTO, PHY_LE_1M_MASK));
+ return (connectGatt(context, autoConnect,callback, transport, PHY_LE_1M_MASK));
}
/**
@@ -1693,7 +1693,7 @@
*/
public BluetoothGatt connectGatt(Context context, boolean autoConnect,
BluetoothGattCallback callback, int transport, int phy) {
- return connectGatt(context, autoConnect,callback, TRANSPORT_AUTO, PHY_LE_1M_MASK, null);
+ return connectGatt(context, autoConnect,callback, transport, phy, null);
}
/**
diff --git a/core/java/com/android/internal/os/SamplingProfilerIntegration.java b/core/java/com/android/internal/os/SamplingProfilerIntegration.java
deleted file mode 100644
index 6429aa4..0000000
--- a/core/java/com/android/internal/os/SamplingProfilerIntegration.java
+++ /dev/null
@@ -1,226 +0,0 @@
-/*
- * Copyright (C) 2009 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.os;
-
-import android.content.pm.PackageInfo;
-import android.os.Build;
-import android.os.SystemProperties;
-import android.util.Log;
-import dalvik.system.profiler.BinaryHprofWriter;
-import dalvik.system.profiler.SamplingProfiler;
-import java.io.BufferedOutputStream;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.PrintStream;
-import java.util.Date;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.atomic.AtomicBoolean;
-import libcore.io.IoUtils;
-
-/**
- * Integrates the framework with Dalvik's sampling profiler.
- */
-public class SamplingProfilerIntegration {
-
- private static final String TAG = "SamplingProfilerIntegration";
-
- public static final String SNAPSHOT_DIR = "/data/snapshots";
-
- private static final boolean enabled;
- private static final Executor snapshotWriter;
- private static final int samplingProfilerMilliseconds;
- private static final int samplingProfilerDepth;
-
- /** Whether or not a snapshot is being persisted. */
- private static final AtomicBoolean pending = new AtomicBoolean(false);
-
- static {
- samplingProfilerMilliseconds = SystemProperties.getInt("persist.sys.profiler_ms", 0);
- samplingProfilerDepth = SystemProperties.getInt("persist.sys.profiler_depth", 4);
- if (samplingProfilerMilliseconds > 0) {
- File dir = new File(SNAPSHOT_DIR);
- dir.mkdirs();
- // the directory needs to be writable to anybody to allow file writing
- dir.setWritable(true, false);
- // the directory needs to be executable to anybody to allow file creation
- dir.setExecutable(true, false);
- if (dir.isDirectory()) {
- snapshotWriter = Executors.newSingleThreadExecutor(new ThreadFactory() {
- public Thread newThread(Runnable r) {
- return new Thread(r, TAG);
- }
- });
- enabled = true;
- Log.i(TAG, "Profiling enabled. Sampling interval ms: "
- + samplingProfilerMilliseconds);
- } else {
- snapshotWriter = null;
- enabled = true;
- Log.w(TAG, "Profiling setup failed. Could not create " + SNAPSHOT_DIR);
- }
- } else {
- snapshotWriter = null;
- enabled = false;
- Log.i(TAG, "Profiling disabled.");
- }
- }
-
- private static SamplingProfiler samplingProfiler;
- private static long startMillis;
-
- /**
- * Is profiling enabled?
- */
- public static boolean isEnabled() {
- return enabled;
- }
-
- /**
- * Starts the profiler if profiling is enabled.
- */
- public static void start() {
- if (!enabled) {
- return;
- }
- if (samplingProfiler != null) {
- Log.e(TAG, "SamplingProfilerIntegration already started at " + new Date(startMillis));
- return;
- }
-
- ThreadGroup group = Thread.currentThread().getThreadGroup();
- SamplingProfiler.ThreadSet threadSet = SamplingProfiler.newThreadGroupThreadSet(group);
- samplingProfiler = new SamplingProfiler(samplingProfilerDepth, threadSet);
- samplingProfiler.start(samplingProfilerMilliseconds);
- startMillis = System.currentTimeMillis();
- }
-
- /**
- * Writes a snapshot if profiling is enabled.
- */
- public static void writeSnapshot(final String processName, final PackageInfo packageInfo) {
- if (!enabled) {
- return;
- }
- if (samplingProfiler == null) {
- Log.e(TAG, "SamplingProfilerIntegration is not started");
- return;
- }
-
- /*
- * If we're already writing a snapshot, don't bother enqueueing another
- * request right now. This will reduce the number of individual
- * snapshots and in turn the total amount of memory consumed (one big
- * snapshot is smaller than N subset snapshots).
- */
- if (pending.compareAndSet(false, true)) {
- snapshotWriter.execute(new Runnable() {
- public void run() {
- try {
- writeSnapshotFile(processName, packageInfo);
- } finally {
- pending.set(false);
- }
- }
- });
- }
- }
-
- /**
- * Writes the zygote's snapshot to internal storage if profiling is enabled.
- */
- public static void writeZygoteSnapshot() {
- if (!enabled) {
- return;
- }
- writeSnapshotFile("zygote", null);
- samplingProfiler.shutdown();
- samplingProfiler = null;
- startMillis = 0;
- }
-
- /**
- * pass in PackageInfo to retrieve various values for snapshot header
- */
- private static void writeSnapshotFile(String processName, PackageInfo packageInfo) {
- if (!enabled) {
- return;
- }
- samplingProfiler.stop();
-
- /*
- * We use the global start time combined with the process name
- * as a unique ID. We can't use a counter because processes
- * restart. This could result in some overlap if we capture
- * two snapshots in rapid succession.
- */
- String name = processName.replaceAll(":", ".");
- String path = SNAPSHOT_DIR + "/" + name + "-" + startMillis + ".snapshot";
- long start = System.currentTimeMillis();
- OutputStream outputStream = null;
- try {
- outputStream = new BufferedOutputStream(new FileOutputStream(path));
- PrintStream out = new PrintStream(outputStream);
- generateSnapshotHeader(name, packageInfo, out);
- if (out.checkError()) {
- throw new IOException();
- }
- BinaryHprofWriter.write(samplingProfiler.getHprofData(), outputStream);
- } catch (IOException e) {
- Log.e(TAG, "Error writing snapshot to " + path, e);
- return;
- } finally {
- IoUtils.closeQuietly(outputStream);
- }
- // set file readable to the world so that SamplingProfilerService
- // can put it to dropbox
- new File(path).setReadable(true, false);
-
- long elapsed = System.currentTimeMillis() - start;
- Log.i(TAG, "Wrote snapshot " + path + " in " + elapsed + "ms.");
- samplingProfiler.start(samplingProfilerMilliseconds);
- }
-
- /**
- * generate header for snapshots, with the following format
- * (like an HTTP header but without the \r):
- *
- * Version: <version number of profiler>\n
- * Process: <process name>\n
- * Package: <package name, if exists>\n
- * Package-Version: <version number of the package, if exists>\n
- * Build: <fingerprint>\n
- * \n
- * <the actual snapshot content begins here...>
- */
- private static void generateSnapshotHeader(String processName, PackageInfo packageInfo,
- PrintStream out) {
- // profiler version
- out.println("Version: 3");
- out.println("Process: " + processName);
- if (packageInfo != null) {
- out.println("Package: " + packageInfo.packageName);
- out.println("Package-Version: " + packageInfo.versionCode);
- }
- out.println("Build: " + Build.FINGERPRINT);
- // single blank line means the end of snapshot header.
- out.println();
- }
-}
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 1d73c79..b7dff96 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -167,8 +167,8 @@
private static void preloadOpenGL() {
String driverPackageName = SystemProperties.get(PROPERTY_GFX_DRIVER);
- if (!SystemProperties.getBoolean(PROPERTY_DISABLE_OPENGL_PRELOADING, false) ||
- driverPackageName == null || driverPackageName.isEmpty()) {
+ if (!SystemProperties.getBoolean(PROPERTY_DISABLE_OPENGL_PRELOADING, false) &&
+ (driverPackageName == null || driverPackageName.isEmpty())) {
EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
}
}
@@ -648,8 +648,6 @@
try {
Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "ZygoteInit");
RuntimeInit.enableDdms();
- // Start profiling the zygote initialization.
- SamplingProfilerIntegration.start();
boolean startSystemServer = false;
String socketName = "zygote";
@@ -679,9 +677,6 @@
SystemClock.uptimeMillis());
Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
- // Finish profiling the zygote initialization.
- SamplingProfilerIntegration.writeZygoteSnapshot();
-
// Do an initial gc to clean up after startup
Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PostZygoteInitGC");
gcAndFinalize();
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 07f1b45..81504c0 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -620,6 +620,7 @@
char useJitProfilesOptsBuf[sizeof("-Xjitsaveprofilinginfo:")-1 + PROPERTY_VALUE_MAX];
char jitprithreadweightOptBuf[sizeof("-Xjitprithreadweight:")-1 + PROPERTY_VALUE_MAX];
char jittransitionweightOptBuf[sizeof("-Xjittransitionweight:")-1 + PROPERTY_VALUE_MAX];
+ char hotstartupsamplesOptsBuf[sizeof("-Xps-hot-startup-method-samples:")-1 + PROPERTY_VALUE_MAX];
char gctypeOptsBuf[sizeof("-Xgc:")-1 + PROPERTY_VALUE_MAX];
char backgroundgcOptsBuf[sizeof("-XX:BackgroundGC=")-1 + PROPERTY_VALUE_MAX];
char heaptargetutilizationOptsBuf[sizeof("-XX:HeapTargetUtilization=")-1 + PROPERTY_VALUE_MAX];
@@ -746,6 +747,12 @@
jittransitionweightOptBuf,
"-Xjittransitionweight:");
+ /*
+ * Profile related options.
+ */
+ parseRuntimeOption("dalvik.vm.hot-startup-method-samples", hotstartupsamplesOptsBuf,
+ "-Xps-hot-startup-method-samples:");
+
property_get("ro.config.low_ram", propBuf, "");
if (strcmp(propBuf, "true") == 0) {
addOption("-XX:LowMemoryMode");
diff --git a/packages/CaptivePortalLogin/res/values/strings.xml b/packages/CaptivePortalLogin/res/values/strings.xml
index b1a3852..f486fe4 100644
--- a/packages/CaptivePortalLogin/res/values/strings.xml
+++ b/packages/CaptivePortalLogin/res/values/strings.xml
@@ -5,6 +5,7 @@
<string name="action_use_network">Use this network as is</string>
<string name="action_do_not_use_network">Do not use this network</string>
<string name="action_bar_label">Sign in to network</string>
+ <string name="action_bar_title">Sign in to %1$s</string>
<string name="ssl_error_warning">The network you’re trying to join has security issues.</string>
<string name="ssl_error_example">For example, the login page may not belong to the organization shown.</string>
<string name="ssl_error_continue">Continue anyway via browser</string>
diff --git a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
index 75c71aa..71d9fc8 100644
--- a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
+++ b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
@@ -26,6 +26,7 @@
import android.net.ConnectivityManager.NetworkCallback;
import android.net.Network;
import android.net.NetworkCapabilities;
+import android.net.NetworkInfo;
import android.net.NetworkRequest;
import android.net.Proxy;
import android.net.Uri;
@@ -484,7 +485,15 @@
}
private String getHeaderTitle() {
- return getString(R.string.action_bar_label);
+ NetworkInfo info = mCm.getNetworkInfo(mNetwork);
+ if (info == null) {
+ return getString(R.string.action_bar_label);
+ }
+ NetworkCapabilities nc = mCm.getNetworkCapabilities(mNetwork);
+ if (!nc.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
+ return getString(R.string.action_bar_label);
+ }
+ return getString(R.string.action_bar_title, info.getExtraInfo().replaceAll("^\"|\"$", ""));
}
private String getHeaderSubtitle(String urlString) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
index f743a4b..dbe2b2e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
@@ -312,7 +312,9 @@
protected abstract void setListening(boolean listening);
protected void handleDestroy() {
- setListening(false);
+ if (mListeners.size() != 0) {
+ setListening(false);
+ }
mCallbacks.clear();
}
diff --git a/services/core/java/com/android/server/SamplingProfilerService.java b/services/core/java/com/android/server/SamplingProfilerService.java
deleted file mode 100644
index fbf1aa4..0000000
--- a/services/core/java/com/android/server/SamplingProfilerService.java
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright (C) 2010 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.server;
-
-import android.content.ContentResolver;
-import android.os.DropBoxManager;
-import android.os.FileObserver;
-import android.os.Binder;
-
-import android.util.Slog;
-import android.content.Context;
-import android.database.ContentObserver;
-import android.os.SystemProperties;
-import android.provider.Settings;
-import com.android.internal.os.SamplingProfilerIntegration;
-
-import java.io.File;
-import java.io.FileDescriptor;
-import java.io.IOException;
-import java.io.PrintWriter;
-
-public class SamplingProfilerService extends Binder {
-
- private static final String TAG = "SamplingProfilerService";
- private static final boolean LOCAL_LOGV = false;
- public static final String SNAPSHOT_DIR = SamplingProfilerIntegration.SNAPSHOT_DIR;
-
- private final Context mContext;
- private FileObserver snapshotObserver;
-
- public SamplingProfilerService(Context context) {
- mContext = context;
- registerSettingObserver(context);
- startWorking(context);
- }
-
- private void startWorking(Context context) {
- if (LOCAL_LOGV) Slog.v(TAG, "starting SamplingProfilerService!");
-
- final DropBoxManager dropbox =
- (DropBoxManager) context.getSystemService(Context.DROPBOX_SERVICE);
-
- // before FileObserver is ready, there could have already been some snapshots
- // in the directory, we don't want to miss them
- File[] snapshotFiles = new File(SNAPSHOT_DIR).listFiles();
- for (int i = 0; snapshotFiles != null && i < snapshotFiles.length; i++) {
- handleSnapshotFile(snapshotFiles[i], dropbox);
- }
-
- // detect new snapshot and put it in dropbox
- // delete it afterwards no matter what happened before
- // Note: needs listening at event ATTRIB rather than CLOSE_WRITE, because we set the
- // readability of snapshot files after writing them!
- snapshotObserver = new FileObserver(SNAPSHOT_DIR, FileObserver.ATTRIB) {
- @Override
- public void onEvent(int event, String path) {
- handleSnapshotFile(new File(SNAPSHOT_DIR, path), dropbox);
- }
- };
- snapshotObserver.startWatching();
-
- if (LOCAL_LOGV) Slog.v(TAG, "SamplingProfilerService activated");
- }
-
- private void handleSnapshotFile(File file, DropBoxManager dropbox) {
- try {
- dropbox.addFile(TAG, file, 0);
- if (LOCAL_LOGV) Slog.v(TAG, file.getPath() + " added to dropbox");
- } catch (IOException e) {
- Slog.e(TAG, "Can't add " + file.getPath() + " to dropbox", e);
- } finally {
- file.delete();
- }
- }
-
- private void registerSettingObserver(Context context) {
- ContentResolver contentResolver = context.getContentResolver();
- contentResolver.registerContentObserver(
- Settings.Global.getUriFor(Settings.Global.SAMPLING_PROFILER_MS),
- false, new SamplingProfilerSettingsObserver(contentResolver));
- }
-
- @Override
- protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
-
- pw.println("SamplingProfilerService:");
- pw.println("Watching directory: " + SNAPSHOT_DIR);
- }
-
- private class SamplingProfilerSettingsObserver extends ContentObserver {
- private ContentResolver mContentResolver;
- public SamplingProfilerSettingsObserver(ContentResolver contentResolver) {
- super(null);
- mContentResolver = contentResolver;
- onChange(false);
- }
- @Override
- public void onChange(boolean selfChange) {
- Integer samplingProfilerMs = Settings.Global.getInt(
- mContentResolver, Settings.Global.SAMPLING_PROFILER_MS, 0);
- // setting this secure property will start or stop sampling profiler,
- // as well as adjust the the time between taking snapshots.
- SystemProperties.set("persist.sys.profiler_ms", samplingProfilerMs.toString());
- }
- }
-}
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index 4858dfa..08df271 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -1232,8 +1232,8 @@
protected void chooseUpstreamType(boolean tryCell) {
updateConfiguration(); // TODO - remove?
- final int upstreamType = findPreferredUpstreamType(
- getConnectivityManager(), mConfig);
+ final int upstreamType = mUpstreamNetworkMonitor.selectPreferredUpstreamType(
+ mConfig.preferredUpstreamIfaceTypes);
if (upstreamType == ConnectivityManager.TYPE_NONE) {
if (tryCell) {
mUpstreamNetworkMonitor.registerMobileNetworkRequest();
@@ -1245,58 +1245,6 @@
setUpstreamByType(upstreamType);
}
- // TODO: Move this function into UpstreamNetworkMonitor.
- protected int findPreferredUpstreamType(ConnectivityManager cm,
- TetheringConfiguration cfg) {
- int upType = ConnectivityManager.TYPE_NONE;
-
- if (VDBG) {
- Log.d(TAG, "chooseUpstreamType has upstream iface types:");
- for (Integer netType : cfg.preferredUpstreamIfaceTypes) {
- Log.d(TAG, " " + netType);
- }
- }
-
- for (Integer netType : cfg.preferredUpstreamIfaceTypes) {
- NetworkInfo info = cm.getNetworkInfo(netType.intValue());
- // TODO: if the network is suspended we should consider
- // that to be the same as connected here.
- if ((info != null) && info.isConnected()) {
- upType = netType.intValue();
- break;
- }
- }
-
- final int preferredUpstreamMobileApn = cfg.isDunRequired
- ? ConnectivityManager.TYPE_MOBILE_DUN
- : ConnectivityManager.TYPE_MOBILE_HIPRI;
- mLog.log(String.format(
- "findPreferredUpstreamType(), preferredApn=%s, got type=%s",
- getNetworkTypeName(preferredUpstreamMobileApn),
- getNetworkTypeName(upType)));
-
- switch (upType) {
- case ConnectivityManager.TYPE_MOBILE_DUN:
- case ConnectivityManager.TYPE_MOBILE_HIPRI:
- // If we're on DUN, put our own grab on it.
- mUpstreamNetworkMonitor.registerMobileNetworkRequest();
- break;
- case ConnectivityManager.TYPE_NONE:
- break;
- default:
- /* If we've found an active upstream connection that's not DUN/HIPRI
- * we should stop any outstanding DUN/HIPRI start requests.
- *
- * If we found NONE we don't want to do this as we want any previous
- * requests to keep trying to bring up something we can use.
- */
- mUpstreamNetworkMonitor.releaseMobileNetworkRequest();
- break;
- }
-
- return upType;
- }
-
protected void setUpstreamByType(int upType) {
final ConnectivityManager cm = getConnectivityManager();
Network network = null;
diff --git a/services/core/java/com/android/server/connectivity/tethering/OffloadController.java b/services/core/java/com/android/server/connectivity/tethering/OffloadController.java
index c64e705..cb50e9f 100644
--- a/services/core/java/com/android/server/connectivity/tethering/OffloadController.java
+++ b/services/core/java/com/android/server/connectivity/tethering/OffloadController.java
@@ -20,10 +20,15 @@
import android.content.ContentResolver;
import android.net.LinkProperties;
+import android.net.RouteInfo;
import android.net.util.SharedLog;
import android.os.Handler;
import android.provider.Settings;
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.util.ArrayList;
+
/**
* A class to encapsulate the business logic of programming the tethering
* hardware offload interface.
@@ -92,8 +97,12 @@
public void setUpstreamLinkProperties(LinkProperties lp) {
if (!started()) return;
- // TODO: setUpstreamParameters().
- mUpstreamLinkProperties = lp;
+ mUpstreamLinkProperties = (lp != null) ? new LinkProperties(lp) : null;
+ // TODO: examine return code and decide what to do if programming
+ // upstream parameters fails (probably just wait for a subsequent
+ // onOffloadEvent() callback to tell us offload is available again and
+ // then reapply all state).
+ pushUpstreamParameters();
}
// TODO: public void addDownStream(...)
@@ -106,4 +115,40 @@
private boolean started() {
return mConfigInitialized && mControlInitialized;
}
+
+ private boolean pushUpstreamParameters() {
+ if (mUpstreamLinkProperties == null) {
+ return mHwInterface.setUpstreamParameters(null, null, null, null);
+ }
+
+ // A stacked interface cannot be an upstream for hardware offload.
+ // Consequently, we examine only the primary interface name, look at
+ // getAddresses() rather than getAllAddresses(), and check getRoutes()
+ // rather than getAllRoutes().
+ final String iface = mUpstreamLinkProperties.getInterfaceName();
+ final ArrayList<String> v6gateways = new ArrayList<>();
+ String v4addr = null;
+ String v4gateway = null;
+
+ for (InetAddress ip : mUpstreamLinkProperties.getAddresses()) {
+ if (ip instanceof Inet4Address) {
+ v4addr = ip.getHostAddress();
+ break;
+ }
+ }
+
+ // Find the gateway addresses of all default routes of either address family.
+ for (RouteInfo ri : mUpstreamLinkProperties.getRoutes()) {
+ if (!ri.hasGateway()) continue;
+
+ final String gateway = ri.getGateway().getHostAddress();
+ if (ri.isIPv4Default()) {
+ v4gateway = gateway;
+ } else if (ri.isIPv6Default()) {
+ v6gateways.add(gateway);
+ }
+ }
+
+ return mHwInterface.setUpstreamParameters(iface, v4addr, v4gateway, v6gateways);
+ }
}
diff --git a/services/core/java/com/android/server/connectivity/tethering/OffloadHardwareInterface.java b/services/core/java/com/android/server/connectivity/tethering/OffloadHardwareInterface.java
index 0429ab3..3ecf0d1 100644
--- a/services/core/java/com/android/server/connectivity/tethering/OffloadHardwareInterface.java
+++ b/services/core/java/com/android/server/connectivity/tethering/OffloadHardwareInterface.java
@@ -23,6 +23,8 @@
import android.os.RemoteException;
import android.net.util.SharedLog;
+import java.util.ArrayList;
+
/**
* Capture tethering dependencies, for injection.
@@ -103,6 +105,25 @@
mControlCallback = null;
}
+ public boolean setUpstreamParameters(
+ String iface, String v4addr, String v4gateway, ArrayList<String> v6gws) {
+ final CbResults results = new CbResults();
+ try {
+ mOffloadControl.setUpstreamParameters(
+ iface, v4addr, v4gateway, v6gws,
+ (boolean success, String errMsg) -> {
+ results.success = success;
+ results.errMsg = errMsg;
+ });
+ } catch (RemoteException e) {
+ mLog.e("failed to setUpstreamParameters: " + e);
+ return false;
+ }
+
+ if (!results.success) mLog.e("setUpstreamParameters failed: " + results.errMsg);
+ return results.success;
+ }
+
private static class TetheringOffloadCallback extends ITetheringOffloadCallback.Stub {
public final Handler handler;
public final ControlCallback controlCb;
diff --git a/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java b/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
index cd6038f..b2d5051 100644
--- a/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
+++ b/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
@@ -16,6 +16,8 @@
package com.android.server.connectivity.tethering;
+import static android.net.ConnectivityManager.getNetworkTypeName;
+import static android.net.ConnectivityManager.TYPE_NONE;
import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
@@ -176,6 +178,41 @@
return (network != null) ? mNetworkMap.get(network) : null;
}
+ // So many TODOs here, but chief among them is: make this functionality an
+ // integral part of this class such that whenever a higher priority network
+ // becomes available and useful we (a) file a request to keep it up as
+ // necessary and (b) change all upstream tracking state accordingly (by
+ // passing LinkProperties up to Tethering).
+ //
+ // Next TODO: return NetworkState instead of just the type.
+ public int selectPreferredUpstreamType(Iterable<Integer> preferredTypes) {
+ final TypeStatePair typeStatePair = findFirstAvailableUpstreamByType(
+ mNetworkMap.values(), preferredTypes);
+
+ mLog.log("preferred upstream type: " + getNetworkTypeName(typeStatePair.type));
+
+ switch (typeStatePair.type) {
+ case TYPE_MOBILE_DUN:
+ case TYPE_MOBILE_HIPRI:
+ // If we're on DUN, put our own grab on it.
+ registerMobileNetworkRequest();
+ break;
+ case TYPE_NONE:
+ break;
+ default:
+ /* If we've found an active upstream connection that's not DUN/HIPRI
+ * we should stop any outstanding DUN/HIPRI start requests.
+ *
+ * If we found NONE we don't want to do this as we want any previous
+ * requests to keep trying to bring up something we can use.
+ */
+ releaseMobileNetworkRequest();
+ break;
+ }
+
+ return typeStatePair.type;
+ }
+
private void handleAvailable(int callbackType, Network network) {
if (VDBG) Log.d(TAG, "EVENT_ON_AVAILABLE for " + network);
@@ -365,4 +402,37 @@
private void notifyTarget(int which, NetworkState netstate) {
mTarget.sendMessage(mWhat, which, 0, netstate);
}
+
+ static private class TypeStatePair {
+ public int type = TYPE_NONE;
+ public NetworkState ns = null;
+ }
+
+ static private TypeStatePair findFirstAvailableUpstreamByType(
+ Iterable<NetworkState> netStates, Iterable<Integer> preferredTypes) {
+ final TypeStatePair result = new TypeStatePair();
+
+ for (int type : preferredTypes) {
+ NetworkCapabilities nc;
+ try {
+ nc = ConnectivityManager.networkCapabilitiesForType(type);
+ } catch (IllegalArgumentException iae) {
+ Log.e(TAG, "No NetworkCapabilities mapping for legacy type: " +
+ ConnectivityManager.getNetworkTypeName(type));
+ continue;
+ }
+
+ for (NetworkState value : netStates) {
+ if (!nc.satisfiedByNetworkCapabilities(value.networkCapabilities)) {
+ continue;
+ }
+
+ result.type = type;
+ result.ns = value;
+ return result;
+ }
+ }
+
+ return result;
+ }
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index e1cbc91..99ad00b 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -51,7 +51,6 @@
import com.android.internal.R;
import com.android.internal.app.NightDisplayController;
import com.android.internal.os.BinderInternal;
-import com.android.internal.os.SamplingProfilerIntegration;
import com.android.internal.os.ZygoteInit;
import com.android.internal.policy.EmergencyAffordanceManager;
import com.android.internal.widget.ILockSettings;
@@ -281,18 +280,6 @@
// the property. http://b/11463182
SystemProperties.set("persist.sys.dalvik.vm.lib.2", VMRuntime.getRuntime().vmLibrary());
- // Enable the sampling profiler.
- if (SamplingProfilerIntegration.isEnabled()) {
- SamplingProfilerIntegration.start();
- mProfilerSnapshotTimer = new Timer();
- mProfilerSnapshotTimer.schedule(new TimerTask() {
- @Override
- public void run() {
- SamplingProfilerIntegration.writeSnapshot("system_server", null);
- }
- }, SNAPSHOT_INTERVAL, SNAPSHOT_INTERVAL);
- }
-
// Mmmmmm... more memory!
VMRuntime.getRuntime().clearGrowthLimit();
@@ -568,8 +555,6 @@
false);
boolean disableTextServices = SystemProperties.getBoolean("config.disable_textservices",
false);
- boolean disableSamplingProfiler = SystemProperties.getBoolean("config.disable_samplingprof",
- false);
boolean disableConsumerIr = SystemProperties.getBoolean("config.disable_consumerir", false);
boolean disableVrManager = SystemProperties.getBoolean("config.disable_vrmanager", false);
boolean disableCameraService = SystemProperties.getBoolean("config.disable_cameraservice",
@@ -1088,21 +1073,6 @@
}
Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
- if (!disableSamplingProfiler) {
- traceBeginAndSlog("StartSamplingProfilerService");
- try {
- // need to add this service even if SamplingProfilerIntegration.isEnabled()
- // is false, because it is this service that detects system property change and
- // turns on SamplingProfilerIntegration. Plus, when sampling profiler doesn't work,
- // there is little overhead for running this service.
- ServiceManager.addService("samplingprofiler",
- new SamplingProfilerService(context));
- } catch (Throwable e) {
- reportWtf("starting SamplingProfiler Service", e);
- }
- Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
- }
-
if (!disableNetwork && !disableNetworkTime) {
traceBeginAndSlog("StartNetworkTimeUpdateService");
try {
diff --git a/telephony/java/android/telephony/MbmsDownloadManager.java b/telephony/java/android/telephony/MbmsDownloadManager.java
index a9ec299..c8c6d01 100644
--- a/telephony/java/android/telephony/MbmsDownloadManager.java
+++ b/telephony/java/android/telephony/MbmsDownloadManager.java
@@ -16,19 +16,24 @@
package android.telephony;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.ServiceConnection;
import android.net.Uri;
+import android.os.IBinder;
import android.os.RemoteException;
import android.telephony.mbms.IDownloadCallback;
import android.telephony.mbms.DownloadRequest;
import android.telephony.mbms.DownloadStatus;
import android.telephony.mbms.IMbmsDownloadManagerCallback;
import android.telephony.mbms.MbmsException;
+import android.telephony.mbms.MbmsUtils;
import android.telephony.mbms.vendor.IMbmsDownloadService;
import android.util.Log;
import java.util.List;
+import java.util.concurrent.CountDownLatch;
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
@@ -36,6 +41,8 @@
public class MbmsDownloadManager {
private static final String LOG_TAG = MbmsDownloadManager.class.getSimpleName();
+ public static final String MBMS_DOWNLOAD_SERVICE_ACTION =
+ "android.telephony.action.EmbmsDownload";
/**
* The MBMS middleware should send this when a download of single file has completed or
* failed. Mandatory extras are
@@ -76,15 +83,15 @@
"android.telephony.mbms.action.CLEANUP";
/**
- * Integer extra indicating the result code of the download.
- * TODO: put in link to error list
- * TODO: future systemapi (here and and all extras)
+ * Integer extra indicating the result code of the download. One of
+ * {@link #RESULT_SUCCESSFUL}, {@link #RESULT_EXPIRED}, or {@link #RESULT_CANCELLED}.
*/
public static final String EXTRA_RESULT = "android.telephony.mbms.extra.RESULT";
/**
* Extra containing the {@link android.telephony.mbms.FileInfo} for which the download result
* is for. Must not be null.
+ * TODO: future systemapi (here and and all extras) except the two for the app intent
*/
public static final String EXTRA_INFO = "android.telephony.mbms.extra.INFO";
@@ -143,11 +150,23 @@
public static final String EXTRA_TEMP_FILES_IN_USE =
"android.telephony.mbms.extra.TEMP_FILES_IN_USE";
+ /**
+ * Extra containing a single {@link Uri} indicating the location of the successfully
+ * downloaded file. Set on the intent provided via
+ * {@link android.telephony.mbms.DownloadRequest.Builder#setAppIntent(Intent)}.
+ * Will always be set to a non-null value if {@link #EXTRA_RESULT} is set to
+ * {@link #RESULT_SUCCESSFUL}.
+ */
+ public static final String EXTRA_COMPLETED_FILE_URI =
+ "android.telephony.mbms.extra.COMPLETED_FILE_URI";
+
public static final int RESULT_SUCCESSFUL = 1;
public static final int RESULT_CANCELLED = 2;
public static final int RESULT_EXPIRED = 3;
// TODO - more results!
+ private static final long BIND_TIMEOUT_MS = 3000;
+
private final Context mContext;
private int mSubId = INVALID_SUBSCRIPTION_ID;
@@ -199,12 +218,31 @@
}
private void bindAndInitialize() throws MbmsException {
- // TODO: bind
- try {
- mService.initialize(mDownloadAppName, mSubId, mCallback);
- } catch (RemoteException e) {
- throw new MbmsException(0); // TODO: proper error code
- }
+ // TODO: fold binding for download and streaming into a common utils class.
+ final CountDownLatch latch = new CountDownLatch(1);
+ ServiceConnection bindListener = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ mService = IMbmsDownloadService.Stub.asInterface(service);
+ latch.countDown();
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ mService = null;
+ }
+ };
+
+ Intent bindIntent = new Intent();
+ bindIntent.setComponent(MbmsUtils.toComponentName(
+ MbmsUtils.getMiddlewareServiceInfo(mContext, MBMS_DOWNLOAD_SERVICE_ACTION)));
+
+ // Kick off the binding, and synchronously wait until binding is complete
+ mContext.bindService(bindIntent, bindListener, Context.BIND_AUTO_CREATE);
+
+ MbmsUtils.waitOnLatchWithTimeout(latch, BIND_TIMEOUT_MS);
+
+ // TODO: initialize
}
/**
@@ -245,6 +283,11 @@
*/
public DownloadRequest download(DownloadRequest request, IDownloadCallback listener) {
request.setAppName(mDownloadAppName);
+ try {
+ mService.download(request, listener);
+ } catch (RemoteException e) {
+ mService = null;
+ }
return request;
}
diff --git a/telephony/java/android/telephony/MbmsStreamingManager.java b/telephony/java/android/telephony/MbmsStreamingManager.java
index e90a63c..f68e243 100644
--- a/telephony/java/android/telephony/MbmsStreamingManager.java
+++ b/telephony/java/android/telephony/MbmsStreamingManager.java
@@ -27,6 +27,7 @@
import android.os.RemoteException;
import android.telephony.mbms.MbmsException;
import android.telephony.mbms.MbmsStreamingManagerCallback;
+import android.telephony.mbms.MbmsUtils;
import android.telephony.mbms.StreamingService;
import android.telephony.mbms.StreamingServiceCallback;
import android.telephony.mbms.StreamingServiceInfo;
@@ -62,7 +63,9 @@
Log.i(LOG_TAG, String.format("Connected to service %s", name));
synchronized (MbmsStreamingManager.this) {
mService = IMbmsStreamingService.Stub.asInterface(service);
- mServiceListeners.forEach(ServiceListener::onServiceConnected);
+ for (ServiceListener l : mServiceListeners) {
+ l.onServiceConnected();
+ }
}
}
}
@@ -72,10 +75,13 @@
Log.i(LOG_TAG, String.format("Disconnected from service %s", name));
synchronized (MbmsStreamingManager.this) {
mService = null;
- mServiceListeners.forEach(ServiceListener::onServiceDisconnected);
+ for (ServiceListener l : mServiceListeners) {
+ l.onServiceDisconnected();
+ }
}
}
};
+
private List<ServiceListener> mServiceListeners = new LinkedList<>();
private MbmsStreamingManagerCallback mCallbackToApp;
@@ -218,22 +224,6 @@
}
private void bindAndInitialize() throws MbmsException {
- // Query for the proper service
- PackageManager packageManager = mContext.getPackageManager();
- Intent queryIntent = new Intent();
- queryIntent.setAction(MBMS_STREAMING_SERVICE_ACTION);
- List<ResolveInfo> streamingServices = packageManager.queryIntentServices(queryIntent,
- PackageManager.MATCH_SYSTEM_ONLY);
-
- if (streamingServices == null || streamingServices.size() == 0) {
- throw new MbmsException(
- MbmsException.ERROR_NO_SERVICE_INSTALLED);
- }
- if (streamingServices.size() > 1) {
- throw new MbmsException(
- MbmsException.ERROR_MULTIPLE_SERVICES_INSTALLED);
- }
-
// Kick off the binding, and synchronously wait until binding is complete
final CountDownLatch latch = new CountDownLatch(1);
ServiceListener bindListener = new ServiceListener() {
@@ -252,13 +242,14 @@
}
Intent bindIntent = new Intent();
- bindIntent.setComponent(streamingServices.get(0).getComponentInfo().getComponentName());
+ bindIntent.setComponent(MbmsUtils.toComponentName(
+ MbmsUtils.getMiddlewareServiceInfo(mContext, MBMS_STREAMING_SERVICE_ACTION)));
mContext.bindService(bindIntent, mServiceConnection, Context.BIND_AUTO_CREATE);
- waitOnLatchWithTimeout(latch, BIND_TIMEOUT_MS);
+ MbmsUtils.waitOnLatchWithTimeout(latch, BIND_TIMEOUT_MS);
- // Remove the listener and call the initialization method through the interface.
+ // Remove the listener and call the initialization method through the interface.
synchronized (this) {
mServiceListeners.remove(bindListener);
@@ -279,17 +270,4 @@
}
}
- private static void waitOnLatchWithTimeout(CountDownLatch l, long timeoutMs) {
- long endTime = System.currentTimeMillis() + timeoutMs;
- while (System.currentTimeMillis() < endTime) {
- try {
- l.await(timeoutMs, TimeUnit.MILLISECONDS);
- } catch (InterruptedException e) {
- // keep waiting
- }
- if (l.getCount() <= 0) {
- return;
- }
- }
- }
}
diff --git a/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java b/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java
new file mode 100644
index 0000000..c01ddae
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java
@@ -0,0 +1,365 @@
+/*
+ * Copyright (C) 2017 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 android.telephony.mbms;
+
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.telephony.MbmsDownloadManager;
+import android.util.Log;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * @hide
+ */
+public class MbmsDownloadReceiver extends BroadcastReceiver {
+ private static final String LOG_TAG = "MbmsDownloadReceiver";
+ private static final String TEMP_FILE_SUFFIX = ".embms.temp";
+ private static final int MAX_TEMP_FILE_RETRIES = 5;
+
+ public static final String MBMS_FILE_PROVIDER_META_DATA_KEY = "mbms-file-provider-authority";
+
+ private String mFileProviderAuthorityCache = null;
+ private String mMiddlewarePackageNameCache = null;
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (!verifyIntentContents(intent)) {
+ setResultCode(1 /* TODO: define error constants */);
+ return;
+ }
+
+ if (MbmsDownloadManager.ACTION_DOWNLOAD_RESULT_INTERNAL.equals(intent.getAction())) {
+ moveDownloadedFile(context, intent);
+ cleanupPostMove(context, intent);
+ } else if (MbmsDownloadManager.ACTION_FILE_DESCRIPTOR_REQUEST.equals(intent.getAction())) {
+ generateTempFiles(context, intent);
+ }
+ // TODO: Add handling for ACTION_CLEANUP
+ }
+
+ private boolean verifyIntentContents(Intent intent) {
+ if (MbmsDownloadManager.ACTION_DOWNLOAD_RESULT_INTERNAL.equals(intent.getAction())) {
+ if (!intent.hasExtra(MbmsDownloadManager.EXTRA_RESULT)) {
+ Log.w(LOG_TAG, "Download result did not include a result code. Ignoring.");
+ return false;
+ }
+ if (!intent.hasExtra(MbmsDownloadManager.EXTRA_REQUEST)) {
+ Log.w(LOG_TAG, "Download result did not include the associated request. Ignoring.");
+ return false;
+ }
+ if (!intent.hasExtra(MbmsDownloadManager.EXTRA_INFO)) {
+ Log.w(LOG_TAG, "Download result did not include the associated file info. " +
+ "Ignoring.");
+ return false;
+ }
+ if (!intent.hasExtra(MbmsDownloadManager.EXTRA_FINAL_URI)) {
+ Log.w(LOG_TAG, "Download result did not include the path to the final " +
+ "temp file. Ignoring.");
+ return false;
+ }
+ return true;
+ } else if (MbmsDownloadManager.ACTION_FILE_DESCRIPTOR_REQUEST.equals(intent.getAction())) {
+ if (!intent.hasExtra(MbmsDownloadManager.EXTRA_REQUEST)) {
+ Log.w(LOG_TAG, "Temp file request not include the associated request. Ignoring.");
+ return false;
+ }
+ return true;
+ }
+
+ Log.w(LOG_TAG, "Received intent with unknown action: " + intent.getAction());
+ return false;
+ }
+
+ private void moveDownloadedFile(Context context, Intent intent) {
+ DownloadRequest request = intent.getParcelableExtra(MbmsDownloadManager.EXTRA_REQUEST);
+ // TODO: check request against token
+ Intent intentForApp = request.getIntentForApp();
+
+ int result = intent.getIntExtra(MbmsDownloadManager.EXTRA_RESULT,
+ MbmsDownloadManager.RESULT_CANCELLED);
+ intentForApp.putExtra(MbmsDownloadManager.EXTRA_RESULT, result);
+
+ if (result != MbmsDownloadManager.RESULT_SUCCESSFUL) {
+ Log.i(LOG_TAG, "Download request indicated a failed download. Aborting.");
+ context.sendBroadcast(intentForApp);
+ return;
+ }
+
+ Uri destinationUri = request.getDestinationUri();
+ Uri finalTempFile = intent.getParcelableExtra(MbmsDownloadManager.EXTRA_FINAL_URI);
+ if (!verifyTempFilePath(context, request, finalTempFile)) {
+ Log.w(LOG_TAG, "Download result specified an invalid temp file " + finalTempFile);
+ setResultCode(1);
+ return;
+ }
+
+ String relativePath = calculateDestinationFileRelativePath(request,
+ (FileInfo) intent.getParcelableExtra(MbmsDownloadManager.EXTRA_INFO));
+
+ if (!moveTempFile(finalTempFile, destinationUri, relativePath)) {
+ Log.w(LOG_TAG, "Failed to move temp file to final destination");
+ setResultCode(1);
+ }
+
+ context.sendBroadcast(intentForApp);
+ setResultCode(0);
+ }
+
+ private void cleanupPostMove(Context context, Intent intent) {
+ // TODO: account for in-use temp files
+ DownloadRequest request = intent.getParcelableExtra(MbmsDownloadManager.EXTRA_REQUEST);
+ if (request == null) {
+ Log.w(LOG_TAG, "Intent does not include a DownloadRequest. Ignoring.");
+ return;
+ }
+
+ List<Uri> tempFiles = intent.getParcelableExtra(MbmsDownloadManager.EXTRA_TEMP_LIST);
+ if (tempFiles == null) {
+ return;
+ }
+
+ for (Uri tempFileUri : tempFiles) {
+ if (verifyTempFilePath(context, request, tempFileUri)) {
+ File tempFile = new File(tempFileUri.getSchemeSpecificPart());
+ tempFile.delete();
+ }
+ }
+ }
+
+ private void generateTempFiles(Context context, Intent intent) {
+ // TODO: update pursuant to final decision on temp file locations
+ DownloadRequest request = intent.getParcelableExtra(MbmsDownloadManager.EXTRA_REQUEST);
+ if (request == null) {
+ Log.w(LOG_TAG, "Temp file request did not include the associated request. Ignoring.");
+ setResultCode(1 /* TODO: define error constants */);
+ return;
+ }
+ int fdCount = intent.getIntExtra(MbmsDownloadManager.EXTRA_FD_COUNT, 0);
+ List<Uri> pausedList = intent.getParcelableExtra(MbmsDownloadManager.EXTRA_PAUSED_LIST);
+
+ if (fdCount == 0 && (pausedList == null || pausedList.size() == 0)) {
+ Log.i(LOG_TAG, "No temp files actually requested. Ending.");
+ setResultCode(0);
+ setResultExtras(Bundle.EMPTY);
+ return;
+ }
+
+ ArrayList<UriPathPair> freshTempFiles = generateFreshTempFiles(context, request, fdCount);
+ ArrayList<UriPathPair> pausedFiles =
+ generateUrisForPausedFiles(context, request, pausedList);
+
+ Bundle result = new Bundle();
+ result.putParcelableArrayList(MbmsDownloadManager.EXTRA_FREE_URI_LIST, freshTempFiles);
+ result.putParcelableArrayList(MbmsDownloadManager.EXTRA_PAUSED_URI_LIST, pausedFiles);
+ setResultExtras(result);
+ }
+
+ private ArrayList<UriPathPair> generateFreshTempFiles(Context context, DownloadRequest request,
+ int freshFdCount) {
+ File tempFileDir = getEmbmsTempFileDirForRequest(context, request);
+ if (!tempFileDir.exists()) {
+ tempFileDir.mkdirs();
+ }
+
+ // Name the files with the template "N-UUID", where N is the request ID and UUID is a
+ // random uuid.
+ ArrayList<UriPathPair> result = new ArrayList<>(freshFdCount);
+ for (int i = 0; i < freshFdCount; i++) {
+ File tempFile = generateSingleTempFile(tempFileDir);
+ if (tempFile == null) {
+ setResultCode(2 /* TODO: define error constants */);
+ Log.w(LOG_TAG, "Failed to generate a temp file. Moving on.");
+ continue;
+ }
+ Uri fileUri = Uri.fromParts(ContentResolver.SCHEME_FILE, tempFile.getPath(), null);
+ Uri contentUri = MbmsTempFileProvider.getUriForFile(
+ context, getFileProviderAuthorityCached(context), tempFile);
+ context.grantUriPermission(getMiddlewarePackageCached(context), contentUri,
+ Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+ result.add(new UriPathPair(fileUri, contentUri));
+ }
+
+ return result;
+ }
+
+ private static File generateSingleTempFile(File tempFileDir) {
+ int numTries = 0;
+ while (numTries < MAX_TEMP_FILE_RETRIES) {
+ numTries++;
+ String fileName = UUID.randomUUID() + TEMP_FILE_SUFFIX;
+ File tempFile = new File(tempFileDir, fileName);
+ try {
+ if (tempFile.createNewFile()) {
+ return tempFile.getCanonicalFile();
+ }
+ } catch (IOException e) {
+ continue;
+ }
+ }
+ return null;
+ }
+
+
+ private ArrayList<UriPathPair> generateUrisForPausedFiles(Context context,
+ DownloadRequest request, List<Uri> pausedFiles) {
+ if (pausedFiles == null) {
+ return new ArrayList<>(0);
+ }
+ ArrayList<UriPathPair> result = new ArrayList<>(pausedFiles.size());
+
+ for (Uri fileUri : pausedFiles) {
+ if (!verifyTempFilePath(context, request, fileUri)) {
+ Log.w(LOG_TAG, "Supplied file " + fileUri + " is not a valid temp file to resume");
+ setResultCode(2 /* TODO: define error codes */);
+ continue;
+ }
+ File tempFile = new File(fileUri.getSchemeSpecificPart());
+ if (!tempFile.exists()) {
+ Log.w(LOG_TAG, "Supplied file " + fileUri + " does not exist.");
+ setResultCode(2 /* TODO: define error codes */);
+ continue;
+ }
+ Uri contentUri = MbmsTempFileProvider.getUriForFile(
+ context, getFileProviderAuthorityCached(context), tempFile);
+ context.grantUriPermission(getMiddlewarePackageCached(context), contentUri,
+ Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+
+ result.add(new UriPathPair(fileUri, contentUri));
+ }
+ return result;
+ }
+
+ private static String calculateDestinationFileRelativePath(DownloadRequest request,
+ FileInfo info) {
+ // TODO: determine whether this is actually the path determination scheme we want to use
+ List<String> filePathComponents = info.uri.getPathSegments();
+ List<String> requestPathComponents = request.getSourceUri().getPathSegments();
+ Iterator<String> filePathIter = filePathComponents.iterator();
+ Iterator<String> requestPathIter = requestPathComponents.iterator();
+
+ LinkedList<String> relativePathComponents = new LinkedList<>();
+ while (filePathIter.hasNext()) {
+ String currFilePathComponent = filePathIter.next();
+ if (requestPathIter.hasNext()) {
+ String requestFilePathComponent = requestPathIter.next();
+ if (requestFilePathComponent.equals(currFilePathComponent)) {
+ continue;
+ }
+ }
+ relativePathComponents.add(currFilePathComponent);
+ }
+ return String.join("/", relativePathComponents);
+ }
+
+ private static boolean moveTempFile(Uri fromPath, Uri toPath, String relativePath) {
+ if (!ContentResolver.SCHEME_FILE.equals(fromPath.getScheme())) {
+ Log.w(LOG_TAG, "Moving source uri " + fromPath+ " does not have a file scheme");
+ return false;
+ }
+ if (!ContentResolver.SCHEME_FILE.equals(toPath.getScheme())) {
+ Log.w(LOG_TAG, "Moving destination uri " + toPath + " does not have a file scheme");
+ return false;
+ }
+
+ File fromFile = new File(fromPath.getSchemeSpecificPart());
+ File toFile = new File(toPath.getSchemeSpecificPart(), relativePath);
+ toFile.getParentFile().mkdirs();
+
+ // TODO: This may not work if the two files are on different filesystems. Should we
+ // enforce that the temp file storage and the permanent storage are both in the same fs?
+ return fromFile.renameTo(toFile);
+ }
+
+ private static boolean verifyTempFilePath(Context context, DownloadRequest request,
+ Uri filePath) {
+ // TODO: modify pursuant to final decision on temp file path scheme
+ if (!ContentResolver.SCHEME_FILE.equals(filePath.getScheme())) {
+ Log.w(LOG_TAG, "Uri " + filePath + " does not have a file scheme");
+ return false;
+ }
+
+ String path = filePath.getSchemeSpecificPart();
+ File tempFile = new File(path);
+ if (!tempFile.exists()) {
+ Log.w(LOG_TAG, "File at " + path + " does not exist.");
+ return false;
+ }
+
+ if (!MbmsUtils.isContainedIn(getEmbmsTempFileDirForRequest(context, request), tempFile)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns a File linked to the directory used to store temp files for this request
+ */
+ private static File getEmbmsTempFileDirForRequest(Context context, DownloadRequest request) {
+ File embmsTempFileDir = MbmsTempFileProvider.getEmbmsTempFileDir(
+ context, getFileProviderAuthority(context));
+
+ // TODO: better naming scheme for temp file dirs
+ String tempFileDirName = String.valueOf(request.getFileServiceInfo().getServiceId());
+ return new File(embmsTempFileDir, tempFileDirName);
+ }
+
+ private String getFileProviderAuthorityCached(Context context) {
+ if (mFileProviderAuthorityCache != null) {
+ return mFileProviderAuthorityCache;
+ }
+
+ mFileProviderAuthorityCache = getFileProviderAuthority(context);
+ return mFileProviderAuthorityCache;
+ }
+
+ private static String getFileProviderAuthority(Context context) {
+ ApplicationInfo appInfo;
+ try {
+ appInfo = context.getPackageManager()
+ .getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA);
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new RuntimeException("Package manager couldn't find " + context.getPackageName());
+ }
+ String authority = appInfo.metaData.getString(MBMS_FILE_PROVIDER_META_DATA_KEY);
+ if (authority == null) {
+ throw new RuntimeException("Must declare the file provider authority as meta data");
+ }
+ return authority;
+ }
+
+ private String getMiddlewarePackageCached(Context context) {
+ if (mMiddlewarePackageNameCache == null) {
+ mMiddlewarePackageNameCache = MbmsUtils.getMiddlewareServiceInfo(context,
+ MbmsDownloadManager.MBMS_DOWNLOAD_SERVICE_ACTION).packageName;
+ }
+ return mMiddlewarePackageNameCache;
+ }
+}
diff --git a/telephony/java/android/telephony/mbms/MbmsTempFileProvider.java b/telephony/java/android/telephony/mbms/MbmsTempFileProvider.java
new file mode 100644
index 0000000..9842581
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/MbmsTempFileProvider.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2017 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 android.telephony.mbms;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ContentProvider;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.ProviderInfo;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+/**
+ * @hide
+ */
+public class MbmsTempFileProvider extends ContentProvider {
+ public static final String META_DATA_USE_EXTERNAL_STORAGE = "use-external-storage";
+ public static final String META_DATA_TEMP_FILE_DIRECTORY = "temp-file-path";
+ public static final String DEFAULT_TOP_LEVEL_TEMP_DIRECTORY = "androidMbmsTempFileRoot";
+
+ private String mAuthority;
+ private Context mContext;
+
+ @Override
+ public boolean onCreate() {
+ return true;
+ }
+
+ @Override
+ public Cursor query(@NonNull Uri uri, @Nullable String[] projection,
+ @Nullable String selection, @Nullable String[] selectionArgs,
+ @Nullable String sortOrder) {
+ throw new UnsupportedOperationException("No querying supported");
+ }
+
+ @Override
+ public String getType(@NonNull Uri uri) {
+ // EMBMS temp files can contain arbitrary content.
+ return "application/octet-stream";
+ }
+
+ @Override
+ public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
+ throw new UnsupportedOperationException("No inserting supported");
+ }
+
+ @Override
+ public int delete(@NonNull Uri uri, @Nullable String selection,
+ @Nullable String[] selectionArgs) {
+ throw new UnsupportedOperationException("No deleting supported");
+ }
+
+ @Override
+ public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String
+ selection, @Nullable String[] selectionArgs) {
+ throw new UnsupportedOperationException("No updating supported");
+ }
+
+ @Override
+ public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
+ // ContentProvider has already checked granted permissions
+ final File file = getFileForUri(mContext, mAuthority, uri);
+ final int fileMode = ParcelFileDescriptor.parseMode(mode);
+ return ParcelFileDescriptor.open(file, fileMode);
+ }
+
+ @Override
+ public void attachInfo(Context context, ProviderInfo info) {
+ super.attachInfo(context, info);
+
+ // Sanity check our security
+ if (info.exported) {
+ throw new SecurityException("Provider must not be exported");
+ }
+ if (!info.grantUriPermissions) {
+ throw new SecurityException("Provider must grant uri permissions");
+ }
+
+ mAuthority = info.authority;
+ mContext = context;
+ }
+
+ public static Uri getUriForFile(Context context, String authority, File file) {
+ // Get the canonical path of the temp file
+ String filePath;
+ try {
+ filePath = file.getCanonicalPath();
+ } catch (IOException e) {
+ throw new IllegalArgumentException("Could not get canonical path for file " + file);
+ }
+
+ // Make sure the temp file is contained in the temp file directory as configured in the
+ // manifest
+ File tempFileDir = getEmbmsTempFileDir(context, authority);
+ if (!MbmsUtils.isContainedIn(tempFileDir, file)) {
+ throw new IllegalArgumentException("File " + file + " is not contained in the temp " +
+ "file directory, which is " + tempFileDir);
+ }
+
+ // Get the canonical path of the temp file directory
+ String tempFileDirPath;
+ try {
+ tempFileDirPath = tempFileDir.getCanonicalPath();
+ } catch (IOException e) {
+ throw new RuntimeException(
+ "Could not get canonical path for temp file root dir " + tempFileDir);
+ }
+
+ // Start at first char of path under temp file directory
+ String pathFragment;
+ if (tempFileDirPath.endsWith("/")) {
+ pathFragment = filePath.substring(tempFileDirPath.length());
+ } else {
+ pathFragment = filePath.substring(tempFileDirPath.length() + 1);
+ }
+
+ String encodedPath = Uri.encode(pathFragment);
+ return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
+ .authority(authority).encodedPath(encodedPath).build();
+ }
+
+ public static File getFileForUri(Context context, String authority, Uri uri)
+ throws FileNotFoundException {
+ if (!ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) {
+ throw new IllegalArgumentException("Uri must have scheme content");
+ }
+
+ String relPath = Uri.decode(uri.getEncodedPath());
+ File file;
+ File tempFileDir;
+
+ try {
+ tempFileDir = getEmbmsTempFileDir(context, authority).getCanonicalFile();
+ file = new File(tempFileDir, relPath).getCanonicalFile();
+ } catch (IOException e) {
+ throw new FileNotFoundException("Could not resolve paths");
+ }
+
+ if (!file.getPath().startsWith(tempFileDir.getPath())) {
+ throw new SecurityException("Resolved path jumped beyond configured root");
+ }
+
+ return file;
+ }
+
+ /**
+ * Returns a File for the directory used to store temp files for this app
+ */
+ public static File getEmbmsTempFileDir(Context context, String authority) {
+ Bundle metadata = getMetadata(context, authority);
+ File parentDirectory;
+ if (metadata.getBoolean(META_DATA_USE_EXTERNAL_STORAGE, false)) {
+ parentDirectory = context.getExternalFilesDir(null);
+ } else {
+ parentDirectory = context.getFilesDir();
+ }
+
+ String tmpFilePath = metadata.getString(META_DATA_TEMP_FILE_DIRECTORY);
+ if (tmpFilePath == null) {
+ tmpFilePath = DEFAULT_TOP_LEVEL_TEMP_DIRECTORY;
+ }
+ return new File(parentDirectory, tmpFilePath);
+ }
+
+ private static Bundle getMetadata(Context context, String authority) {
+ final ProviderInfo info = context.getPackageManager()
+ .resolveContentProvider(authority, PackageManager.GET_META_DATA);
+ return info.metaData;
+ }
+}
diff --git a/telephony/java/android/telephony/mbms/MbmsUtils.java b/telephony/java/android/telephony/mbms/MbmsUtils.java
new file mode 100644
index 0000000..de30805
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/MbmsUtils.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2017 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 android.telephony.mbms;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.*;
+import android.content.pm.ServiceInfo;
+import android.telephony.MbmsDownloadManager;
+import android.util.Log;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @hide
+ */
+public class MbmsUtils {
+ private static final String LOG_TAG = "MbmsUtils";
+
+ public static boolean isContainedIn(File parent, File child) {
+ try {
+ String parentPath = parent.getCanonicalPath();
+ String childPath = child.getCanonicalPath();
+ return childPath.startsWith(parentPath);
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to resolve canonical paths: " + e);
+ }
+ }
+
+ public static void waitOnLatchWithTimeout(CountDownLatch l, long timeoutMs) {
+ long endTime = System.currentTimeMillis() + timeoutMs;
+ while (System.currentTimeMillis() < endTime) {
+ try {
+ l.await(timeoutMs, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ // keep waiting
+ }
+ if (l.getCount() <= 0) {
+ return;
+ }
+ }
+ }
+
+ public static ComponentName toComponentName(ComponentInfo ci) {
+ return new ComponentName(ci.packageName, ci.name);
+ }
+
+ public static ServiceInfo getMiddlewareServiceInfo(Context context, String serviceAction) {
+ // Query for the proper service
+ PackageManager packageManager = context.getPackageManager();
+ Intent queryIntent = new Intent();
+ queryIntent.setAction(serviceAction);
+ List<ResolveInfo> downloadServices = packageManager.queryIntentServices(queryIntent,
+ PackageManager.MATCH_SYSTEM_ONLY);
+
+ if (downloadServices == null || downloadServices.size() == 0) {
+ Log.w(LOG_TAG, "No download services found, cannot get service info");
+ return null;
+ }
+
+ if (downloadServices.size() > 1) {
+ Log.w(LOG_TAG, "More than one download service found, cannot get unique service");
+ return null;
+ }
+ return downloadServices.get(0).serviceInfo;
+ }
+}
diff --git a/test-runner/Android.mk b/test-runner/Android.mk
index 3c36e42..0752661 100644
--- a/test-runner/Android.mk
+++ b/test-runner/Android.mk
@@ -16,6 +16,8 @@
LOCAL_PATH:= $(call my-dir)
+# Build the android.test.runner library
+# =====================================
include $(CLEAR_VARS)
LOCAL_SRC_FILES := $(call all-java-files-under, src)
@@ -26,5 +28,56 @@
include $(BUILD_JAVA_LIBRARY)
+# Build the android.test.mock library
+# ===================================
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src/android/test/mock)
+
+LOCAL_JAVA_LIBRARIES := core-oj core-libart framework
+
+LOCAL_MODULE:= android.test.mock
+
+include $(BUILD_JAVA_LIBRARY)
+
+# Generate the stub source files for android.test.mock.sdk
+# ========================================================
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := $(call all-java-files-under, src/android/test/mock)
+
+LOCAL_JAVA_LIBRARIES := core-oj core-libart framework
+LOCAL_MODULE_CLASS := JAVA_LIBRARIES
+LOCAL_DROIDDOC_SOURCE_PATH := $(LOCAL_PATH)/src/android/test/mock
+
+LOCAL_DROIDDOC_OPTIONS:= \
+ -stubpackages android.test.mock \
+ -stubs $(TARGET_OUT_COMMON_INTERMEDIATES)/JAVA_LIBRARIES/android.test.mock.sdk_intermediates/src \
+ -nodocs
+
+LOCAL_UNINSTALLABLE_MODULE := true
+LOCAL_MODULE := android-test-mock-stubs-gen
+
+include $(BUILD_DROIDDOC)
+
+# Remember the target that will trigger the code generation.
+android_test_mock_gen_stamp := $(full_target)
+
+# Build the android.test.mock.sdk library
+# =======================================
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := android.test.mock.sdk
+
+LOCAL_SOURCE_FILES_ALL_GENERATED := true
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+# Make sure to run droiddoc first to generate the stub source files.
+$(full_classes_compiled_jar) : $(android_test_mock_gen_stamp)
+$(full_classes_jack) : $(android_test_mock_gen_stamp)
+
+# Archive a copy of the classes.jar in SDK build.
+$(call dist-for-goals,sdk win_sdk,$(full_classes_jar):android.test.mock.jar)
+
# additionally, build unit tests in a separate .apk
include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/net/java/com/android/server/connectivity/TetheringTest.java b/tests/net/java/com/android/server/connectivity/TetheringTest.java
index b6922d4..be0924a 100644
--- a/tests/net/java/com/android/server/connectivity/TetheringTest.java
+++ b/tests/net/java/com/android/server/connectivity/TetheringTest.java
@@ -42,6 +42,7 @@
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
import android.content.res.Resources;
import android.hardware.usb.UsbManager;
import android.net.ConnectivityManager;
@@ -85,6 +86,7 @@
public class TetheringTest {
private static final String[] PROVISIONING_APP_NAME = {"some", "app"};
+ @Mock private ApplicationInfo mApplicationInfo;
@Mock private Context mContext;
@Mock private ConnectivityManager mConnectivityManager;
@Mock private INetworkManagementService mNMService;
@@ -116,6 +118,9 @@
}
@Override
+ public ApplicationInfo getApplicationInfo() { return mApplicationInfo; }
+
+ @Override
public ContentResolver getContentResolver() { return mContentResolver; }
@Override
@@ -363,7 +368,6 @@
any(NetworkCallback.class), any(Handler.class));
// In tethering mode, in the default configuration, an explicit request
// for a mobile network is also made.
- verify(mConnectivityManager, atLeastOnce()).getNetworkInfo(anyInt());
verify(mConnectivityManager, times(1)).requestNetwork(
any(NetworkRequest.class), any(NetworkCallback.class), eq(0), anyInt(),
any(Handler.class));
diff --git a/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java b/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java
index fb7971e..4d340d1 100644
--- a/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java
+++ b/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java
@@ -17,15 +17,23 @@
package com.android.server.connectivity.tethering;
import static android.provider.Settings.Global.TETHER_OFFLOAD_DISABLED;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyObject;
+import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.when;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.RouteInfo;
import android.net.util.SharedLog;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
@@ -35,9 +43,13 @@
import android.test.mock.MockContentResolver;
import com.android.internal.util.test.FakeSettingsProvider;
+import java.net.InetAddress;
+import java.util.ArrayList;
+
import org.junit.Before;
import org.junit.runner.RunWith;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -48,14 +60,17 @@
public class OffloadControllerTest {
@Mock private OffloadHardwareInterface mHardware;
+ @Mock private ApplicationInfo mApplicationInfo;
@Mock private Context mContext;
+ final ArgumentCaptor<ArrayList> mStringArrayCaptor = ArgumentCaptor.forClass(ArrayList.class);
private MockContentResolver mContentResolver;
@Before public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
+ when(mContext.getApplicationInfo()).thenReturn(mApplicationInfo);
+ when(mContext.getPackageName()).thenReturn("OffloadControllerTest");
mContentResolver = new MockContentResolver(mContext);
mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
- when(mContext.getPackageName()).thenReturn("OffloadControllerTest");
when(mContext.getContentResolver()).thenReturn(mContentResolver);
}
@@ -114,4 +129,87 @@
inOrder.verify(mHardware, never()).initOffloadControl(anyObject());
inOrder.verifyNoMoreInteractions();
}
+
+ @Test
+ public void testSetUpstreamLinkPropertiesWorking() throws Exception {
+ setupFunctioningHardwareInterface();
+ final OffloadController offload =
+ new OffloadController(null, mHardware, mContentResolver, new SharedLog("test"));
+ offload.start();
+
+ final InOrder inOrder = inOrder(mHardware);
+ inOrder.verify(mHardware, times(1)).initOffloadConfig();
+ inOrder.verify(mHardware, times(1)).initOffloadControl(
+ any(OffloadHardwareInterface.ControlCallback.class));
+ inOrder.verifyNoMoreInteractions();
+
+ offload.setUpstreamLinkProperties(null);
+ inOrder.verify(mHardware, times(1)).setUpstreamParameters(
+ eq(null), eq(null), eq(null), eq(null));
+ inOrder.verifyNoMoreInteractions();
+ reset(mHardware);
+
+ final LinkProperties lp = new LinkProperties();
+
+ final String testIfName = "rmnet_data17";
+ lp.setInterfaceName(testIfName);
+ offload.setUpstreamLinkProperties(lp);
+ inOrder.verify(mHardware, times(1)).setUpstreamParameters(
+ eq(testIfName), eq(null), eq(null), mStringArrayCaptor.capture());
+ assertTrue(mStringArrayCaptor.getValue().isEmpty());
+ inOrder.verifyNoMoreInteractions();
+
+ final String ipv4Addr = "192.0.2.5";
+ final String linkAddr = ipv4Addr + "/24";
+ lp.addLinkAddress(new LinkAddress(linkAddr));
+ offload.setUpstreamLinkProperties(lp);
+ inOrder.verify(mHardware, times(1)).setUpstreamParameters(
+ eq(testIfName), eq(ipv4Addr), eq(null), mStringArrayCaptor.capture());
+ assertTrue(mStringArrayCaptor.getValue().isEmpty());
+ inOrder.verifyNoMoreInteractions();
+
+ final String ipv4Gateway = "192.0.2.1";
+ lp.addRoute(new RouteInfo(InetAddress.getByName(ipv4Gateway)));
+ offload.setUpstreamLinkProperties(lp);
+ inOrder.verify(mHardware, times(1)).setUpstreamParameters(
+ eq(testIfName), eq(ipv4Addr), eq(ipv4Gateway), mStringArrayCaptor.capture());
+ assertTrue(mStringArrayCaptor.getValue().isEmpty());
+ inOrder.verifyNoMoreInteractions();
+
+ final String ipv6Gw1 = "fe80::cafe";
+ lp.addRoute(new RouteInfo(InetAddress.getByName(ipv6Gw1)));
+ offload.setUpstreamLinkProperties(lp);
+ inOrder.verify(mHardware, times(1)).setUpstreamParameters(
+ eq(testIfName), eq(ipv4Addr), eq(ipv4Gateway), mStringArrayCaptor.capture());
+ ArrayList<String> v6gws = mStringArrayCaptor.getValue();
+ assertEquals(1, v6gws.size());
+ assertTrue(v6gws.contains(ipv6Gw1));
+ inOrder.verifyNoMoreInteractions();
+
+ final String ipv6Gw2 = "fe80::d00d";
+ lp.addRoute(new RouteInfo(InetAddress.getByName(ipv6Gw2)));
+ offload.setUpstreamLinkProperties(lp);
+ inOrder.verify(mHardware, times(1)).setUpstreamParameters(
+ eq(testIfName), eq(ipv4Addr), eq(ipv4Gateway), mStringArrayCaptor.capture());
+ v6gws = mStringArrayCaptor.getValue();
+ assertEquals(2, v6gws.size());
+ assertTrue(v6gws.contains(ipv6Gw1));
+ assertTrue(v6gws.contains(ipv6Gw2));
+ inOrder.verifyNoMoreInteractions();
+
+ final LinkProperties stacked = new LinkProperties();
+ stacked.setInterfaceName("stacked");
+ stacked.addLinkAddress(new LinkAddress("192.0.2.129/25"));
+ stacked.addRoute(new RouteInfo(InetAddress.getByName("192.0.2.254")));
+ stacked.addRoute(new RouteInfo(InetAddress.getByName("fe80::bad:f00")));
+ assertTrue(lp.addStackedLink(stacked));
+ offload.setUpstreamLinkProperties(lp);
+ inOrder.verify(mHardware, times(1)).setUpstreamParameters(
+ eq(testIfName), eq(ipv4Addr), eq(ipv4Gateway), mStringArrayCaptor.capture());
+ v6gws = mStringArrayCaptor.getValue();
+ assertEquals(2, v6gws.size());
+ assertTrue(v6gws.contains(ipv6Gw1));
+ assertTrue(v6gws.contains(ipv6Gw2));
+ inOrder.verifyNoMoreInteractions();
+ }
}
diff --git a/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java b/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java
index 9bb392a..fb6066e 100644
--- a/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java
+++ b/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java
@@ -18,7 +18,12 @@
import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
+import static android.net.ConnectivityManager.TYPE_NONE;
+import static android.net.ConnectivityManager.TYPE_WIFI;
import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -59,6 +64,7 @@
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
@@ -240,6 +246,72 @@
assertFalse(mUNM.mobileNetworkRequested());
}
+ @Test
+ public void testSelectPreferredUpstreamType() throws Exception {
+ final Collection<Integer> preferredTypes = new ArrayList<>();
+ preferredTypes.add(TYPE_WIFI);
+
+ mUNM.start();
+ // There are no networks, so there is nothing to select.
+ assertEquals(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes));
+
+ final TestNetworkAgent wifiAgent = new TestNetworkAgent(mCM, TRANSPORT_WIFI);
+ wifiAgent.fakeConnect();
+ // WiFi is up, we should prefer it.
+ assertEquals(TYPE_WIFI, mUNM.selectPreferredUpstreamType(preferredTypes));
+ wifiAgent.fakeDisconnect();
+ // There are no networks, so there is nothing to select.
+ assertEquals(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes));
+
+ final TestNetworkAgent cellAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR);
+ cellAgent.fakeConnect();
+ assertEquals(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes));
+
+ preferredTypes.add(TYPE_MOBILE_DUN);
+ // This is coupled with preferred types in TetheringConfiguration.
+ mUNM.updateMobileRequiresDun(true);
+ // DUN is available, but only use regular cell: no upstream selected.
+ assertEquals(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes));
+ preferredTypes.remove(TYPE_MOBILE_DUN);
+ // No WiFi, but our preferred flavour of cell is up.
+ preferredTypes.add(TYPE_MOBILE_HIPRI);
+ // This is coupled with preferred types in TetheringConfiguration.
+ mUNM.updateMobileRequiresDun(false);
+ assertEquals(TYPE_MOBILE_HIPRI, mUNM.selectPreferredUpstreamType(preferredTypes));
+ // Check to see we filed an explicit request.
+ assertEquals(1, mCM.requested.size());
+ NetworkRequest netReq = (NetworkRequest) mCM.requested.values().toArray()[0];
+ assertTrue(netReq.networkCapabilities.hasTransport(TRANSPORT_CELLULAR));
+ assertFalse(netReq.networkCapabilities.hasCapability(NET_CAPABILITY_DUN));
+
+ wifiAgent.fakeConnect();
+ // WiFi is up, and we should prefer it over cell.
+ assertEquals(TYPE_WIFI, mUNM.selectPreferredUpstreamType(preferredTypes));
+ assertEquals(0, mCM.requested.size());
+
+ preferredTypes.remove(TYPE_MOBILE_HIPRI);
+ preferredTypes.add(TYPE_MOBILE_DUN);
+ // This is coupled with preferred types in TetheringConfiguration.
+ mUNM.updateMobileRequiresDun(true);
+ assertEquals(TYPE_WIFI, mUNM.selectPreferredUpstreamType(preferredTypes));
+
+ final TestNetworkAgent dunAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR);
+ dunAgent.networkCapabilities.addCapability(NET_CAPABILITY_DUN);
+ dunAgent.fakeConnect();
+
+ // WiFi is still preferred.
+ assertEquals(TYPE_WIFI, mUNM.selectPreferredUpstreamType(preferredTypes));
+
+ // WiFi goes down, cell and DUN are still up but only DUN is preferred.
+ wifiAgent.fakeDisconnect();
+ assertEquals(TYPE_MOBILE_DUN, mUNM.selectPreferredUpstreamType(preferredTypes));
+ // Check to see we filed an explicit request.
+ assertEquals(1, mCM.requested.size());
+ netReq = (NetworkRequest) mCM.requested.values().toArray()[0];
+ assertTrue(netReq.networkCapabilities.hasTransport(TRANSPORT_CELLULAR));
+ assertTrue(netReq.networkCapabilities.hasCapability(NET_CAPABILITY_DUN));
+ }
+
private void assertUpstreamTypeRequested(int upstreamType) throws Exception {
assertEquals(1, mCM.requested.size());
assertEquals(1, mCM.legacyTypeMap.size());
@@ -254,6 +326,8 @@
public Map<NetworkCallback, NetworkRequest> requested = new HashMap<>();
public Map<NetworkCallback, Integer> legacyTypeMap = new HashMap<>();
+ private int mNetworkId = 100;
+
public TestConnectivityManager(Context ctx, IConnectivityManager svc) {
super(ctx, svc);
}
@@ -287,6 +361,8 @@
return false;
}
+ int getNetworkId() { return ++mNetworkId; }
+
@Override
public void requestNetwork(NetworkRequest req, NetworkCallback cb, Handler h) {
assertFalse(allCallbacks.containsKey(cb));
@@ -360,6 +436,35 @@
}
}
+ public static class TestNetworkAgent {
+ public final TestConnectivityManager cm;
+ public final Network networkId;
+ public final int transportType;
+ public final NetworkCapabilities networkCapabilities;
+
+ public TestNetworkAgent(TestConnectivityManager cm, int transportType) {
+ this.cm = cm;
+ this.networkId = new Network(cm.getNetworkId());
+ this.transportType = transportType;
+ networkCapabilities = new NetworkCapabilities();
+ networkCapabilities.addTransportType(transportType);
+ networkCapabilities.addCapability(NET_CAPABILITY_INTERNET);
+ }
+
+ public void fakeConnect() {
+ for (NetworkCallback cb : cm.listening.keySet()) {
+ cb.onAvailable(networkId);
+ cb.onCapabilitiesChanged(networkId, copy(networkCapabilities));
+ }
+ }
+
+ public void fakeDisconnect() {
+ for (NetworkCallback cb : cm.listening.keySet()) {
+ cb.onLost(networkId);
+ }
+ }
+ }
+
public static class TestStateMachine extends StateMachine {
public final ArrayList<Message> messages = new ArrayList<>();
private final State mLoggingState = new LoggingState();
@@ -382,4 +487,8 @@
super.start();
}
}
+
+ static NetworkCapabilities copy(NetworkCapabilities nc) {
+ return new NetworkCapabilities(nc);
+ }
}