Home | History | Annotate | Download | only in webkit
      1 /*
      2  * Copyright (C) 2006 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 android.webkit;
     18 
     19 import android.content.BroadcastReceiver;
     20 import android.content.Context;
     21 import android.content.Intent;
     22 import android.content.IntentFilter;
     23 import android.net.ConnectivityManager;
     24 import android.net.NetworkInfo;
     25 import android.net.http.*;
     26 import android.os.*;
     27 import android.util.Log;
     28 
     29 import java.io.ByteArrayInputStream;
     30 import java.io.InputStream;
     31 import java.util.Map;
     32 
     33 import junit.framework.Assert;
     34 
     35 class Network {
     36 
     37     private static final String LOGTAG = "network";
     38 
     39     /**
     40      * Static instance of a Network object.
     41      */
     42     private static Network sNetwork;
     43 
     44     /**
     45      * Flag to store the state of platform notifications, for the case
     46      * when the Network object has not been constructed yet
     47      */
     48     private static boolean sPlatformNotifications;
     49 
     50     /**
     51      * Reference count for platform notifications as the network class is a
     52      * static and can exist over multiple activities, thus over multiple
     53      * onPause/onResume pairs.
     54      */
     55     private static int sPlatformNotificationEnableRefCount;
     56 
     57     /**
     58      * Proxy username if known (used for pre-emptive proxy authentication).
     59      */
     60     private String mProxyUsername;
     61 
     62     /**
     63      * Proxy password if known (used for pre-emptive proxy authentication).
     64      */
     65     private String mProxyPassword;
     66 
     67     /**
     68      * Network request queue (requests are added from the browser thread).
     69      */
     70     private RequestQueue mRequestQueue;
     71 
     72     /**
     73      * SSL error handler: takes care of synchronization of multiple async
     74      * loaders with SSL-related problems.
     75      */
     76     private SslErrorHandlerImpl mSslErrorHandler;
     77 
     78     /**
     79      * HTTP authentication handler: takes care of synchronization of HTTP
     80      * authentication requests.
     81      */
     82     private HttpAuthHandlerImpl mHttpAuthHandler;
     83 
     84     private Context mContext;
     85 
     86     /**
     87      * True if the currently used network connection is a roaming phone
     88      * connection.
     89      */
     90     private boolean mRoaming;
     91 
     92     /**
     93      * Tracks if we are roaming.
     94      */
     95     private RoamingMonitor mRoamingMonitor;
     96 
     97     /**
     98      * @return The singleton instance of the network.
     99      */
    100     public static synchronized Network getInstance(Context context) {
    101         if (sNetwork == null) {
    102             // Note Context of the Application is used here, rather than
    103             // the what is passed in (usually a Context derived from an
    104             // Activity) so the intent receivers belong to the application
    105             // rather than an activity - this fixes the issue where
    106             // Activities are created and destroyed during the lifetime of
    107             // an Application
    108             sNetwork = new Network(context.getApplicationContext());
    109             if (sPlatformNotifications) {
    110                 // Adjust the ref count before calling enable as it is already
    111                 // taken into account when the static function was called
    112                 // directly
    113                 --sPlatformNotificationEnableRefCount;
    114                 enablePlatformNotifications();
    115             }
    116         }
    117         return sNetwork;
    118     }
    119 
    120 
    121     /**
    122      * Enables data state and proxy tracking
    123      */
    124     public static void enablePlatformNotifications() {
    125         if (++sPlatformNotificationEnableRefCount == 1) {
    126             if (sNetwork != null) {
    127                 sNetwork.mRequestQueue.enablePlatformNotifications();
    128                 sNetwork.monitorRoaming();
    129             } else {
    130                 sPlatformNotifications = true;
    131             }
    132         }
    133     }
    134 
    135     /**
    136      * If platform notifications are enabled, this should be called
    137      * from onPause() or onStop()
    138      */
    139     public static void disablePlatformNotifications() {
    140         if (--sPlatformNotificationEnableRefCount == 0) {
    141             if (sNetwork != null) {
    142                 sNetwork.mRequestQueue.disablePlatformNotifications();
    143                 sNetwork.stopMonitoringRoaming();
    144             } else {
    145                 sPlatformNotifications = false;
    146             }
    147         }
    148     }
    149 
    150     /**
    151      * Creates a new Network object.
    152      * XXX: Must be created in the same thread as WebCore!!!!!
    153      */
    154     private Network(Context context) {
    155         if (DebugFlags.NETWORK) {
    156             Assert.assertTrue(Thread.currentThread().
    157                     getName().equals(WebViewCore.THREAD_NAME));
    158         }
    159         mContext = context;
    160         mSslErrorHandler = new SslErrorHandlerImpl();
    161         mHttpAuthHandler = new HttpAuthHandlerImpl(this);
    162 
    163         mRequestQueue = new RequestQueue(context);
    164     }
    165 
    166     private class RoamingMonitor extends BroadcastReceiver {
    167         @Override
    168         public void onReceive(Context context, Intent intent) {
    169             if (!ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction()))
    170                 return;
    171 
    172             NetworkInfo info = (NetworkInfo)intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO);
    173             if (info != null)
    174                 mRoaming = info.isRoaming();
    175         };
    176     };
    177 
    178     private void monitorRoaming() {
    179         mRoamingMonitor = new RoamingMonitor();
    180         IntentFilter filter = new IntentFilter();
    181         filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
    182         mContext.registerReceiver(sNetwork.mRoamingMonitor, filter);
    183     }
    184 
    185     private void stopMonitoringRoaming() {
    186         if (mRoamingMonitor != null) {
    187             mContext.unregisterReceiver(mRoamingMonitor);
    188             mRoamingMonitor = null;
    189         }
    190     }
    191 
    192     /**
    193      * Request a url from either the network or the file system.
    194      * @param url The url to load.
    195      * @param method The http method.
    196      * @param headers The http headers.
    197      * @param postData The body of the request.
    198      * @param loader A LoadListener for receiving the results of the request.
    199      * @return True if the request was successfully queued.
    200      */
    201     public boolean requestURL(String method,
    202                               Map<String, String> headers,
    203                               byte [] postData,
    204                               LoadListener loader) {
    205 
    206         String url = loader.url();
    207 
    208         // Not a valid url, return false because we won't service the request!
    209         if (!URLUtil.isValidUrl(url)) {
    210             return false;
    211         }
    212 
    213         // asset, res, file system or data stream are handled in the other code
    214         // path. This only handles network request.
    215         if (URLUtil.isAssetUrl(url) || URLUtil.isResourceUrl(url)
    216                 || URLUtil.isFileUrl(url) || URLUtil.isDataUrl(url)) {
    217             return false;
    218         }
    219 
    220         // If this is a prefetch, abort it if we're roaming.
    221         if (mRoaming && headers.containsKey("X-Moz") && "prefetch".equals(headers.get("X-Moz"))) {
    222             return false;
    223         }
    224 
    225         /* FIXME: this is lame.  Pass an InputStream in, rather than
    226            making this lame one here */
    227         InputStream bodyProvider = null;
    228         int bodyLength = 0;
    229         if (postData != null) {
    230             bodyLength = postData.length;
    231             bodyProvider = new ByteArrayInputStream(postData);
    232         }
    233 
    234         RequestQueue q = mRequestQueue;
    235         RequestHandle handle = null;
    236         if (loader.isSynchronous()) {
    237             handle = q.queueSynchronousRequest(url, loader.getWebAddress(),
    238                     method, headers, loader, bodyProvider, bodyLength);
    239             loader.attachRequestHandle(handle);
    240             handle.processRequest();
    241             loader.loadSynchronousMessages();
    242         } else {
    243             handle = q.queueRequest(url, loader.getWebAddress(), method,
    244                     headers, loader, bodyProvider, bodyLength);
    245             // FIXME: Although this is probably a rare condition, normal network
    246             // requests are processed in a separate thread. This means that it
    247             // is possible to process part of the request before setting the
    248             // request handle on the loader. We should probably refactor this to
    249             // ensure the handle is attached before processing begins.
    250             loader.attachRequestHandle(handle);
    251         }
    252 
    253         return true;
    254     }
    255 
    256     /**
    257      * @return True iff there is a valid proxy set.
    258      */
    259     public boolean isValidProxySet() {
    260         // The proxy host and port can be set within a different thread during
    261         // an Intent broadcast.
    262         synchronized (mRequestQueue) {
    263             return mRequestQueue.getProxyHost() != null;
    264         }
    265     }
    266 
    267     /**
    268      * Get the proxy hostname.
    269      * @return The proxy hostname obtained from the network queue and proxy
    270      *         settings.
    271      */
    272     public String getProxyHostname() {
    273         return mRequestQueue.getProxyHost().getHostName();
    274     }
    275 
    276     /**
    277      * @return The proxy username or null if none.
    278      */
    279     public synchronized String getProxyUsername() {
    280         return mProxyUsername;
    281     }
    282 
    283     /**
    284      * Sets the proxy username.
    285      * @param proxyUsername Username to use when
    286      * connecting through the proxy.
    287      */
    288     public synchronized void setProxyUsername(String proxyUsername) {
    289         if (DebugFlags.NETWORK) {
    290             Assert.assertTrue(isValidProxySet());
    291         }
    292 
    293         mProxyUsername = proxyUsername;
    294     }
    295 
    296     /**
    297      * @return The proxy password or null if none.
    298      */
    299     public synchronized String getProxyPassword() {
    300         return mProxyPassword;
    301     }
    302 
    303     /**
    304      * Sets the proxy password.
    305      * @param proxyPassword Password to use when
    306      * connecting through the proxy.
    307      */
    308     public synchronized void setProxyPassword(String proxyPassword) {
    309         if (DebugFlags.NETWORK) {
    310             Assert.assertTrue(isValidProxySet());
    311         }
    312 
    313         mProxyPassword = proxyPassword;
    314     }
    315 
    316     /**
    317      * Saves the state of network handlers (user SSL and HTTP-authentication
    318      * preferences).
    319      * @param outState The out-state to save (write) to.
    320      * @return True iff succeeds.
    321      */
    322     public boolean saveState(Bundle outState) {
    323         if (DebugFlags.NETWORK) {
    324             Log.v(LOGTAG, "Network.saveState()");
    325         }
    326 
    327         return mSslErrorHandler.saveState(outState);
    328     }
    329 
    330     /**
    331      * Restores the state of network handlers (user SSL and HTTP-authentication
    332      * preferences).
    333      * @param inState The in-state to load (read) from.
    334      * @return True iff succeeds.
    335      */
    336     public boolean restoreState(Bundle inState) {
    337         if (DebugFlags.NETWORK) {
    338             Log.v(LOGTAG, "Network.restoreState()");
    339         }
    340 
    341         return mSslErrorHandler.restoreState(inState);
    342     }
    343 
    344     /**
    345      * Clears user SSL-error preference table.
    346      */
    347     public void clearUserSslPrefTable() {
    348         mSslErrorHandler.clear();
    349     }
    350 
    351     /**
    352      * Handles SSL error(s) on the way up to the user: the user must decide
    353      * whether errors should be ignored or not.
    354      * @param loader The loader that resulted in SSL errors.
    355      */
    356     public void handleSslErrorRequest(LoadListener loader) {
    357         if (DebugFlags.NETWORK) Assert.assertNotNull(loader);
    358         if (loader != null) {
    359             mSslErrorHandler.handleSslErrorRequest(loader);
    360         }
    361     }
    362 
    363     /* package */ boolean checkSslPrefTable(LoadListener loader,
    364             SslError error) {
    365         if (loader != null && error != null) {
    366             return mSslErrorHandler.checkSslPrefTable(loader, error);
    367         }
    368         return false;
    369     }
    370 
    371      /**
    372      * Handles authentication requests on their way up to the user (the user
    373      * must provide credentials).
    374      * @param loader The loader that resulted in an HTTP
    375      * authentication request.
    376      */
    377     public void handleAuthRequest(LoadListener loader) {
    378         if (DebugFlags.NETWORK) Assert.assertNotNull(loader);
    379         if (loader != null) {
    380             mHttpAuthHandler.handleAuthRequest(loader);
    381         }
    382     }
    383 
    384     // Performance probe
    385     public void startTiming() {
    386         mRequestQueue.startTiming();
    387     }
    388 
    389     public void stopTiming() {
    390         mRequestQueue.stopTiming();
    391     }
    392 }
    393