diff options
29 files changed, 629 insertions, 370 deletions
diff --git a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java index 67d56d5060e9..dfd5996c6a00 100644 --- a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java +++ b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java @@ -208,6 +208,8 @@ public final class BluetoothLeAdvertiser { if (wrapper == null) return; stopAdvertisingSet(wrapper); + + mLegacyAdvertisers.remove(callback); } } diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index d6688e3c00f7..d5e240a8e211 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -740,7 +740,8 @@ public class Process { /** * Return the current priority of a thread, based on Linux priorities. * - * @param tid The identifier of the thread/process to change. + * @param tid The identifier of the thread/process. If tid equals zero, the priority of the + * calling process/thread will be returned. * * @return Returns the current priority, as a Linux priority level, * from -20 for highest scheduling priority to 19 for lowest scheduling diff --git a/core/jni/android_os_HwBinder.cpp b/core/jni/android_os_HwBinder.cpp index b5c881537c41..a271aad0eb47 100644 --- a/core/jni/android_os_HwBinder.cpp +++ b/core/jni/android_os_HwBinder.cpp @@ -58,6 +58,29 @@ static struct fields_t { jmethodID onTransactID; } gFields; +struct JHwBinderHolder : public RefBase { + JHwBinderHolder() {} + + sp<JHwBinder> get(JNIEnv *env, jobject obj) { + Mutex::Autolock autoLock(mLock); + + sp<JHwBinder> binder = mBinder.promote(); + + if (binder == NULL) { + binder = new JHwBinder(env, obj); + mBinder = binder; + } + + return binder; + } + +private: + Mutex mLock; + wp<JHwBinder> mBinder; + + DISALLOW_COPY_AND_ASSIGN(JHwBinderHolder); +}; + // static void JHwBinder::InitClass(JNIEnv *env) { ScopedLocalRef<jclass> clazz( @@ -75,10 +98,10 @@ void JHwBinder::InitClass(JNIEnv *env) { } // static -sp<JHwBinder> JHwBinder::SetNativeContext( - JNIEnv *env, jobject thiz, const sp<JHwBinder> &context) { - sp<JHwBinder> old = - (JHwBinder *)env->GetLongField(thiz, gFields.contextID); +sp<JHwBinderHolder> JHwBinder::SetNativeContext( + JNIEnv *env, jobject thiz, const sp<JHwBinderHolder> &context) { + sp<JHwBinderHolder> old = + (JHwBinderHolder *)env->GetLongField(thiz, gFields.contextID); if (context != NULL) { context->incStrong(NULL /* id */); @@ -94,27 +117,27 @@ sp<JHwBinder> JHwBinder::SetNativeContext( } // static -sp<JHwBinder> JHwBinder::GetNativeContext( +sp<JHwBinder> JHwBinder::GetNativeBinder( JNIEnv *env, jobject thiz) { - return (JHwBinder *)env->GetLongField(thiz, gFields.contextID); + JHwBinderHolder *holder = + reinterpret_cast<JHwBinderHolder *>( + env->GetLongField(thiz, gFields.contextID)); + + return holder->get(env, thiz); } JHwBinder::JHwBinder(JNIEnv *env, jobject thiz) { jclass clazz = env->GetObjectClass(thiz); CHECK(clazz != NULL); - mClass = (jclass)env->NewGlobalRef(clazz); - mObject = env->NewWeakGlobalRef(thiz); + mObject = env->NewGlobalRef(thiz); } JHwBinder::~JHwBinder() { JNIEnv *env = AndroidRuntime::getJNIEnv(); - env->DeleteWeakGlobalRef(mObject); + env->DeleteGlobalRef(mObject); mObject = NULL; - - env->DeleteGlobalRef(mClass); - mClass = NULL; } status_t JHwBinder::onTransact( @@ -201,10 +224,10 @@ status_t JHwBinder::onTransact( using namespace android; static void releaseNativeContext(void *nativeContext) { - sp<JHwBinder> binder = (JHwBinder *)nativeContext; + sp<JHwBinderHolder> context = static_cast<JHwBinderHolder *>(nativeContext); - if (binder != NULL) { - binder->decStrong(NULL /* id */); + if (context != NULL) { + context->decStrong(NULL /* id */); } } @@ -215,8 +238,7 @@ static jlong JHwBinder_native_init(JNIEnv *env) { } static void JHwBinder_native_setup(JNIEnv *env, jobject thiz) { - sp<JHwBinder> context = new JHwBinder(env, thiz); - + sp<JHwBinderHolder> context = new JHwBinderHolder; JHwBinder::SetNativeContext(env, thiz, context); } @@ -244,7 +266,7 @@ static void JHwBinder_native_registerService( return; // XXX exception already pending? } - sp<hardware::IBinder> binder = JHwBinder::GetNativeContext(env, thiz); + sp<hardware::IBinder> binder = JHwBinder::GetNativeBinder(env, thiz); /* TODO(b/33440494) this is not right */ sp<hidl::base::V1_0::IBase> base = new hidl::base::V1_0::BpHwBase(binder); diff --git a/core/jni/android_os_HwBinder.h b/core/jni/android_os_HwBinder.h index fa8fe01d6e93..5352f1e607c2 100644 --- a/core/jni/android_os_HwBinder.h +++ b/core/jni/android_os_HwBinder.h @@ -24,13 +24,15 @@ namespace android { +struct JHwBinderHolder; + struct JHwBinder : public hardware::BHwBinder { static void InitClass(JNIEnv *env); - static sp<JHwBinder> SetNativeContext( - JNIEnv *env, jobject thiz, const sp<JHwBinder> &context); + static sp<JHwBinderHolder> SetNativeContext( + JNIEnv *env, jobject thiz, const sp<JHwBinderHolder> &context); - static sp<JHwBinder> GetNativeContext(JNIEnv *env, jobject thiz); + static sp<JHwBinder> GetNativeBinder(JNIEnv *env, jobject thiz); JHwBinder(JNIEnv *env, jobject thiz); @@ -45,7 +47,6 @@ protected: TransactCallback callback); private: - jclass mClass; jobject mObject; DISALLOW_COPY_AND_ASSIGN(JHwBinder); diff --git a/core/jni/android_os_HwParcel.cpp b/core/jni/android_os_HwParcel.cpp index b21ea828f2a4..6ea809aa95ad 100644 --- a/core/jni/android_os_HwParcel.cpp +++ b/core/jni/android_os_HwParcel.cpp @@ -169,7 +169,6 @@ JHwParcel::JHwParcel(JNIEnv *env, jobject thiz) jclass clazz = env->GetObjectClass(thiz); CHECK(clazz != NULL); - mClass = (jclass)env->NewGlobalRef(clazz); mObject = env->NewWeakGlobalRef(thiz); } @@ -182,9 +181,6 @@ JHwParcel::~JHwParcel() { env->DeleteWeakGlobalRef(mObject); mObject = NULL; - - env->DeleteGlobalRef(mClass); - mClass = NULL; } hardware::Parcel *JHwParcel::getParcel() { @@ -542,7 +538,7 @@ static void JHwParcel_native_writeStrongBinder( env, FindClassOrDie(env, PACKAGE_PATH "/HwRemoteBinder")); if (env->IsInstanceOf(binderObj, hwBinderKlass.get())) { - binder = JHwBinder::GetNativeContext(env, binderObj); + binder = JHwBinder::GetNativeBinder(env, binderObj); } else if (env->IsInstanceOf(binderObj, hwRemoteBinderKlass.get())) { binder = JHwRemoteBinder::GetNativeContext( env, binderObj)->getBinder(); diff --git a/core/jni/android_os_HwParcel.h b/core/jni/android_os_HwParcel.h index f81de9bf30b7..f6e61004b0e3 100644 --- a/core/jni/android_os_HwParcel.h +++ b/core/jni/android_os_HwParcel.h @@ -53,7 +53,6 @@ protected: virtual ~JHwParcel(); private: - jclass mClass; jobject mObject; hardware::Parcel *mParcel; diff --git a/core/jni/android_os_HwRemoteBinder.cpp b/core/jni/android_os_HwRemoteBinder.cpp index f2f8e52db9f5..9c2ee9cfec45 100644 --- a/core/jni/android_os_HwRemoteBinder.cpp +++ b/core/jni/android_os_HwRemoteBinder.cpp @@ -272,7 +272,6 @@ JHwRemoteBinder::JHwRemoteBinder( jclass clazz = env->GetObjectClass(thiz); CHECK(clazz != NULL); - mClass = (jclass)env->NewGlobalRef(clazz); mObject = env->NewWeakGlobalRef(thiz); } @@ -281,9 +280,6 @@ JHwRemoteBinder::~JHwRemoteBinder() { env->DeleteWeakGlobalRef(mObject); mObject = NULL; - - env->DeleteGlobalRef(mClass); - mClass = NULL; } sp<hardware::IBinder> JHwRemoteBinder::getBinder() const { diff --git a/core/jni/android_os_HwRemoteBinder.h b/core/jni/android_os_HwRemoteBinder.h index 77a02784926d..63aad0ab2923 100644 --- a/core/jni/android_os_HwRemoteBinder.h +++ b/core/jni/android_os_HwRemoteBinder.h @@ -68,7 +68,6 @@ protected: virtual ~JHwRemoteBinder(); private: - jclass mClass; jobject mObject; sp<hardware::IBinder> mBinder; diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java index 4a5129d6b8e2..0f9d881472cc 100644 --- a/services/core/java/com/android/server/connectivity/Tethering.java +++ b/services/core/java/com/android/server/connectivity/Tethering.java @@ -1258,10 +1258,10 @@ public class Tethering extends BaseNetworkObserver { sendMessageDelayed(CMD_RETRY_UPSTREAM, UPSTREAM_SETTLE_TIME_MS); } } - setUpstreamByType(ns); + setUpstreamNetwork(ns); } - protected void setUpstreamByType(NetworkState ns) { + protected void setUpstreamNetwork(NetworkState ns) { String iface = null; if (ns != null && ns.linkProperties != null) { // Find the interface with the default IPv4 route. It may be the @@ -1786,7 +1786,9 @@ public class Tethering extends BaseNetworkObserver { } } - mLog.log(String.format("OBSERVED LinkProperties update iface=%s state=%s", iface, state)); + mLog.log(String.format( + "OBSERVED LinkProperties update iface=%s state=%s lp=%s", + iface, IControlsTethering.getStateString(state), newLp)); final int which = TetherMasterSM.EVENT_IFACE_UPDATE_LINKPROPERTIES; mTetherMasterSM.sendMessage(which, state, 0, newLp); } 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 aaa63b1613df..2b813475222f 100644 --- a/services/core/java/com/android/server/connectivity/tethering/IControlsTethering.java +++ b/services/core/java/com/android/server/connectivity/tethering/IControlsTethering.java @@ -33,6 +33,16 @@ public class IControlsTethering { public static final int STATE_TETHERED = 2; public static final int STATE_LOCAL_ONLY = 3; + public static String getStateString(int state) { + switch (state) { + case STATE_UNAVAILABLE: return "UNAVAILABLE"; + case STATE_AVAILABLE: return "AVAILABLE"; + case STATE_TETHERED: return "TETHERED"; + case STATE_LOCAL_ONLY: return "LOCAL_ONLY"; + } + return "UNKNOWN: " + state; + } + /** * Notify that |who| has changed its tethering state. * 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 ce6b8be77d44..08deef84f3ae 100644 --- a/services/core/java/com/android/server/connectivity/tethering/OffloadController.java +++ b/services/core/java/com/android/server/connectivity/tethering/OffloadController.java @@ -85,13 +85,16 @@ public class OffloadController { mLog.i("tethering offload control not supported"); stop(); } + mLog.log("tethering offload started"); } public void stop() { + final boolean wasStarted = started(); mUpstreamLinkProperties = null; mHwInterface.stopOffloadControl(); mControlInitialized = false; mConfigInitialized = false; + if (wasStarted) mLog.log("tethering offload stopped"); } public void setUpstreamLinkProperties(LinkProperties lp) { 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 bff13d4cf8a6..86b255128d6d 100644 --- a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java +++ b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java @@ -56,9 +56,10 @@ import java.util.Objects; import java.util.Random; /** - * @hide + * Provides the interface to IP-layer serving functionality for a given network + * interface, e.g. for tethering or "local-only hotspot" mode. * - * Tracks the eligibility of a given network interface for tethering. + * @hide */ public class TetherInterfaceStateMachine extends StateMachine { private static final IpPrefix LINK_LOCAL_PREFIX = new IpPrefix("fe80::/64"); @@ -117,6 +118,12 @@ public class TetherInterfaceStateMachine extends StateMachine { private String mMyUpstreamIfaceName; // may change over time private NetworkInterface mNetworkInterface; private byte[] mHwAddr; + // TODO: De-duplicate this with mLinkProperties above. Currently, these link + // properties are those selected by the IPv6TetheringCoordinator and relayed + // to us. By comparison, mLinkProperties contains the addresses and directly + // connected routes that have been formed from these properties iff. we have + // succeeded in configuring them and are able to announce them within Router + // Advertisements (otherwise, we do not add them to mLinkProperties at all). private LinkProperties mLastIPv6LinkProperties; private RouterAdvertisementDaemon mRaDaemon; private RaParams mLastRaParams; @@ -133,7 +140,7 @@ public class TetherInterfaceStateMachine extends StateMachine { mIfaceName = ifaceName; mInterfaceType = interfaceType; mLinkProperties = new LinkProperties(); - mLinkProperties.setInterfaceName(mIfaceName); + resetLinkProperties(); mLastError = ConnectivityManager.TETHER_ERROR_NO_ERROR; mInitialState = new InitialState(); @@ -162,10 +169,15 @@ public class TetherInterfaceStateMachine extends StateMachine { * Internals. */ - // configured when we start tethering and unconfig'd on error or conclusion - private boolean configureIfaceIp(boolean enabled) { - if (VDBG) Log.d(TAG, "configureIfaceIp(" + enabled + ")"); + private boolean startIPv4() { return configureIPv4(true); } + private void stopIPv4() { configureIPv4(false); } + + private boolean configureIPv4(boolean enabled) { + if (VDBG) Log.d(TAG, "configureIPv4(" + enabled + ")"); + + // TODO: Replace this hard-coded information with dynamically selected + // config passed down to us by a higher layer IP-coordinating element. String ipAsString = null; int prefixLen = 0; if (mInterfaceType == ConnectivityManager.TETHERING_USB) { @@ -179,32 +191,45 @@ public class TetherInterfaceStateMachine extends StateMachine { return true; } - InterfaceConfiguration ifcg = null; + final LinkAddress linkAddr; try { - ifcg = mNMService.getInterfaceConfig(mIfaceName); - if (ifcg != null) { - InetAddress addr = NetworkUtils.numericToInetAddress(ipAsString); - ifcg.setLinkAddress(new LinkAddress(addr, prefixLen)); - if (mInterfaceType == ConnectivityManager.TETHERING_WIFI) { - // The WiFi stack has ownership of the interface up/down state. - // It is unclear whether the bluetooth or USB stacks will manage their own - // state. - ifcg.ignoreInterfaceUpDownStatus(); + final InterfaceConfiguration ifcg = mNMService.getInterfaceConfig(mIfaceName); + if (ifcg == null) { + mLog.e("Received null interface config"); + return false; + } + + InetAddress addr = NetworkUtils.numericToInetAddress(ipAsString); + linkAddr = new LinkAddress(addr, prefixLen); + ifcg.setLinkAddress(linkAddr); + if (mInterfaceType == ConnectivityManager.TETHERING_WIFI) { + // The WiFi stack has ownership of the interface up/down state. + // It is unclear whether the Bluetooth or USB stacks will manage their own + // state. + ifcg.ignoreInterfaceUpDownStatus(); + } else { + if (enabled) { + ifcg.setInterfaceUp(); } else { - if (enabled) { - ifcg.setInterfaceUp(); - } else { - ifcg.setInterfaceDown(); - } + ifcg.setInterfaceDown(); } - ifcg.clearFlag("running"); - mNMService.setInterfaceConfig(mIfaceName, ifcg); } + ifcg.clearFlag("running"); + mNMService.setInterfaceConfig(mIfaceName, ifcg); } catch (Exception e) { mLog.e("Error configuring interface " + e); return false; } + // Directly-connected route. + final RouteInfo route = new RouteInfo(linkAddr); + if (enabled) { + mLinkProperties.addLinkAddress(linkAddr); + mLinkProperties.addRoute(route); + } else { + mLinkProperties.removeLinkAddress(linkAddr); + mLinkProperties.removeRoute(route); + } return true; } @@ -294,7 +319,7 @@ public class TetherInterfaceStateMachine extends StateMachine { mLastIPv6LinkProperties = v6only; } - private void configureLocalRoutes( + private void configureLocalIPv6Routes( HashSet<IpPrefix> deprecatedPrefixes, HashSet<IpPrefix> newPrefixes) { // [1] Remove the routes that are deprecated. if (!deprecatedPrefixes.isEmpty()) { @@ -309,6 +334,8 @@ public class TetherInterfaceStateMachine extends StateMachine { } catch (RemoteException e) { mLog.e("Failed to remove IPv6 routes from local table: " + e); } + + for (RouteInfo route : toBeRemoved) mLinkProperties.removeRoute(route); } // [2] Add only the routes that have not previously been added. @@ -340,11 +367,13 @@ public class TetherInterfaceStateMachine extends StateMachine { } catch (RemoteException e) { mLog.e("Failed to add IPv6 routes to local table: " + e); } + + for (RouteInfo route : toBeAdded) mLinkProperties.addRoute(route); } } } - private void configureLocalDns( + private void configureLocalIPv6Dns( HashSet<Inet6Address> deprecatedDnses, HashSet<Inet6Address> newDnses) { final INetd netd = NetdService.getInstance(); if (netd == null) { @@ -362,6 +391,8 @@ public class TetherInterfaceStateMachine extends StateMachine { } catch (ServiceSpecificException | RemoteException e) { mLog.e("Failed to remove local dns IP " + dnsString + ": " + e); } + + mLinkProperties.removeLinkAddress(new LinkAddress(dns, RFC7421_PREFIX_LENGTH)); } } @@ -380,6 +411,8 @@ public class TetherInterfaceStateMachine extends StateMachine { mLog.e("Failed to add local dns IP " + dnsString + ": " + e); newDnses.remove(dns); } + + mLinkProperties.addLinkAddress(new LinkAddress(dns, RFC7421_PREFIX_LENGTH)); } } @@ -396,10 +429,10 @@ public class TetherInterfaceStateMachine extends StateMachine { final RaParams deprecatedParams = RaParams.getDeprecatedRaParams(mLastRaParams, newParams); - configureLocalRoutes(deprecatedParams.prefixes, + configureLocalIPv6Routes(deprecatedParams.prefixes, (newParams != null) ? newParams.prefixes : null); - configureLocalDns(deprecatedParams.dnses, + configureLocalIPv6Dns(deprecatedParams.dnses, (newParams != null) ? newParams.dnses : null); mRaDaemon.buildNewRa(deprecatedParams, newParams); @@ -419,12 +452,19 @@ public class TetherInterfaceStateMachine extends StateMachine { private void sendInterfaceState(int newInterfaceState) { mTetherController.updateInterfaceState( TetherInterfaceStateMachine.this, newInterfaceState, mLastError); - // TODO: Populate mLinkProperties correctly, and send more sensible - // updates more frequently (not just here). + sendLinkProperties(); + } + + private void sendLinkProperties() { mTetherController.updateLinkProperties( TetherInterfaceStateMachine.this, new LinkProperties(mLinkProperties)); } + private void resetLinkProperties() { + mLinkProperties.clear(); + mLinkProperties.setInterfaceName(mIfaceName); + } + class InitialState extends State { @Override public void enter() { @@ -464,7 +504,7 @@ public class TetherInterfaceStateMachine extends StateMachine { class BaseServingState extends State { @Override public void enter() { - if (!configureIfaceIp(true)) { + if (!startIPv4()) { mLastError = ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR; return; } @@ -498,7 +538,9 @@ public class TetherInterfaceStateMachine extends StateMachine { mLog.e("Failed to untether interface: " + e); } - configureIfaceIp(false); + stopIPv4(); + + resetLinkProperties(); } @Override @@ -515,6 +557,7 @@ public class TetherInterfaceStateMachine extends StateMachine { break; case CMD_IPV6_TETHER_UPDATE: updateUpstreamIPv6LinkProperties((LinkProperties) message.obj); + sendLinkProperties(); break; case CMD_IP_FORWARDING_ENABLE_ERROR: case CMD_IP_FORWARDING_DISABLE_ERROR: @@ -625,7 +668,6 @@ public class TetherInterfaceStateMachine extends StateMachine { if (super.processMessage(message)) return true; maybeLogMessage(this, message.what); - boolean retValue = true; switch (message.what) { case CMD_TETHER_REQUESTED: mLog.e("CMD_TETHER_REQUESTED while already tethering."); @@ -655,10 +697,9 @@ public class TetherInterfaceStateMachine extends StateMachine { mMyUpstreamIfaceName = newUpstreamIfaceName; break; default: - retValue = false; - break; + return false; } - return retValue; + return true; } } diff --git a/telephony/java/android/telephony/MbmsDownloadManager.java b/telephony/java/android/telephony/MbmsDownloadManager.java index c8c6d014a059..79ee37a168d4 100644 --- a/telephony/java/android/telephony/MbmsDownloadManager.java +++ b/telephony/java/android/telephony/MbmsDownloadManager.java @@ -16,10 +16,13 @@ package android.telephony; +import android.annotation.NonNull; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; +import android.content.SharedPreferences; +import android.content.pm.ResolveInfo; import android.net.Uri; import android.os.IBinder; import android.os.RemoteException; @@ -27,13 +30,18 @@ import android.telephony.mbms.IDownloadCallback; import android.telephony.mbms.DownloadRequest; import android.telephony.mbms.DownloadStatus; import android.telephony.mbms.IMbmsDownloadManagerCallback; +import android.telephony.mbms.MbmsDownloadManagerCallback; +import android.telephony.mbms.MbmsDownloadReceiver; import android.telephony.mbms.MbmsException; +import android.telephony.mbms.MbmsTempFileProvider; import android.telephony.mbms.MbmsUtils; import android.telephony.mbms.vendor.IMbmsDownloadService; 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.atomic.AtomicReference; import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; @@ -47,7 +55,7 @@ public class MbmsDownloadManager { * The MBMS middleware should send this when a download of single file has completed or * failed. Mandatory extras are * {@link #EXTRA_RESULT} - * {@link #EXTRA_INFO} + * {@link #EXTRA_FILE_INFO} * {@link #EXTRA_REQUEST} * {@link #EXTRA_TEMP_LIST} * {@link #EXTRA_FINAL_URI} @@ -93,7 +101,7 @@ public class MbmsDownloadManager { * 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"; + public static final String EXTRA_FILE_INFO = "android.telephony.mbms.extra.FILE_INFO"; /** * Extra containing the {@link DownloadRequest} for which the download result or file @@ -144,6 +152,14 @@ public class MbmsDownloadManager { "android.telephony.mbms.extra.PAUSED_URI_LIST"; /** + * Extra containing a string that points to the middleware's knowledge of where the temp file + * root for the app is. The path should be a canonical path as returned by + * {@link File#getCanonicalPath()} + */ + public static final String EXTRA_TEMP_FILE_ROOT = + "android.telephony.mbms.extra.TEMP_FILE_ROOT"; + + /** * Extra containing a list of {@link Uri}s indicating temp files which the middleware is * still using. */ @@ -165,12 +181,10 @@ public class MbmsDownloadManager { 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; + private int mSubscriptionId = INVALID_SUBSCRIPTION_ID; - private IMbmsDownloadService mService; + private AtomicReference<IMbmsDownloadService> mService = new AtomicReference<>(null); private final IMbmsDownloadManagerCallback mCallback; private final String mDownloadAppName; @@ -179,116 +193,221 @@ public class MbmsDownloadManager { mContext = context; mCallback = callback; mDownloadAppName = downloadAppName; - mSubId = subId; + mSubscriptionId = subId; } /** * Create a new MbmsDownloadManager using the system default data subscription ID. - * - * Note that this call will bind a remote service and that may take a bit. This - * may throw an Illegal ArgumentException or RemoteException. + * See {@link #create(Context, IMbmsDownloadManagerCallback, String, int)} * * @hide */ - public static MbmsDownloadManager createManager(Context context, + public static MbmsDownloadManager create(Context context, IMbmsDownloadManagerCallback listener, String downloadAppName) throws MbmsException { - MbmsDownloadManager mdm = new MbmsDownloadManager(context, listener, downloadAppName, + return create(context, listener, downloadAppName, SubscriptionManager.getDefaultSubscriptionId()); - mdm.bindAndInitialize(); - return mdm; } /** * Create a new MbmsDownloadManager using the given subscription ID. * - * Note that this call will bind a remote service and that may take a bit. This - * may throw an Illegal ArgumentException or RemoteException. + * Note that this call will bind a remote service and that may take a bit. The instance of + * {@link MbmsDownloadManager} that is returned will not be ready for use until + * {@link IMbmsDownloadManagerCallback#middlewareReady()} is called on the provided callback. + * If you attempt to use the manager before it is ready, a {@link MbmsException} will be thrown. + * + * This also may throw an {@link IllegalArgumentException} or a {@link MbmsException}. * + * @param context The instance of {@link Context} to use + * @param listener A callback to get asynchronous error messages and file service updates. + * @param downloadAppName The app name, as negotiated with the eMBMS provider + * @param subscriptionId The data subscription ID to use * @hide */ - - public static MbmsDownloadManager createManager(Context context, - IMbmsDownloadManagerCallback listener, String downloadAppName, int subId) + public static MbmsDownloadManager create(Context context, + IMbmsDownloadManagerCallback listener, String downloadAppName, int subscriptionId) throws MbmsException { MbmsDownloadManager mdm = new MbmsDownloadManager(context, listener, downloadAppName, - subId); + subscriptionId); mdm.bindAndInitialize(); return mdm; } private void bindAndInitialize() throws MbmsException { - // 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 + MbmsUtils.startBinding(mContext, MBMS_DOWNLOAD_SERVICE_ACTION, + new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + IMbmsDownloadService downloadService = + IMbmsDownloadService.Stub.asInterface(service); + try { + downloadService.initialize( + mDownloadAppName, mSubscriptionId, mCallback); + } catch (RemoteException e) { + Log.e(LOG_TAG, "Service died before initialization"); + return; + } + mService.set(downloadService); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + mService.set(null); + } + }); } /** - * Gets the list of files published for download. - * They may occur at times far in the future. - * servicesClasses lets the app filter on types of files and is opaque data between - * the app and the carrier + * An inspection API to retrieve the list of available + * {@link android.telephony.mbms.FileServiceInfo}s currently being advertised. + * The results are returned asynchronously via a call to + * {@link IMbmsDownloadManagerCallback#fileServicesUpdated(List)} * - * Multiple calls replace trhe list of serviceClasses of interest. + * The serviceClasses argument lets the app filter on types of programming and is opaque data + * negotiated beforehand between the app and the carrier. * - * May throw an IllegalArgumentException or RemoteException. + * Multiple calls replace the list of serviceClasses of interest. * - * Synchronous responses include - * <li>SUCCESS</li> - * <li>ERROR_MSDC_CONCURRENT_SERVICE_LIMIT_REACHED</li> + * This may throw an {@link MbmsException} containing one of the following errors: + * {@link MbmsException#ERROR_MIDDLEWARE_NOT_BOUND} + * {@link MbmsException#ERROR_CONCURRENT_SERVICE_LIMIT_REACHED} + * {@link MbmsException#ERROR_SERVICE_LOST} * - * Asynchronous errors through the listener include any of the errors except - * <li>ERROR_MSDC_UNABLE_TO_)START_SERVICE</li> - * <li>ERROR_MSDC_INVALID_SERVICE_ID</li> - * <li>ERROR_MSDC_END_OF_SESSION</li> + * Asynchronous error codes via the {@link MbmsDownloadManagerCallback#error(int, String)} + * callback can include any of the errors except: + * {@link MbmsException#ERROR_UNABLE_TO_START_SERVICE} + * {@link MbmsException#ERROR_END_OF_SESSION} */ - public int getFileServices(List<String> serviceClasses) { - return 0; + public void getFileServices(List<String> classList) throws MbmsException { + IMbmsDownloadService downloadService = mService.get(); + if (downloadService == null) { + throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND); + } + try { + int returnCode = downloadService.getFileServices( + mDownloadAppName, mSubscriptionId, classList); + if (returnCode != MbmsException.SUCCESS) { + throw new MbmsException(returnCode); + } + } catch (RemoteException e) { + Log.w(LOG_TAG, "Remote process died"); + mService.set(null); + throw new MbmsException(MbmsException.ERROR_SERVICE_LOST); + } } + /** + * Sets the temp file root for downloads. + * All temp files created for the middleware to write to will be contained in the specified + * directory. Applications that wish to specify a location only need to call this method once + * as long their data is persisted in storage -- the argument will be stored both in a + * local instance of {@link android.content.SharedPreferences} and by the middleware. + * + * If this method is not called at least once before calling + * {@link #download(DownloadRequest, IDownloadCallback)}, the framework + * will default to a directory formed by the concatenation of the app's files directory and + * {@link android.telephony.mbms.MbmsTempFileProvider#DEFAULT_TOP_LEVEL_TEMP_DIRECTORY}. + * + * This method may not be called while any download requests are still active. If this is + * the case, an {@link MbmsException} will be thrown with code + * {@link MbmsException#ERROR_CANNOT_CHANGE_TEMP_FILE_ROOT} + * + * The {@link File} supplied as a root temp file directory must already exist. If not, an + * {@link IllegalArgumentException} will be thrown. + * @param tempFileRootDirectory A directory to place temp files in. + */ + public void setTempFileRootDirectory(@NonNull File tempFileRootDirectory) + throws MbmsException { + IMbmsDownloadService downloadService = mService.get(); + if (downloadService == null) { + throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND); + } + if (!tempFileRootDirectory.exists()) { + throw new IllegalArgumentException("Provided directory does not exist"); + } + if (!tempFileRootDirectory.isDirectory()) { + throw new IllegalArgumentException("Provided File is not a directory"); + } + String filePath; + try { + filePath = tempFileRootDirectory.getCanonicalPath(); + } catch (IOException e) { + throw new IllegalArgumentException("Unable to canonicalize the provided path: " + e); + } + + try { + int result = downloadService.setTempFileRootDirectory( + mDownloadAppName, mSubscriptionId, filePath); + if (result != MbmsException.SUCCESS) { + throw new MbmsException(result); + } + } catch (RemoteException e) { + mService.set(null); + throw new MbmsException(MbmsException.ERROR_SERVICE_LOST); + } + + SharedPreferences prefs = mContext.getSharedPreferences( + MbmsTempFileProvider.TEMP_FILE_ROOT_PREF_FILE_NAME, 0); + prefs.edit().putString(MbmsTempFileProvider.TEMP_FILE_ROOT_PREF_NAME, filePath).apply(); + } /** - * Requests a future download. - * returns a token which may be used to cancel a download. + * Requests a download of a file that is available via multicast. + * * downloadListener is an optional callback object which can be used to get progress reports * of a currently occuring download. Note this can only run while the calling app * is running, so future downloads will simply result in resultIntents being sent * for completed or errored-out downloads. A NULL indicates no callbacks are needed. * - * May throw an IllegalArgumentException or RemoteExcpetion. + * May throw an {@link IllegalArgumentException} + * + * If {@link #setTempFileRootDirectory(File)} has not called after the app has been installed, + * this method will create a directory at the default location defined at + * {@link MbmsTempFileProvider#DEFAULT_TOP_LEVEL_TEMP_DIRECTORY} and store that as the temp + * file root directory. * * Asynchronous errors through the listener include any of the errors + * + * @param request The request that specifies what should be downloaded + * @param callback Optional callback that will provide progress updates if the app is running. */ - public DownloadRequest download(DownloadRequest request, IDownloadCallback listener) { + public void download(DownloadRequest request, IDownloadCallback callback) + throws MbmsException { + IMbmsDownloadService downloadService = mService.get(); + if (downloadService == null) { + throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND); + } + + // Check to see whether the app's set a temp root dir yet, and set it if not. + SharedPreferences prefs = mContext.getSharedPreferences( + MbmsTempFileProvider.TEMP_FILE_ROOT_PREF_FILE_NAME, 0); + if (prefs.getString(MbmsTempFileProvider.TEMP_FILE_ROOT_PREF_NAME, null) == null) { + File tempRootDirectory = new File(mContext.getFilesDir(), + MbmsTempFileProvider.DEFAULT_TOP_LEVEL_TEMP_DIRECTORY); + tempRootDirectory.mkdirs(); + setTempFileRootDirectory(tempRootDirectory); + } + request.setAppName(mDownloadAppName); + // Check if the request is a multipart download. If so, validate that the destination is + // a directory that exists. + // TODO: figure out what qualifies a request as a multipart download request. + if (request.getSourceUri().getLastPathSegment() != null && + request.getSourceUri().getLastPathSegment().contains("*")) { + File toFile = new File(request.getDestinationUri().getSchemeSpecificPart()); + if (!toFile.isDirectory()) { + throw new IllegalArgumentException("Multipart download must specify valid " + + "destination directory."); + } + } + // TODO: check to make sure destination is clear + // TODO: write download request token try { - mService.download(request, listener); + downloadService.download(request, callback); } catch (RemoteException e) { - mService = null; + mService.set(null); } - return request; } /** @@ -355,13 +474,45 @@ public class MbmsDownloadManager { return 0; } + /** + * Retrieves the {@link ComponentName} for the {@link android.content.BroadcastReceiver} that + * the various intents from the middleware should be targeted towards. + * @param uid The uid of the frontend app. + * @return The component name of the receiver that the middleware should send its intents to, + * or null if the app didn't declare it in the manifest. + * + * @hide + * future systemapi + */ + public static ComponentName getAppReceiverFromUid(Context context, int uid) { + String[] packageNames = context.getPackageManager().getPackagesForUid(uid); + if (packageNames == null) { + return null; + } + + for (String packageName : packageNames) { + ComponentName candidate = new ComponentName(packageName, + MbmsDownloadReceiver.class.getCanonicalName()); + Intent queryIntent = new Intent(); + queryIntent.setComponent(candidate); + List<ResolveInfo> receivers = + context.getPackageManager().queryBroadcastReceivers(queryIntent, 0); + if (receivers != null && receivers.size() > 0) { + return candidate; + } + } + return null; + } + public void dispose() { try { - if (mService != null) { - mService.dispose(mDownloadAppName, mSubId); - } else { + IMbmsDownloadService downloadService = mService.get(); + if (downloadService == null) { Log.i(LOG_TAG, "Service already dead"); + return; } + downloadService.dispose(mDownloadAppName, mSubscriptionId); + mService.set(null); } catch (RemoteException e) { // Ignore Log.i(LOG_TAG, "Remote exception while disposing of service"); diff --git a/telephony/java/android/telephony/MbmsStreamingManager.java b/telephony/java/android/telephony/MbmsStreamingManager.java index f68e2439971f..af7f333390d8 100644 --- a/telephony/java/android/telephony/MbmsStreamingManager.java +++ b/telephony/java/android/telephony/MbmsStreamingManager.java @@ -18,11 +18,7 @@ package android.telephony; import android.content.ComponentName; import android.content.Context; -import android.content.Intent; import android.content.ServiceConnection; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.os.DeadObjectException; import android.os.IBinder; import android.os.RemoteException; import android.telephony.mbms.MbmsException; @@ -34,56 +30,18 @@ import android.telephony.mbms.StreamingServiceInfo; import android.telephony.mbms.vendor.IMbmsStreamingService; import android.util.Log; -import java.util.LinkedList; import java.util.List; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; /** @hide */ public class MbmsStreamingManager { - private interface ServiceListener { - void onServiceConnected(); - void onServiceDisconnected(); - } - private static final String LOG_TAG = "MbmsStreamingManager"; public static final String MBMS_STREAMING_SERVICE_ACTION = "android.telephony.action.EmbmsStreaming"; - private static final boolean DEBUG = true; - private static final int BIND_TIMEOUT_MS = 3000; - - private IMbmsStreamingService mService; - private ServiceConnection mServiceConnection = new ServiceConnection() { - @Override - public void onServiceConnected(ComponentName name, IBinder service) { - if (service != null) { - Log.i(LOG_TAG, String.format("Connected to service %s", name)); - synchronized (MbmsStreamingManager.this) { - mService = IMbmsStreamingService.Stub.asInterface(service); - for (ServiceListener l : mServiceListeners) { - l.onServiceConnected(); - } - } - } - } - - @Override - public void onServiceDisconnected(ComponentName name) { - Log.i(LOG_TAG, String.format("Disconnected from service %s", name)); - synchronized (MbmsStreamingManager.this) { - mService = null; - for (ServiceListener l : mServiceListeners) { - l.onServiceDisconnected(); - } - } - } - }; - - private List<ServiceListener> mServiceListeners = new LinkedList<>(); - + private AtomicReference<IMbmsStreamingService> mService = new AtomicReference<>(null); private MbmsStreamingManagerCallback mCallbackToApp; private final String mAppName; @@ -128,28 +86,26 @@ public class MbmsStreamingManager { public static MbmsStreamingManager create(Context context, MbmsStreamingManagerCallback listener, String streamingAppName) throws MbmsException { - int subId = SubscriptionManager.getDefaultSubscriptionId(); - MbmsStreamingManager manager = new MbmsStreamingManager(context, listener, - streamingAppName, subId); - manager.bindAndInitialize(); - return manager; + return create(context, listener, streamingAppName, + SubscriptionManager.getDefaultSubscriptionId()); } /** * Terminates this instance, ending calls to the registered listener. Also terminates * any streaming services spawned from this instance. */ - public synchronized void dispose() { - if (mService == null) { + public void dispose() { + IMbmsStreamingService streamingService = mService.get(); + if (streamingService == null) { // Ignore and return, assume already disposed. return; } try { - mService.dispose(mAppName, mSubscriptionId); + streamingService.dispose(mAppName, mSubscriptionId); } catch (RemoteException e) { // Ignore for now } - mService = null; + mService.set(null); } /** @@ -171,17 +127,19 @@ public class MbmsStreamingManager { * {@link MbmsException#ERROR_END_OF_SESSION} */ public void getStreamingServices(List<String> classList) throws MbmsException { - if (mService == null) { + IMbmsStreamingService streamingService = mService.get(); + if (streamingService == null) { throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND); } try { - int returnCode = mService.getStreamingServices(mAppName, mSubscriptionId, classList); + int returnCode = streamingService.getStreamingServices( + mAppName, mSubscriptionId, classList); if (returnCode != MbmsException.SUCCESS) { throw new MbmsException(returnCode); } } catch (RemoteException e) { Log.w(LOG_TAG, "Remote process died"); - mService = null; + mService.set(null); throw new MbmsException(MbmsException.ERROR_SERVICE_LOST); } } @@ -190,7 +148,7 @@ public class MbmsStreamingManager { * Starts streaming a requested service, reporting status to the indicated listener. * Returns an object used to control that stream. The stream may not be ready for consumption * immediately upon return from this method -- wait until the streaming state has been - * reported via {@link android.telephony.mbms.StreamingServiceCallback#streamStateChanged(int)}. + * reported via {@link android.telephony.mbms.StreamingServiceCallback#streamStateUpdated(int)} * * May throw an {@link MbmsException} containing any of the following error codes: * {@link MbmsException#ERROR_MIDDLEWARE_NOT_BOUND} @@ -203,71 +161,47 @@ public class MbmsStreamingManager { */ public StreamingService startStreaming(StreamingServiceInfo serviceInfo, StreamingServiceCallback listener) throws MbmsException { - if (mService == null) { + IMbmsStreamingService streamingService = mService.get(); + if (streamingService == null) { throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND); } try { - int returnCode = mService.startStreaming( + int returnCode = streamingService.startStreaming( mAppName, mSubscriptionId, serviceInfo.getServiceId(), listener); if (returnCode != MbmsException.SUCCESS) { throw new MbmsException(returnCode); } } catch (RemoteException e) { Log.w(LOG_TAG, "Remote process died"); - mService = null; + mService.set(null); throw new MbmsException(MbmsException.ERROR_SERVICE_LOST); } return new StreamingService( - mAppName, mSubscriptionId, mService, serviceInfo, listener); + mAppName, mSubscriptionId, streamingService, serviceInfo, listener); } private void bindAndInitialize() throws MbmsException { - // Kick off the binding, and synchronously wait until binding is complete - final CountDownLatch latch = new CountDownLatch(1); - ServiceListener bindListener = new ServiceListener() { - @Override - public void onServiceConnected() { - latch.countDown(); - } - - @Override - public void onServiceDisconnected() { - } - }; - - synchronized (this) { - mServiceListeners.add(bindListener); - } - - Intent bindIntent = new Intent(); - bindIntent.setComponent(MbmsUtils.toComponentName( - MbmsUtils.getMiddlewareServiceInfo(mContext, MBMS_STREAMING_SERVICE_ACTION))); - - mContext.bindService(bindIntent, mServiceConnection, Context.BIND_AUTO_CREATE); - - MbmsUtils.waitOnLatchWithTimeout(latch, BIND_TIMEOUT_MS); - - // Remove the listener and call the initialization method through the interface. - synchronized (this) { - mServiceListeners.remove(bindListener); - - if (mService == null) { - throw new MbmsException(MbmsException.ERROR_BIND_TIMEOUT_OR_FAILURE); - } + MbmsUtils.startBinding(mContext, MBMS_STREAMING_SERVICE_ACTION, + new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + IMbmsStreamingService streamingService = + IMbmsStreamingService.Stub.asInterface(service); + try { + streamingService.initialize(mCallbackToApp, mAppName, mSubscriptionId); + } catch (RemoteException e) { + Log.e(LOG_TAG, "Service died before initialization"); + return; + } + mService.set(null); + } - try { - int returnCode = mService.initialize(mCallbackToApp, mAppName, mSubscriptionId); - if (returnCode != MbmsException.SUCCESS) { - throw new MbmsException(returnCode); - } - } catch (RemoteException e) { - mService = null; - Log.e(LOG_TAG, "Service died before initialization"); - throw new MbmsException(MbmsException.ERROR_SERVICE_LOST); - } - } + @Override + public void onServiceDisconnected(ComponentName name) { + mService.set(null); + } + }); } - } diff --git a/telephony/java/android/telephony/mbms/DownloadRequest.java b/telephony/java/android/telephony/mbms/DownloadRequest.java index f3ca05840da2..c561741cc80c 100644 --- a/telephony/java/android/telephony/mbms/DownloadRequest.java +++ b/telephony/java/android/telephony/mbms/DownloadRequest.java @@ -35,9 +35,8 @@ public class DownloadRequest implements Parcelable { private FileServiceInfo serviceInfo; private Uri source; private Uri dest; - private int sub; + private int subscriptionId; private String appIntent; - private String appName; // not the Android app Name, the embms app Name public Builder setId(int id) { this.id = id; @@ -59,8 +58,8 @@ public class DownloadRequest implements Parcelable { return this; } - public Builder setSub(int sub) { - this.sub = sub; + public Builder setSubscriptionId(int sub) { + this.subscriptionId = sub; return this; } @@ -70,7 +69,8 @@ public class DownloadRequest implements Parcelable { } public DownloadRequest build() { - return new DownloadRequest(id, serviceInfo, source, dest, sub, appIntent, appName); + return new DownloadRequest(id, serviceInfo, source, dest, + subscriptionId, appIntent, null); } } @@ -78,7 +78,7 @@ public class DownloadRequest implements Parcelable { private final FileServiceInfo fileServiceInfo; private final Uri sourceUri; private final Uri destinationUri; - private final int subId; + private final int subscriptionId; private final String serializedResultIntentForApp; private String appName; // not the Android app Name, the embms app name @@ -89,7 +89,7 @@ public class DownloadRequest implements Parcelable { fileServiceInfo = serviceInfo; sourceUri = source; destinationUri = dest; - subId = sub; + subscriptionId = sub; serializedResultIntentForApp = appIntent; appName = name; } @@ -103,7 +103,7 @@ public class DownloadRequest implements Parcelable { fileServiceInfo = dr.fileServiceInfo; sourceUri = dr.sourceUri; destinationUri = dr.destinationUri; - subId = dr.subId; + subscriptionId = dr.subscriptionId; serializedResultIntentForApp = dr.serializedResultIntentForApp; appName = dr.appName; } @@ -113,7 +113,7 @@ public class DownloadRequest implements Parcelable { fileServiceInfo = in.readParcelable(getClass().getClassLoader()); sourceUri = in.readParcelable(getClass().getClassLoader()); destinationUri = in.readParcelable(getClass().getClassLoader()); - subId = in.readInt(); + subscriptionId = in.readInt(); serializedResultIntentForApp = in.readString(); appName = in.readString(); } @@ -127,7 +127,7 @@ public class DownloadRequest implements Parcelable { out.writeParcelable(fileServiceInfo, flags); out.writeParcelable(sourceUri, flags); out.writeParcelable(destinationUri, flags); - out.writeInt(subId); + out.writeInt(subscriptionId); out.writeString(serializedResultIntentForApp); out.writeString(appName); } @@ -148,8 +148,8 @@ public class DownloadRequest implements Parcelable { return destinationUri; } - public int getSubId() { - return subId; + public int getSubscriptionId() { + return subscriptionId; } public Intent getIntentForApp() { diff --git a/telephony/java/android/telephony/mbms/FileInfo.java b/telephony/java/android/telephony/mbms/FileInfo.java index d3888bdd2c9a..1b873938a3f2 100644 --- a/telephony/java/android/telephony/mbms/FileInfo.java +++ b/telephony/java/android/telephony/mbms/FileInfo.java @@ -31,29 +31,22 @@ public class FileInfo implements Parcelable { * This is used internally but is also one of the few pieces of data about the content that is * exposed and may be needed for disambiguation by the application. */ - final Uri uri; + private final Uri uri; /** * The mime type of the content. */ - final String mimeType; + private final String mimeType; /** * The size of the file in bytes. */ - final long size; + private final long size; /** * The MD5 hash of the file. */ - final byte md5Hash[]; - - /** - * Gets the parent service for this file. - */ - public FileServiceInfo getFileServiceInfo() { - return null; - } + private final byte md5Hash[]; public static final Parcelable.Creator<FileInfo> CREATOR = new Parcelable.Creator<FileInfo>() { @@ -68,6 +61,13 @@ public class FileInfo implements Parcelable { } }; + public FileInfo(Uri uri, String mimeType, long size, byte[] md5Hash) { + this.uri = uri; + this.mimeType = mimeType; + this.size = size; + this.md5Hash = md5Hash; + } + private FileInfo(Parcel in) { uri = in.readParcelable(null); mimeType = in.readString(); @@ -90,4 +90,20 @@ public class FileInfo implements Parcelable { public int describeContents() { return 0; } + + public Uri getUri() { + return uri; + } + + public String getMimeType() { + return mimeType; + } + + public long getSize() { + return size; + } + + public byte[] getMd5Hash() { + return md5Hash; + } } diff --git a/telephony/java/android/telephony/mbms/FileServiceInfo.java b/telephony/java/android/telephony/mbms/FileServiceInfo.java index 8e890fd580e3..6646dc8a56df 100644 --- a/telephony/java/android/telephony/mbms/FileServiceInfo.java +++ b/telephony/java/android/telephony/mbms/FileServiceInfo.java @@ -30,13 +30,13 @@ import java.util.Map; * @hide */ public class FileServiceInfo extends ServiceInfo implements Parcelable { - public List<FileInfo> files; + private final List<FileInfo> files; public FileServiceInfo(Map<Locale, String> newNames, String newClassName, List<Locale> newLocales, String newServiceId, Date start, Date end, List<FileInfo> newFiles) { super(newNames, newClassName, newLocales, newServiceId, start, end); - files = new ArrayList(newFiles); + files = new ArrayList<>(newFiles); } public static final Parcelable.Creator<FileServiceInfo> CREATOR = @@ -68,4 +68,9 @@ public class FileServiceInfo extends ServiceInfo implements Parcelable { public int describeContents() { return 0; } + + public List<FileInfo> getFiles() { + return files; + } + } diff --git a/telephony/java/android/telephony/mbms/IMbmsDownloadManagerCallback.aidl b/telephony/java/android/telephony/mbms/IMbmsDownloadManagerCallback.aidl index 03227d0d9f5a..ac2f20243896 100755 --- a/telephony/java/android/telephony/mbms/IMbmsDownloadManagerCallback.aidl +++ b/telephony/java/android/telephony/mbms/IMbmsDownloadManagerCallback.aidl @@ -24,19 +24,11 @@ import java.util.List; * The interface the clients top-level file download listener will satisfy. * @hide */ -interface IMbmsDownloadManagerCallback +oneway interface IMbmsDownloadManagerCallback { void error(int errorCode, String message); - /** - * Called to indicate published File Services have changed. - * - * This will only be called after the application has requested - * a list of file services and specified a service class list - * of interest AND the results of a subsequent getFileServices - * call with the same service class list would - * return different - * results. - */ void fileServicesUpdated(in List<FileServiceInfo> services); + + void middlewareReady(); } diff --git a/telephony/java/android/telephony/mbms/IMbmsStreamingManagerCallback.aidl b/telephony/java/android/telephony/mbms/IMbmsStreamingManagerCallback.aidl index cbf0fca461f0..8116a7f0b7c4 100755 --- a/telephony/java/android/telephony/mbms/IMbmsStreamingManagerCallback.aidl +++ b/telephony/java/android/telephony/mbms/IMbmsStreamingManagerCallback.aidl @@ -24,30 +24,13 @@ import java.util.List; * The interface the clients top-level streaming listener will satisfy. * @hide */ -interface IMbmsStreamingManagerCallback +oneway interface IMbmsStreamingManagerCallback { void error(int errorCode, String message); - /** - * Called to indicate published Streaming Services have changed. - * - * This will only be called after the application has requested - * a list of streaming services and specified a service class list - * of interest AND the results of a subsequent getStreamServices - * call with the same service class list would - * return different - * results. - */ void streamingServicesUpdated(in List<StreamingServiceInfo> services); - /** - * Called to indicate the active Streaming Services have changed. - * - * This will be caused whenever a new service starts streaming or whenever - * MbmsStreamServiceManager.getActiveStreamingServices is called. - * - * @param services a list of StreamingServiceInfos. May be empty if - * there are no active StreamingServices - */ void activeStreamingServicesUpdated(in List<StreamingServiceInfo> services); + + void middlewareReady(); } diff --git a/telephony/java/android/telephony/mbms/MbmsDownloadManagerCallback.java b/telephony/java/android/telephony/mbms/MbmsDownloadManagerCallback.java index 16fafe415b0f..5b22199bea1c 100644 --- a/telephony/java/android/telephony/mbms/MbmsDownloadManagerCallback.java +++ b/telephony/java/android/telephony/mbms/MbmsDownloadManagerCallback.java @@ -48,4 +48,17 @@ public class MbmsDownloadManagerCallback extends IMbmsDownloadManagerCallback.St public void fileServicesUpdated(List<FileServiceInfo> services) { // default implementation empty } + + /** + * Called to indicate that the middleware has been initialized and is ready. + * + * Before this method is called, calling any method on an instance of + * {@link android.telephony.MbmsDownloadManager} will result in an {@link MbmsException} + * being thrown with error code {@link MbmsException#ERROR_MIDDLEWARE_NOT_BOUND} + * or {@link MbmsException#ERROR_MIDDLEWARE_NOT_YET_READY} + */ + @Override + public void middlewareReady() { + // default implementation empty + } } diff --git a/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java b/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java index c01ddaedbd88..b51c367deb36 100644 --- a/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java +++ b/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java @@ -31,8 +31,8 @@ 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.Objects; import java.util.UUID; /** @@ -54,6 +54,11 @@ public class MbmsDownloadReceiver extends BroadcastReceiver { setResultCode(1 /* TODO: define error constants */); return; } + if (!Objects.equals(intent.getStringExtra(MbmsDownloadManager.EXTRA_TEMP_FILE_ROOT), + MbmsTempFileProvider.getEmbmsTempFileDir(context).getPath())) { + setResultCode(1 /* TODO: define error constants */); + return; + } if (MbmsDownloadManager.ACTION_DOWNLOAD_RESULT_INTERNAL.equals(intent.getAction())) { moveDownloadedFile(context, intent); @@ -74,7 +79,11 @@ public class MbmsDownloadReceiver extends BroadcastReceiver { Log.w(LOG_TAG, "Download result did not include the associated request. Ignoring."); return false; } - if (!intent.hasExtra(MbmsDownloadManager.EXTRA_INFO)) { + if (!intent.hasExtra(MbmsDownloadManager.EXTRA_TEMP_FILE_ROOT)) { + Log.w(LOG_TAG, "Download result did not include the temp file root. Ignoring."); + return false; + } + if (!intent.hasExtra(MbmsDownloadManager.EXTRA_FILE_INFO)) { Log.w(LOG_TAG, "Download result did not include the associated file info. " + "Ignoring."); return false; @@ -90,6 +99,10 @@ public class MbmsDownloadReceiver extends BroadcastReceiver { Log.w(LOG_TAG, "Temp file request not include the associated request. Ignoring."); return false; } + if (!intent.hasExtra(MbmsDownloadManager.EXTRA_TEMP_FILE_ROOT)) { + Log.w(LOG_TAG, "Download result did not include the temp file root. Ignoring."); + return false; + } return true; } @@ -121,12 +134,15 @@ public class MbmsDownloadReceiver extends BroadcastReceiver { } String relativePath = calculateDestinationFileRelativePath(request, - (FileInfo) intent.getParcelableExtra(MbmsDownloadManager.EXTRA_INFO)); + (FileInfo) intent.getParcelableExtra(MbmsDownloadManager.EXTRA_FILE_INFO)); - if (!moveTempFile(finalTempFile, destinationUri, relativePath)) { + Uri finalFileLocation = moveTempFile(finalTempFile, destinationUri, relativePath); + if (finalFileLocation == null) { Log.w(LOG_TAG, "Failed to move temp file to final destination"); + // TODO: how do we notify the app of this? setResultCode(1); } + intentForApp.putExtra(MbmsDownloadManager.EXTRA_COMPLETED_FILE_URI, finalFileLocation); context.sendBroadcast(intentForApp); setResultCode(0); @@ -226,7 +242,6 @@ public class MbmsDownloadReceiver extends BroadcastReceiver { return null; } - private ArrayList<UriPathPair> generateUrisForPausedFiles(Context context, DownloadRequest request, List<Uri> pausedFiles) { if (pausedFiles == null) { @@ -258,13 +273,15 @@ public class MbmsDownloadReceiver extends BroadcastReceiver { 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> filePathComponents = info.getUri().getPathSegments(); List<String> requestPathComponents = request.getSourceUri().getPathSegments(); Iterator<String> filePathIter = filePathComponents.iterator(); Iterator<String> requestPathIter = requestPathComponents.iterator(); - LinkedList<String> relativePathComponents = new LinkedList<>(); + StringBuilder pathBuilder = new StringBuilder(); + // Iterate through the segments of the carrier's URI to the file, along with the segments + // of the source URI specified in the download request. The relative path is calculated + // as the tail of the file's URI that does not match the path segments in the source URI. while (filePathIter.hasNext()) { String currFilePathComponent = filePathIter.next(); if (requestPathIter.hasNext()) { @@ -273,28 +290,44 @@ public class MbmsDownloadReceiver extends BroadcastReceiver { continue; } } - relativePathComponents.add(currFilePathComponent); + pathBuilder.append(currFilePathComponent); + pathBuilder.append('/'); } - return String.join("/", relativePathComponents); + // remove the trailing slash + if (pathBuilder.length() > 0) { + pathBuilder.deleteCharAt(pathBuilder.length() - 1); + } + return pathBuilder.toString(); } - private static boolean moveTempFile(Uri fromPath, Uri toPath, String relativePath) { + /* + * Moves a tempfile located at fromPath to a new location at toPath. If + * toPath is a directory, the destination file will be located at relativePath + * underneath toPath. + */ + private static Uri 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; + return null; } if (!ContentResolver.SCHEME_FILE.equals(toPath.getScheme())) { Log.w(LOG_TAG, "Moving destination uri " + toPath + " does not have a file scheme"); - return false; + return null; } File fromFile = new File(fromPath.getSchemeSpecificPart()); - File toFile = new File(toPath.getSchemeSpecificPart(), relativePath); + File toFile = new File(toPath.getSchemeSpecificPart()); + if (toFile.isDirectory()) { + toFile = new File(toFile, 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); + // TODO: This will not work if the two files are on different filesystems. Add manual + // copy later. + if (fromFile.renameTo(toFile)) { + return Uri.fromFile(toFile); + } + return null; } private static boolean verifyTempFilePath(Context context, DownloadRequest request, @@ -323,8 +356,7 @@ public class MbmsDownloadReceiver extends BroadcastReceiver { * 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)); + File embmsTempFileDir = MbmsTempFileProvider.getEmbmsTempFileDir(context); // TODO: better naming scheme for temp file dirs String tempFileDirName = String.valueOf(request.getFileServiceInfo().getServiceId()); diff --git a/telephony/java/android/telephony/mbms/MbmsException.java b/telephony/java/android/telephony/mbms/MbmsException.java index 6b905921dba0..8260b728a52f 100644 --- a/telephony/java/android/telephony/mbms/MbmsException.java +++ b/telephony/java/android/telephony/mbms/MbmsException.java @@ -22,7 +22,7 @@ public class MbmsException extends Exception { public static final int ERROR_NO_SERVICE_INSTALLED = 1; public static final int ERROR_MULTIPLE_SERVICES_INSTALLED = 2; public static final int ERROR_BIND_TIMEOUT_OR_FAILURE = 3; - public static final int ERROR_UNABLE_TO_INITIALIZE = 4; + public static final int ERROR_MIDDLEWARE_NOT_YET_READY = 4; public static final int ERROR_ALREADY_INITIALIZED = 5; public static final int ERROR_CONCURRENT_SERVICE_LIMIT_REACHED = 6; public static final int ERROR_MIDDLEWARE_NOT_BOUND = 7; @@ -36,6 +36,7 @@ public class MbmsException extends Exception { public static final int ERROR_NOT_CONNECTED_TO_HOME_CARRIER_LTE = 15; public static final int ERROR_UNABLE_TO_READ_SIM = 16; public static final int ERROR_CARRIER_CHANGE_NOT_ALLOWED = 17; + public static final int ERROR_CANNOT_CHANGE_TEMP_FILE_ROOT = 18; private final int mErrorCode; diff --git a/telephony/java/android/telephony/mbms/MbmsStreamingManagerCallback.java b/telephony/java/android/telephony/mbms/MbmsStreamingManagerCallback.java index b3bc8146275e..27d9878a1966 100644 --- a/telephony/java/android/telephony/mbms/MbmsStreamingManagerCallback.java +++ b/telephony/java/android/telephony/mbms/MbmsStreamingManagerCallback.java @@ -61,4 +61,17 @@ public class MbmsStreamingManagerCallback extends IMbmsStreamingManagerCallback. public void activeStreamingServicesUpdated(List<StreamingServiceInfo> services) { // default implementation empty } + + /** + * Called to indicate that the middleware has been initialized and is ready. + * + * Before this method is called, calling any method on an instance of + * {@link android.telephony.MbmsStreamingManager} will result in an {@link MbmsException} + * being thrown with error code {@link MbmsException#ERROR_MIDDLEWARE_NOT_BOUND} + * or {@link MbmsException#ERROR_MIDDLEWARE_NOT_YET_READY} + */ + @Override + public void middlewareReady() { + // default implementation empty + } } diff --git a/telephony/java/android/telephony/mbms/MbmsTempFileProvider.java b/telephony/java/android/telephony/mbms/MbmsTempFileProvider.java index 9842581cdc02..c4d033bf2886 100644 --- a/telephony/java/android/telephony/mbms/MbmsTempFileProvider.java +++ b/telephony/java/android/telephony/mbms/MbmsTempFileProvider.java @@ -22,6 +22,7 @@ import android.content.ContentProvider; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; +import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.content.pm.ProviderInfo; import android.database.Cursor; @@ -32,14 +33,15 @@ import android.os.ParcelFileDescriptor; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; +import java.util.Objects; /** * @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"; + public static final String TEMP_FILE_ROOT_PREF_FILE_NAME = "MbmsTempFileRootPrefs"; + public static final String TEMP_FILE_ROOT_PREF_NAME = "mbms_temp_file_root"; private String mAuthority; private Context mContext; @@ -114,7 +116,7 @@ public class MbmsTempFileProvider extends ContentProvider { // Make sure the temp file is contained in the temp file directory as configured in the // manifest - File tempFileDir = getEmbmsTempFileDir(context, authority); + File tempFileDir = getEmbmsTempFileDir(context); if (!MbmsUtils.isContainedIn(tempFileDir, file)) { throw new IllegalArgumentException("File " + file + " is not contained in the temp " + "file directory, which is " + tempFileDir); @@ -147,13 +149,17 @@ public class MbmsTempFileProvider extends ContentProvider { if (!ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) { throw new IllegalArgumentException("Uri must have scheme content"); } + if (!Objects.equals(authority, uri.getAuthority())) { + throw new IllegalArgumentException("Uri does not have a matching authority: " + + authority + ", " + uri.getAuthority()); + } String relPath = Uri.decode(uri.getEncodedPath()); File file; File tempFileDir; try { - tempFileDir = getEmbmsTempFileDir(context, authority).getCanonicalFile(); + tempFileDir = getEmbmsTempFileDir(context).getCanonicalFile(); file = new File(tempFileDir, relPath).getCanonicalFile(); } catch (IOException e) { throw new FileNotFoundException("Could not resolve paths"); @@ -169,25 +175,18 @@ public class MbmsTempFileProvider extends ContentProvider { /** * 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; + public static File getEmbmsTempFileDir(Context context) { + SharedPreferences prefs = context.getSharedPreferences(TEMP_FILE_ROOT_PREF_FILE_NAME, 0); + String storedTempFileRoot = prefs.getString(TEMP_FILE_ROOT_PREF_NAME, null); + try { + if (storedTempFileRoot != null) { + return new File(storedTempFileRoot).getCanonicalFile(); + } else { + return new File(context.getFilesDir(), DEFAULT_TOP_LEVEL_TEMP_DIRECTORY) + .getCanonicalFile(); + } + } catch (IOException e) { + throw new RuntimeException("Unable to canonicalize temp file root path " + e); } - 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 index de308053df56..7d4727563eee 100644 --- a/telephony/java/android/telephony/mbms/MbmsUtils.java +++ b/telephony/java/android/telephony/mbms/MbmsUtils.java @@ -19,6 +19,7 @@ package android.telephony.mbms; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.ServiceConnection; import android.content.pm.*; import android.content.pm.ServiceInfo; import android.telephony.MbmsDownloadManager; @@ -46,20 +47,6 @@ public class MbmsUtils { } } - 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); } @@ -83,4 +70,19 @@ public class MbmsUtils { } return downloadServices.get(0).serviceInfo; } + + public static void startBinding(Context context, String serviceAction, + ServiceConnection serviceConnection) throws MbmsException { + Intent bindIntent = new Intent(); + ServiceInfo mbmsServiceInfo = + MbmsUtils.getMiddlewareServiceInfo(context, serviceAction); + + if (mbmsServiceInfo == null) { + throw new MbmsException(MbmsException.ERROR_NO_SERVICE_INSTALLED); + } + + bindIntent.setComponent(MbmsUtils.toComponentName(mbmsServiceInfo)); + + context.bindService(bindIntent, serviceConnection, Context.BIND_AUTO_CREATE); + } } diff --git a/telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl b/telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl index 6c2b8167d519..ff7d233bbf2c 100755 --- a/telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl +++ b/telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl @@ -47,6 +47,7 @@ interface IMbmsDownloadService */ int getFileServices(String appName, int subId, in List<String> serviceClasses); + int setTempFileRootDirectory(String appName, int subId, String rootDirectoryPath); /** * should move the params into a DownloadRequest parcelable */ diff --git a/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java b/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java index 505aeae15355..9577dd2e3d78 100644 --- a/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java +++ b/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java @@ -32,13 +32,19 @@ import java.util.List; */ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub { @Override - public void initialize(String appName, int subId, IMbmsDownloadManagerCallback listener) + public void initialize(String appName, int subscriptionId, + IMbmsDownloadManagerCallback listener) throws RemoteException { + } + + @Override + public int getFileServices(String appName, int subscriptionId, List<String> serviceClasses) throws RemoteException { + return 0; } @Override - public int getFileServices(String appName, int subId, List<String> serviceClasses) throws - RemoteException { + public int setTempFileRootDirectory(String appName, int subscriptionId, + String rootDirectoryPath) throws RemoteException { return 0; } 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 14284d661744..1ddaf66d2274 100644 --- a/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java +++ b/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java @@ -62,7 +62,8 @@ public class OffloadControllerTest { @Mock private OffloadHardwareInterface mHardware; @Mock private ApplicationInfo mApplicationInfo; @Mock private Context mContext; - final ArgumentCaptor<ArrayList> mStringArrayCaptor = ArgumentCaptor.forClass(ArrayList.class); + private final ArgumentCaptor<ArrayList> mStringArrayCaptor = + ArgumentCaptor.forClass(ArrayList.class); private MockContentResolver mContentResolver; @Before public void setUp() throws Exception { 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 ce419a5070d2..db5373ac34b2 100644 --- a/tests/net/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java +++ b/tests/net/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java @@ -16,6 +16,8 @@ package com.android.server.connectivity.tethering; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; @@ -40,17 +42,23 @@ import static com.android.server.connectivity.tethering.IControlsTethering.STATE import android.net.ConnectivityManager; import android.net.INetworkStatsService; import android.net.InterfaceConfiguration; +import android.net.LinkAddress; import android.net.LinkProperties; +import android.net.RouteInfo; import android.net.util.SharedLog; import android.os.INetworkManagementService; import android.os.RemoteException; import android.os.test.TestLooper; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; +import android.text.TextUtils; + +import java.net.Inet4Address; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -69,6 +77,8 @@ public class TetherInterfaceStateMachineTest { @Mock private SharedLog mSharedLog; private final TestLooper mLooper = new TestLooper(); + private final ArgumentCaptor<LinkProperties> mLinkPropertiesCaptor = + ArgumentCaptor.forClass(LinkProperties.class); private TetherInterfaceStateMachine mTestedSm; private void initStateMachine(int interfaceType) throws Exception { @@ -77,7 +87,7 @@ public class TetherInterfaceStateMachineTest { mNMService, mStatsService, mTetherHelper); mTestedSm.start(); // Starting the state machine always puts us in a consistent state and notifies - // the test of the world that we've changed from an unknown to available state. + // the rest of the world that we've changed from an unknown to available state. mLooper.dispatchAll(); reset(mNMService, mStatsService, mTetherHelper); when(mNMService.getInterfaceConfig(IFACE_NAME)).thenReturn(mInterfaceConfiguration); @@ -181,7 +191,8 @@ public class TetherInterfaceStateMachineTest { inOrder.verify(mTetherHelper).updateInterfaceState( mTestedSm, STATE_TETHERED, TETHER_ERROR_NO_ERROR); inOrder.verify(mTetherHelper).updateLinkProperties( - eq(mTestedSm), any(LinkProperties.class)); + eq(mTestedSm), mLinkPropertiesCaptor.capture()); + assertIPv4AddressAndDirectlyConnectedRoute(mLinkPropertiesCaptor.getValue()); verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper); } @@ -281,7 +292,8 @@ public class TetherInterfaceStateMachineTest { usbTeardownOrder.verify(mTetherHelper).updateInterfaceState( mTestedSm, STATE_UNAVAILABLE, TETHER_ERROR_NO_ERROR); usbTeardownOrder.verify(mTetherHelper).updateLinkProperties( - eq(mTestedSm), any(LinkProperties.class)); + eq(mTestedSm), mLinkPropertiesCaptor.capture()); + assertNoAddressesNorRoutes(mLinkPropertiesCaptor.getValue()); } } @@ -298,7 +310,8 @@ public class TetherInterfaceStateMachineTest { usbTeardownOrder.verify(mTetherHelper).updateInterfaceState( mTestedSm, STATE_AVAILABLE, TETHER_ERROR_TETHER_IFACE_ERROR); usbTeardownOrder.verify(mTetherHelper).updateLinkProperties( - eq(mTestedSm), any(LinkProperties.class)); + eq(mTestedSm), mLinkPropertiesCaptor.capture()); + assertNoAddressesNorRoutes(mLinkPropertiesCaptor.getValue()); } @Test @@ -313,7 +326,8 @@ public class TetherInterfaceStateMachineTest { usbTeardownOrder.verify(mTetherHelper).updateInterfaceState( mTestedSm, STATE_AVAILABLE, TETHER_ERROR_ENABLE_NAT_ERROR); usbTeardownOrder.verify(mTetherHelper).updateLinkProperties( - eq(mTestedSm), any(LinkProperties.class)); + eq(mTestedSm), mLinkPropertiesCaptor.capture()); + assertNoAddressesNorRoutes(mLinkPropertiesCaptor.getValue()); } @Test @@ -360,4 +374,28 @@ public class TetherInterfaceStateMachineTest { upstreamIface); mLooper.dispatchAll(); } + + private void assertIPv4AddressAndDirectlyConnectedRoute(LinkProperties lp) { + // Find the first IPv4 LinkAddress. + LinkAddress addr4 = null; + for (LinkAddress addr : lp.getLinkAddresses()) { + if (!(addr.getAddress() instanceof Inet4Address)) continue; + addr4 = addr; + break; + } + assertTrue("missing IPv4 address", addr4 != null); + + // Assert the presence of the associated directly connected route. + final RouteInfo directlyConnected = new RouteInfo(addr4, null, lp.getInterfaceName()); + assertTrue("missing directly connected route: '" + directlyConnected.toString() + "'", + lp.getRoutes().contains(directlyConnected)); + } + + private void assertNoAddressesNorRoutes(LinkProperties lp) { + assertTrue(lp.getLinkAddresses().isEmpty()); + assertTrue(lp.getRoutes().isEmpty()); + // We also check that interface name is non-empty, because we should + // never see an empty interface name in any LinkProperties update. + assertFalse(TextUtils.isEmpty(lp.getInterfaceName())); + } } |