diff options
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’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); + } } |