Home | History | Annotate | Download | only in net
      1 /*
      2  * Copyright (C) 2016 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.server.net;
     18 
     19 import android.content.Context;
     20 import android.net.ConnectivityManager;
     21 import android.net.ConnectivityManager.NetworkCallback;
     22 import android.net.Network;
     23 import android.net.NetworkRequest;
     24 import android.util.Log;
     25 
     26 import com.android.internal.annotations.GuardedBy;
     27 import com.android.internal.annotations.VisibleForTesting;
     28 
     29 /**
     30  * A class that pins a process to the first network that satisfies a particular NetworkRequest.
     31  *
     32  * We use this to maintain compatibility with pre-M apps that call WifiManager.enableNetwork()
     33  * to connect to a Wi-Fi network that has no Internet access, and then assume that they will be
     34  * able to use that network because it's the system default.
     35  *
     36  * In order to maintain compatibility with apps that call setProcessDefaultNetwork themselves,
     37  * we try not to set the default network unless they have already done so, and we try not to
     38  * clear the default network unless we set it ourselves.
     39  *
     40  * This should maintain behaviour that's compatible with L, which would pin the whole system to
     41  * any wifi network that was created via enableNetwork(..., true) until that network
     42  * disconnected.
     43  *
     44  * Note that while this hack allows network traffic to flow, it is quite limited. For example:
     45  *
     46  * 1. setProcessDefaultNetwork only affects this process, so:
     47  *    - Any subprocesses spawned by this process will not be pinned to Wi-Fi.
     48  *    - If this app relies on any other apps on the device also being on Wi-Fi, that won't work
     49  *      either, because other apps on the device will not be pinned.
     50  * 2. The behaviour of other APIs is not modified. For example:
     51  *    - getActiveNetworkInfo will return the system default network, not Wi-Fi.
     52  *    - There will be no CONNECTIVITY_ACTION broadcasts about TYPE_WIFI.
     53  *    - getProcessDefaultNetwork will not return null, so if any apps are relying on that, they
     54  *      will be surprised as well.
     55  *
     56  * This class is a per-process singleton because the process default network is a per-process
     57  * singleton.
     58  *
     59  */
     60 public class NetworkPinner extends NetworkCallback {
     61 
     62     private static final String TAG = NetworkPinner.class.getSimpleName();
     63 
     64     @VisibleForTesting
     65     protected static final Object sLock = new Object();
     66 
     67     @GuardedBy("sLock")
     68     private static ConnectivityManager sCM;
     69     @GuardedBy("sLock")
     70     private static Callback sCallback;
     71     @VisibleForTesting
     72     @GuardedBy("sLock")
     73     protected static Network sNetwork;
     74 
     75     private static void maybeInitConnectivityManager(Context context) {
     76         // TODO: what happens if an app calls a WifiManager API before ConnectivityManager is
     77         // registered? Can we fix this by starting ConnectivityService before WifiService?
     78         if (sCM == null) {
     79             // Getting a ConnectivityManager does not leak the calling context, because it stores
     80             // the application context and not the calling context.
     81             sCM = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
     82             if (sCM == null) {
     83                 throw new IllegalStateException("Bad luck, ConnectivityService not started.");
     84             }
     85         }
     86     }
     87 
     88     private static class Callback extends NetworkCallback {
     89         @Override
     90         public void onAvailable(Network network) {
     91             synchronized(sLock) {
     92                 if (this != sCallback) return;
     93 
     94                 if (sCM.getBoundNetworkForProcess() == null && sNetwork == null) {
     95                     sCM.bindProcessToNetwork(network);
     96                     sNetwork = network;
     97                     Log.d(TAG, "Wifi alternate reality enabled on network " + network);
     98                 }
     99                 sLock.notify();
    100             }
    101         }
    102 
    103         @Override
    104         public void onLost(Network network) {
    105             synchronized (sLock) {
    106                 if (this != sCallback) return;
    107 
    108                 if (network.equals(sNetwork) && network.equals(sCM.getBoundNetworkForProcess())) {
    109                     unpin();
    110                     Log.d(TAG, "Wifi alternate reality disabled on network " + network);
    111                 }
    112                 sLock.notify();
    113             }
    114         }
    115     }
    116 
    117     public static void pin(Context context, NetworkRequest request) {
    118         synchronized (sLock) {
    119             if (sCallback == null) {
    120                 maybeInitConnectivityManager(context);
    121                 sCallback = new Callback();
    122                 try {
    123                     sCM.registerNetworkCallback(request, sCallback);
    124                 } catch (SecurityException e) {
    125                     Log.d(TAG, "Failed to register network callback", e);
    126                     sCallback = null;
    127                 }
    128             }
    129         }
    130     }
    131 
    132     public static void unpin() {
    133         synchronized (sLock) {
    134             if (sCallback != null) {
    135                 try {
    136                     sCM.bindProcessToNetwork(null);
    137                     sCM.unregisterNetworkCallback(sCallback);
    138                 } catch (SecurityException e) {
    139                     Log.d(TAG, "Failed to unregister network callback", e);
    140                 }
    141                 sCallback = null;
    142                 sNetwork = null;
    143             }
    144         }
    145     }
    146 }
    147