summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/app/ActivityThread.java31
-rw-r--r--core/java/com/android/internal/os/SamplingProfilerIntegration.java226
-rw-r--r--core/java/com/android/internal/os/ZygoteInit.java5
-rw-r--r--core/jni/AndroidRuntime.cpp7
-rw-r--r--packages/CaptivePortalLogin/res/values/strings.xml1
-rw-r--r--packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java11
-rw-r--r--services/core/java/com/android/server/SamplingProfilerService.java122
-rw-r--r--services/core/java/com/android/server/connectivity/Tethering.java122
-rw-r--r--services/core/java/com/android/server/connectivity/tethering/IControlsTethering.java34
-rw-r--r--services/core/java/com/android/server/connectivity/tethering/OffloadController.java14
-rw-r--r--services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java19
-rw-r--r--services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java70
-rw-r--r--services/java/com/android/server/SystemServer.java30
-rw-r--r--telephony/java/android/telephony/MbmsDownloadManager.java61
-rw-r--r--telephony/java/android/telephony/MbmsStreamingManager.java46
-rw-r--r--telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java365
-rw-r--r--telephony/java/android/telephony/mbms/MbmsTempFileProvider.java193
-rw-r--r--telephony/java/android/telephony/mbms/MbmsUtils.java86
-rw-r--r--tests/net/java/com/android/server/connectivity/TetheringTest.java6
-rw-r--r--tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java5
-rw-r--r--tests/net/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java58
-rw-r--r--tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java109
22 files changed, 1065 insertions, 556 deletions
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index df550807dd30..905a3ee9d25b 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -122,7 +122,6 @@ import com.android.internal.app.IVoiceInteractor;
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;
@@ -1608,7 +1607,6 @@ public final class ActivityThread {
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: {
@@ -1678,7 +1676,6 @@ public final class ActivityThread {
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:
@@ -1704,7 +1701,6 @@ public final class ActivityThread {
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:
@@ -1866,32 +1862,6 @@ public final class ActivityThread {
}
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 {
@@ -6501,7 +6471,6 @@ public final class ActivityThread {
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/com/android/internal/os/SamplingProfilerIntegration.java b/core/java/com/android/internal/os/SamplingProfilerIntegration.java
deleted file mode 100644
index 6429aa420fd0..000000000000
--- 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 9e61a99096d9..2c0f8e4a373f 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -694,8 +694,6 @@ public class ZygoteInit {
Trace.TRACE_TAG_DALVIK);
bootTimingsTraceLog.traceBegin("ZygoteInit");
RuntimeInit.enableDdms();
- // Start profiling the zygote initialization.
- SamplingProfilerIntegration.start();
boolean startSystemServer = false;
String socketName = "zygote";
@@ -734,9 +732,6 @@ public class ZygoteInit {
Zygote.resetNicePriority();
}
- // Finish profiling the zygote initialization.
- SamplingProfilerIntegration.writeZygoteSnapshot();
-
// Do an initial gc to clean up after startup
bootTimingsTraceLog.traceBegin("PostZygoteInitGC");
gcAndFinalize();
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 1adc6dde3860..659f47debaf1 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -613,6 +613,7 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote)
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];
@@ -739,6 +740,12 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote)
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 b1a3852a7a8d..f486fe4c5ddf 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&#8217;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 582b660a6e99..2703fbf00a50 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;
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;
@@ -469,7 +470,15 @@ public class CaptivePortalLoginActivity extends Activity {
}
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/services/core/java/com/android/server/SamplingProfilerService.java b/services/core/java/com/android/server/SamplingProfilerService.java
deleted file mode 100644
index 5f9269570e9d..000000000000
--- a/services/core/java/com/android/server/SamplingProfilerService.java
+++ /dev/null
@@ -1,122 +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 com.android.internal.util.DumpUtils;
-
-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) {
- if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
-
- 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 5f74d5e46580..a452404d7841 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -117,7 +117,7 @@ import java.util.concurrent.atomic.AtomicInteger;
* This class holds much of the business logic to allow Android devices
* to act as IP gateways via USB, BT, and WiFi interfaces.
*/
-public class Tethering extends BaseNetworkObserver implements IControlsTethering {
+public class Tethering extends BaseNetworkObserver {
private final static String TAG = Tethering.class.getSimpleName();
private final static boolean DBG = false;
@@ -173,6 +173,8 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
private final StateMachine mTetherMasterSM;
private final OffloadController mOffloadController;
private final UpstreamNetworkMonitor mUpstreamNetworkMonitor;
+ // TODO: Figure out how to merge this and other downstream-tracking objects
+ // into a single coherent structure.
private final HashSet<TetherInterfaceStateMachine> mForwardedDownstreams;
private final SimChangeListener mSimChange;
@@ -184,7 +186,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
private boolean mRndisEnabled; // track the RNDIS function enabled state
private boolean mUsbTetherRequested; // true if USB tethering should be started
// when RNDIS is enabled
- // True iff WiFi tethering should be started when soft AP is ready.
+ // True iff. WiFi tethering should be started when soft AP is ready.
private boolean mWifiTetherRequested;
public Tethering(Context context, INetworkManagementService nmService,
@@ -1112,6 +1114,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
static final int EVENT_UPSTREAM_CALLBACK = BASE_MASTER + 5;
// we treated the error and want now to clear it
static final int CMD_CLEAR_ERROR = BASE_MASTER + 6;
+ static final int EVENT_IFACE_UPDATE_LINKPROPERTIES = BASE_MASTER + 7;
private State mInitialState;
private State mTetherModeAliveState;
@@ -1179,6 +1182,9 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
if (VDBG) Log.d(TAG, "Tether Mode unrequested by " + who);
handleInterfaceServingStateInactive(who);
break;
+ case EVENT_IFACE_UPDATE_LINKPROPERTIES:
+ // Silently ignore these for now.
+ break;
default:
return NOT_HANDLED;
}
@@ -1242,8 +1248,8 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
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();
@@ -1255,58 +1261,6 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
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;
@@ -1397,6 +1351,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
if (mode == IControlsTethering.STATE_TETHERED) {
mForwardedDownstreams.add(who);
} else {
+ mOffloadController.removeDownstreamInterface(who.interfaceName());
mForwardedDownstreams.remove(who);
}
@@ -1421,6 +1376,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
private void handleInterfaceServingStateInactive(TetherInterfaceStateMachine who) {
mNotifyList.remove(who);
mIPv6TetheringCoordinator.removeActiveDownstream(who);
+ mOffloadController.removeDownstreamInterface(who.interfaceName());
mForwardedDownstreams.remove(who);
// If this is a Wi-Fi interface, tell WifiManager of any errors.
@@ -1524,6 +1480,15 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
}
break;
}
+ case EVENT_IFACE_UPDATE_LINKPROPERTIES: {
+ final LinkProperties newLp = (LinkProperties) message.obj;
+ if (message.arg1 == IControlsTethering.STATE_TETHERED) {
+ mOffloadController.notifyDownstreamLinkProperties(newLp);
+ } else {
+ mOffloadController.removeDownstreamInterface(newLp.getInterfaceName());
+ }
+ break;
+ }
case CMD_UPSTREAM_CHANGED:
updateUpstreamWanted();
if (!mUpstreamWanted) break;
@@ -1748,9 +1713,25 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
return false;
}
- @Override
- public void notifyInterfaceStateChange(String iface, TetherInterfaceStateMachine who,
- int state, int error) {
+ private IControlsTethering makeControlCallback(String ifname) {
+ return new IControlsTethering() {
+ @Override
+ public void updateInterfaceState(
+ TetherInterfaceStateMachine who, int state, int lastError) {
+ notifyInterfaceStateChange(ifname, who, state, lastError);
+ }
+
+ @Override
+ public void updateLinkProperties(
+ TetherInterfaceStateMachine who, LinkProperties newLp) {
+ notifyLinkPropertiesChanged(ifname, who, newLp);
+ }
+ };
+ }
+
+ // TODO: Move into TetherMasterSM.
+ private void notifyInterfaceStateChange(
+ String iface, TetherInterfaceStateMachine who, int state, int error) {
synchronized (mPublicSync) {
final TetherState tetherState = mTetherStates.get(iface);
if (tetherState != null && tetherState.stateMachine.equals(who)) {
@@ -1796,6 +1777,24 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
sendTetherStateChangedBroadcast();
}
+ private void notifyLinkPropertiesChanged(String iface, TetherInterfaceStateMachine who,
+ LinkProperties newLp) {
+ final int state;
+ synchronized (mPublicSync) {
+ final TetherState tetherState = mTetherStates.get(iface);
+ if (tetherState != null && tetherState.stateMachine.equals(who)) {
+ state = tetherState.lastState;
+ } else {
+ mLog.log("got notification from stale iface " + iface);
+ return;
+ }
+ }
+
+ mLog.log(String.format("OBSERVED LinkProperties update iface=%s state=%s", iface, state));
+ final int which = TetherMasterSM.EVENT_IFACE_UPDATE_LINKPROPERTIES;
+ mTetherMasterSM.sendMessage(which, state, 0, newLp);
+ }
+
private void maybeTrackNewInterfaceLocked(final String iface) {
// If we don't care about this type of interface, ignore.
final int interfaceType = ifaceNameToType(iface);
@@ -1813,7 +1812,8 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
mLog.log("adding TetheringInterfaceStateMachine for: " + iface);
final TetherState tetherState = new TetherState(
new TetherInterfaceStateMachine(
- iface, mLooper, interfaceType, mLog, mNMService, mStatsService, this,
+ iface, mLooper, interfaceType, mLog, mNMService, mStatsService,
+ makeControlCallback(iface),
new IPv6TetheringInterfaceServices(iface, mNMService, mLog)));
mTetherStates.put(iface, tetherState);
tetherState.stateMachine.start();
@@ -1825,7 +1825,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
mLog.log("attempting to remove unknown iface (" + iface + "), ignoring");
return;
}
- tetherState.stateMachine.sendMessage(TetherInterfaceStateMachine.CMD_INTERFACE_DOWN);
+ tetherState.stateMachine.stop();
mLog.log("removing TetheringInterfaceStateMachine for: " + iface);
mTetherStates.remove(iface);
}
diff --git a/services/core/java/com/android/server/connectivity/tethering/IControlsTethering.java b/services/core/java/com/android/server/connectivity/tethering/IControlsTethering.java
index c5c86bd1c3bb..aaa63b1613df 100644
--- a/services/core/java/com/android/server/connectivity/tethering/IControlsTethering.java
+++ b/services/core/java/com/android/server/connectivity/tethering/IControlsTethering.java
@@ -16,25 +16,41 @@
package com.android.server.connectivity.tethering;
+import android.net.LinkProperties;
+
/**
* @hide
*
* Interface with methods necessary to notify that a given interface is ready for tethering.
+ *
+ * Rename to something more representative, e.g. IpServingControlCallback.
+ *
+ * All methods MUST be called on the TetherMasterSM main Looper's thread.
*/
-public interface IControlsTethering {
- public final int STATE_UNAVAILABLE = 0;
- public final int STATE_AVAILABLE = 1;
- public final int STATE_TETHERED = 2;
- public final int STATE_LOCAL_ONLY = 3;
+public class IControlsTethering {
+ public static final int STATE_UNAVAILABLE = 0;
+ public static final int STATE_AVAILABLE = 1;
+ public static final int STATE_TETHERED = 2;
+ public static final int STATE_LOCAL_ONLY = 3;
/**
- * Notify that |who| has changed its tethering state. This may be called from any thread.
+ * Notify that |who| has changed its tethering state.
+ *
+ * TODO: Remove the need for the |who| argument.
*
- * @param iface a network interface (e.g. "wlan0")
* @param who corresponding instance of a TetherInterfaceStateMachine
* @param state one of IControlsTethering.STATE_*
* @param lastError one of ConnectivityManager.TETHER_ERROR_*
*/
- void notifyInterfaceStateChange(String iface, TetherInterfaceStateMachine who,
- int state, int lastError);
+ public void updateInterfaceState(TetherInterfaceStateMachine who, int state, int lastError) {}
+
+ /**
+ * Notify that |who| has new LinkProperties.
+ *
+ * TODO: Remove the need for the |who| argument.
+ *
+ * @param who corresponding instance of a TetherInterfaceStateMachine
+ * @param newLp the new LinkProperties to report
+ */
+ public void updateLinkProperties(TetherInterfaceStateMachine who, LinkProperties newLp) {}
}
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 cb50e9fdbfed..3aca45f8bd9a 100644
--- a/services/core/java/com/android/server/connectivity/tethering/OffloadController.java
+++ b/services/core/java/com/android/server/connectivity/tethering/OffloadController.java
@@ -105,7 +105,19 @@ public class OffloadController {
pushUpstreamParameters();
}
- // TODO: public void addDownStream(...)
+ public void notifyDownstreamLinkProperties(LinkProperties lp) {
+ if (!started()) return;
+
+ // TODO: Cache LinkProperties on a per-ifname basis and compute the
+ // deltas, calling addDownstream()/removeDownstream() accordingly.
+ }
+
+ public void removeDownstreamInterface(String ifname) {
+ if (!started()) return;
+
+ // TODO: Check cache for LinkProperties of ifname and, if present,
+ // call removeDownstream() accordingly.
+ }
private boolean isOffloadDisabled() {
// Defaults to |false| if not present.
diff --git a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
index 4a1d40590078..82b9ca07bbc0 100644
--- a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
+++ b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
@@ -90,6 +90,7 @@ public class TetherInterfaceStateMachine extends StateMachine {
private final String mIfaceName;
private final int mInterfaceType;
+ private final LinkProperties mLinkProperties;
private final IPv6TetheringInterfaceServices mIPv6TetherSvc;
private int mLastError;
@@ -106,6 +107,8 @@ public class TetherInterfaceStateMachine extends StateMachine {
mTetherController = tetherController;
mIfaceName = ifaceName;
mInterfaceType = interfaceType;
+ mLinkProperties = new LinkProperties();
+ mLinkProperties.setInterfaceName(mIfaceName);
mIPv6TetherSvc = ipv6Svc;
mLastError = ConnectivityManager.TETHER_ERROR_NO_ERROR;
@@ -127,6 +130,8 @@ public class TetherInterfaceStateMachine extends StateMachine {
public int lastError() { return mLastError; }
+ public void stop() { sendMessage(CMD_INTERFACE_DOWN); }
+
// configured when we start tethering and unconfig'd on error or conclusion
private boolean configureIfaceIp(boolean enabled) {
if (VDBG) Log.d(TAG, "configureIfaceIp(" + enabled + ")");
@@ -182,8 +187,12 @@ public class TetherInterfaceStateMachine extends StateMachine {
}
private void sendInterfaceState(int newInterfaceState) {
- mTetherController.notifyInterfaceStateChange(
- mIfaceName, TetherInterfaceStateMachine.this, newInterfaceState, mLastError);
+ mTetherController.updateInterfaceState(
+ TetherInterfaceStateMachine.this, newInterfaceState, mLastError);
+ // TODO: Populate mLinkProperties correctly, and send more sensible
+ // updates more frequently (not just here).
+ mTetherController.updateLinkProperties(
+ TetherInterfaceStateMachine.this, new LinkProperties(mLinkProperties));
}
class InitialState extends State {
@@ -195,7 +204,6 @@ public class TetherInterfaceStateMachine extends StateMachine {
@Override
public boolean processMessage(Message message) {
maybeLogMessage(this, message.what);
- boolean retValue = true;
switch (message.what) {
case CMD_TETHER_REQUESTED:
mLastError = ConnectivityManager.TETHER_ERROR_NO_ERROR;
@@ -218,10 +226,9 @@ public class TetherInterfaceStateMachine extends StateMachine {
(LinkProperties) message.obj);
break;
default:
- retValue = false;
- break;
+ return NOT_HANDLED;
}
- return retValue;
+ return HANDLED;
}
}
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 cd6038ffa0dd..b2d50515c39d 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 @@ public class UpstreamNetworkMonitor {
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 @@ public class UpstreamNetworkMonitor {
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 272c11b985c5..9a756b18c4db 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -55,7 +55,6 @@ import com.android.internal.app.NightDisplayController;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.os.BinderInternal;
-import com.android.internal.os.SamplingProfilerIntegration;
import com.android.internal.util.EmergencyAffordanceManager;
import com.android.internal.util.ConcurrentUtils;
import com.android.internal.widget.ILockSettings;
@@ -330,18 +329,6 @@ public final class SystemServer {
// 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();
@@ -710,8 +697,6 @@ public final class SystemServer {
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",
@@ -1353,21 +1338,6 @@ public final class SystemServer {
}
traceEnd();
- 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);
- }
- traceEnd();
- }
-
if (!disableNetwork && !disableNetworkTime) {
traceBeginAndSlog("StartNetworkTimeUpdateService");
try {
diff --git a/telephony/java/android/telephony/MbmsDownloadManager.java b/telephony/java/android/telephony/MbmsDownloadManager.java
index a9ec299c4cd8..c8c6d014a059 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 @@ import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
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 @@ public class MbmsDownloadManager {
"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 class MbmsDownloadManager {
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 @@ public class MbmsDownloadManager {
}
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 class MbmsDownloadManager {
*/
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 e90a63cb7881..f68e2439971f 100644
--- a/telephony/java/android/telephony/MbmsStreamingManager.java
+++ b/telephony/java/android/telephony/MbmsStreamingManager.java
@@ -27,6 +27,7 @@ import android.os.IBinder;
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 @@ public class MbmsStreamingManager {
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 @@ public class MbmsStreamingManager {
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 @@ public class MbmsStreamingManager {
}
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 @@ public class MbmsStreamingManager {
}
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 @@ public class MbmsStreamingManager {
}
}
- 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 000000000000..c01ddaedbd88
--- /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 000000000000..9842581cdc02
--- /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 000000000000..de308053df56
--- /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/tests/net/java/com/android/server/connectivity/TetheringTest.java b/tests/net/java/com/android/server/connectivity/TetheringTest.java
index bd049b841007..2137e557cd0c 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.Context;
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 @@ import java.util.Vector;
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 @@ public class TetheringTest {
}
@Override
+ public ApplicationInfo getApplicationInfo() { return mApplicationInfo; }
+
+ @Override
public ContentResolver getContentResolver() { return mContentResolver; }
@Override
@@ -389,7 +394,6 @@ public class TetheringTest {
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 c535c455e7a8..4d340d1eff33 100644
--- a/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java
+++ b/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java
@@ -30,6 +30,7 @@ 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;
@@ -59,15 +60,17 @@ import org.mockito.MockitoAnnotations;
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);
}
diff --git a/tests/net/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java b/tests/net/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java
index 27e683c0881c..57c258f8f289 100644
--- a/tests/net/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java
+++ b/tests/net/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java
@@ -16,7 +16,9 @@
package com.android.server.connectivity.tethering;
+import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.reset;
@@ -38,6 +40,7 @@ import static com.android.server.connectivity.tethering.IControlsTethering.STATE
import android.net.ConnectivityManager;
import android.net.INetworkStatsService;
import android.net.InterfaceConfiguration;
+import android.net.LinkProperties;
import android.net.util.SharedLog;
import android.os.INetworkManagementService;
import android.os.RemoteException;
@@ -103,8 +106,9 @@ public class TetherInterfaceStateMachineTest {
mIPv6TetheringInterfaceServices);
mTestedSm.start();
mLooper.dispatchAll();
- verify(mTetherHelper).notifyInterfaceStateChange(
- IFACE_NAME, mTestedSm, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
+ verify(mTetherHelper).updateInterfaceState(
+ mTestedSm, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
+ verify(mTetherHelper).updateLinkProperties(eq(mTestedSm), any(LinkProperties.class));
verifyNoMoreInteractions(mTetherHelper, mNMService, mStatsService);
}
@@ -133,8 +137,9 @@ public class TetherInterfaceStateMachineTest {
initStateMachine(TETHERING_BLUETOOTH);
dispatchCommand(TetherInterfaceStateMachine.CMD_INTERFACE_DOWN);
- verify(mTetherHelper).notifyInterfaceStateChange(
- IFACE_NAME, mTestedSm, STATE_UNAVAILABLE, TETHER_ERROR_NO_ERROR);
+ verify(mTetherHelper).updateInterfaceState(
+ mTestedSm, STATE_UNAVAILABLE, TETHER_ERROR_NO_ERROR);
+ verify(mTetherHelper).updateLinkProperties(eq(mTestedSm), any(LinkProperties.class));
verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
}
@@ -145,8 +150,10 @@ public class TetherInterfaceStateMachineTest {
dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED, STATE_TETHERED);
InOrder inOrder = inOrder(mTetherHelper, mNMService);
inOrder.verify(mNMService).tetherInterface(IFACE_NAME);
- inOrder.verify(mTetherHelper).notifyInterfaceStateChange(
- IFACE_NAME, mTestedSm, STATE_TETHERED, TETHER_ERROR_NO_ERROR);
+ inOrder.verify(mTetherHelper).updateInterfaceState(
+ mTestedSm, STATE_TETHERED, TETHER_ERROR_NO_ERROR);
+ inOrder.verify(mTetherHelper).updateLinkProperties(
+ eq(mTestedSm), any(LinkProperties.class));
verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
}
@@ -157,8 +164,10 @@ public class TetherInterfaceStateMachineTest {
dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_UNREQUESTED);
InOrder inOrder = inOrder(mNMService, mStatsService, mTetherHelper);
inOrder.verify(mNMService).untetherInterface(IFACE_NAME);
- inOrder.verify(mTetherHelper).notifyInterfaceStateChange(
- IFACE_NAME, mTestedSm, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
+ inOrder.verify(mTetherHelper).updateInterfaceState(
+ mTestedSm, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
+ inOrder.verify(mTetherHelper).updateLinkProperties(
+ eq(mTestedSm), any(LinkProperties.class));
verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
}
@@ -171,8 +180,10 @@ public class TetherInterfaceStateMachineTest {
inOrder.verify(mNMService).getInterfaceConfig(IFACE_NAME);
inOrder.verify(mNMService).setInterfaceConfig(IFACE_NAME, mInterfaceConfiguration);
inOrder.verify(mNMService).tetherInterface(IFACE_NAME);
- inOrder.verify(mTetherHelper).notifyInterfaceStateChange(
- IFACE_NAME, mTestedSm, STATE_TETHERED, TETHER_ERROR_NO_ERROR);
+ inOrder.verify(mTetherHelper).updateInterfaceState(
+ mTestedSm, STATE_TETHERED, TETHER_ERROR_NO_ERROR);
+ inOrder.verify(mTetherHelper).updateLinkProperties(
+ eq(mTestedSm), any(LinkProperties.class));
verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
}
@@ -180,7 +191,8 @@ public class TetherInterfaceStateMachineTest {
public void handlesFirstUpstreamChange() throws Exception {
initTetheredStateMachine(TETHERING_BLUETOOTH, null);
- // Telling the state machine about its upstream interface triggers a little more configuration.
+ // Telling the state machine about its upstream interface triggers
+ // a little more configuration.
dispatchTetherConnectionChanged(UPSTREAM_IFACE);
InOrder inOrder = inOrder(mNMService);
inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE);
@@ -248,8 +260,10 @@ public class TetherInterfaceStateMachineTest {
inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE);
inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE);
inOrder.verify(mNMService).untetherInterface(IFACE_NAME);
- inOrder.verify(mTetherHelper).notifyInterfaceStateChange(
- IFACE_NAME, mTestedSm, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
+ inOrder.verify(mTetherHelper).updateInterfaceState(
+ mTestedSm, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
+ inOrder.verify(mTetherHelper).updateLinkProperties(
+ eq(mTestedSm), any(LinkProperties.class));
verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
}
@@ -266,8 +280,10 @@ public class TetherInterfaceStateMachineTest {
usbTeardownOrder.verify(mInterfaceConfiguration).setInterfaceDown();
usbTeardownOrder.verify(mNMService).setInterfaceConfig(
IFACE_NAME, mInterfaceConfiguration);
- usbTeardownOrder.verify(mTetherHelper).notifyInterfaceStateChange(
- IFACE_NAME, mTestedSm, STATE_UNAVAILABLE, TETHER_ERROR_NO_ERROR);
+ usbTeardownOrder.verify(mTetherHelper).updateInterfaceState(
+ mTestedSm, STATE_UNAVAILABLE, TETHER_ERROR_NO_ERROR);
+ usbTeardownOrder.verify(mTetherHelper).updateLinkProperties(
+ eq(mTestedSm), any(LinkProperties.class));
}
}
@@ -281,8 +297,10 @@ public class TetherInterfaceStateMachineTest {
usbTeardownOrder.verify(mInterfaceConfiguration).setInterfaceDown();
usbTeardownOrder.verify(mNMService).setInterfaceConfig(
IFACE_NAME, mInterfaceConfiguration);
- usbTeardownOrder.verify(mTetherHelper).notifyInterfaceStateChange(
- IFACE_NAME, mTestedSm, STATE_AVAILABLE, TETHER_ERROR_TETHER_IFACE_ERROR);
+ usbTeardownOrder.verify(mTetherHelper).updateInterfaceState(
+ mTestedSm, STATE_AVAILABLE, TETHER_ERROR_TETHER_IFACE_ERROR);
+ usbTeardownOrder.verify(mTetherHelper).updateLinkProperties(
+ eq(mTestedSm), any(LinkProperties.class));
}
@Test
@@ -294,8 +312,10 @@ public class TetherInterfaceStateMachineTest {
InOrder usbTeardownOrder = inOrder(mNMService, mInterfaceConfiguration, mTetherHelper);
usbTeardownOrder.verify(mInterfaceConfiguration).setInterfaceDown();
usbTeardownOrder.verify(mNMService).setInterfaceConfig(IFACE_NAME, mInterfaceConfiguration);
- usbTeardownOrder.verify(mTetherHelper).notifyInterfaceStateChange(
- IFACE_NAME, mTestedSm, STATE_AVAILABLE, TETHER_ERROR_ENABLE_NAT_ERROR);
+ usbTeardownOrder.verify(mTetherHelper).updateInterfaceState(
+ mTestedSm, STATE_AVAILABLE, TETHER_ERROR_ENABLE_NAT_ERROR);
+ usbTeardownOrder.verify(mTetherHelper).updateLinkProperties(
+ eq(mTestedSm), any(LinkProperties.class));
}
@Test
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 9bb392a23d56..fb6066e46e66 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 @@ package com.android.server.connectivity.tethering;
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.Mockito;
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 @@ public class UpstreamNetworkMonitorTest {
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 class UpstreamNetworkMonitorTest {
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 @@ public class UpstreamNetworkMonitorTest {
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 class UpstreamNetworkMonitorTest {
}
}
+ 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 @@ public class UpstreamNetworkMonitorTest {
super.start();
}
}
+
+ static NetworkCapabilities copy(NetworkCapabilities nc) {
+ return new NetworkCapabilities(nc);
+ }
}