Home | History | Annotate | Download | only in WebCoreSupport
      1 /*
      2  * Copyright 2010, The Android Open Source Project
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions
      6  * are met:
      7  *  * Redistributions of source code must retain the above copyright
      8  *    notice, this list of conditions and the following disclaimer.
      9  *  * Redistributions in binary form must reproduce the above copyright
     10  *    notice, this list of conditions and the following disclaimer in the
     11  *    documentation and/or other materials provided with the distribution.
     12  *
     13  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
     14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
     17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
     21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     24  */
     25 
     26 #include "config.h"
     27 #include "WebRequest.h"
     28 
     29 #include "JNIUtility.h"
     30 #include "MainThread.h"
     31 #include "UrlInterceptResponse.h"
     32 #include "WebCoreFrameBridge.h"
     33 #include "WebRequestContext.h"
     34 #include "WebResourceRequest.h"
     35 #include "WebUrlLoaderClient.h"
     36 #include "jni.h"
     37 
     38 #include <cutils/log.h>
     39 #include <openssl/x509.h>
     40 #include <string>
     41 #include <utils/AssetManager.h>
     42 
     43 extern android::AssetManager* globalAssetManager();
     44 
     45 // TODO:
     46 // - Finish the file upload. Testcase is mobile buzz
     47 // - Add network throttle needed by Android plugins
     48 
     49 // TODO: Turn off asserts crashing before release
     50 // http://b/issue?id=2951985
     51 #undef ASSERT
     52 #define ASSERT(assertion, ...) do \
     53     if (!(assertion)) { \
     54         android_printLog(ANDROID_LOG_ERROR, __FILE__, __VA_ARGS__); \
     55     } \
     56 while (0)
     57 
     58 namespace android {
     59 
     60 namespace {
     61     const int kInitialReadBufSize = 32768;
     62 }
     63 
     64 WebRequest::WebRequest(WebUrlLoaderClient* loader, const WebResourceRequest& webResourceRequest)
     65     : m_urlLoader(loader)
     66     , m_androidUrl(false)
     67     , m_url(webResourceRequest.url())
     68     , m_userAgent(webResourceRequest.userAgent())
     69     , m_loadState(Created)
     70     , m_authRequestCount(0)
     71     , m_cacheMode(0)
     72     , m_runnableFactory(this)
     73     , m_wantToPause(false)
     74     , m_isPaused(false)
     75     , m_isSync(false)
     76 {
     77     GURL gurl(m_url);
     78 
     79     m_request = new net::URLRequest(gurl, this);
     80 
     81     m_request->SetExtraRequestHeaders(webResourceRequest.requestHeaders());
     82     m_request->set_referrer(webResourceRequest.referrer());
     83     m_request->set_method(webResourceRequest.method());
     84     m_request->set_load_flags(webResourceRequest.loadFlags());
     85 }
     86 
     87 // This is a special URL for Android. Query the Java InputStream
     88 // for data and send to WebCore
     89 WebRequest::WebRequest(WebUrlLoaderClient* loader, const WebResourceRequest& webResourceRequest, UrlInterceptResponse* intercept)
     90     : m_urlLoader(loader)
     91     , m_interceptResponse(intercept)
     92     , m_androidUrl(true)
     93     , m_url(webResourceRequest.url())
     94     , m_userAgent(webResourceRequest.userAgent())
     95     , m_loadState(Created)
     96     , m_authRequestCount(0)
     97     , m_cacheMode(0)
     98     , m_runnableFactory(this)
     99     , m_wantToPause(false)
    100     , m_isPaused(false)
    101     , m_isSync(false)
    102 {
    103 }
    104 
    105 WebRequest::~WebRequest()
    106 {
    107     ASSERT(m_loadState == Finished, "dtor called on a WebRequest in a different state than finished (%d)", m_loadState);
    108 
    109     m_loadState = Deleted;
    110 }
    111 
    112 const std::string& WebRequest::getUrl() const
    113 {
    114     return m_url;
    115 }
    116 
    117 const std::string& WebRequest::getUserAgent() const
    118 {
    119     return m_userAgent;
    120 }
    121 
    122 #ifdef LOG_REQUESTS
    123 namespace {
    124 int remaining = 0;
    125 }
    126 #endif
    127 
    128 void WebRequest::finish(bool success)
    129 {
    130     m_runnableFactory.RevokeAll();
    131     ASSERT(m_loadState < Finished, "(%p) called finish on an already finished WebRequest (%d) (%s)", this, m_loadState, m_url.c_str());
    132     if (m_loadState >= Finished)
    133         return;
    134 #ifdef LOG_REQUESTS
    135     time_t finish;
    136     time(&finish);
    137     finish = finish - m_startTime;
    138     struct tm * timeinfo;
    139     char buffer[80];
    140     timeinfo = localtime(&finish);
    141     strftime(buffer, 80, "Time: %M:%S",timeinfo);
    142     android_printLog(ANDROID_LOG_DEBUG, "KM", "(%p) finish (%d) (%s) (%d) (%s)", this, --remaining, buffer, success, m_url.c_str());
    143 #endif
    144 
    145     // Make sure WebUrlLoaderClient doesn't delete us in the middle of this method.
    146     scoped_refptr<WebRequest> guard(this);
    147 
    148     m_loadState = Finished;
    149     if (success) {
    150         m_urlLoader->maybeCallOnMainThread(NewRunnableMethod(
    151                 m_urlLoader.get(), &WebUrlLoaderClient::didFinishLoading));
    152     } else {
    153         if (m_interceptResponse == NULL) {
    154             OwnPtr<WebResponse> webResponse(new WebResponse(m_request.get()));
    155             m_urlLoader->maybeCallOnMainThread(NewRunnableMethod(
    156                     m_urlLoader.get(), &WebUrlLoaderClient::didFail, webResponse.release()));
    157         } else {
    158             OwnPtr<WebResponse> webResponse(new WebResponse(m_url, m_interceptResponse->mimeType(), 0,
    159                     m_interceptResponse->encoding(), m_interceptResponse->status()));
    160             m_urlLoader->maybeCallOnMainThread(NewRunnableMethod(
    161                     m_urlLoader.get(), &WebUrlLoaderClient::didFail, webResponse.release()));
    162         }
    163     }
    164     m_networkBuffer = 0;
    165     m_request = 0;
    166     m_urlLoader = 0;
    167 }
    168 
    169 void WebRequest::appendFileToUpload(const std::string& filename)
    170 {
    171     // AppendFileToUpload is only valid before calling start
    172     ASSERT(m_loadState == Created, "appendFileToUpload called on a WebRequest not in CREATED state: (%s)", m_url.c_str());
    173     FilePath filePath(filename);
    174     m_request->AppendFileToUpload(filePath);
    175 }
    176 
    177 void WebRequest::appendBytesToUpload(WTF::Vector<char>* data)
    178 {
    179     // AppendBytesToUpload is only valid before calling start
    180     ASSERT(m_loadState == Created, "appendBytesToUpload called on a WebRequest not in CREATED state: (%s)", m_url.c_str());
    181     m_request->AppendBytesToUpload(data->data(), data->size());
    182     delete data;
    183 }
    184 
    185 void WebRequest::setRequestContext(WebRequestContext* context)
    186 {
    187     m_cacheMode = context->getCacheMode();
    188     if (m_request)
    189         m_request->set_context(context);
    190 }
    191 
    192 void WebRequest::updateLoadFlags(int& loadFlags)
    193 {
    194     if (m_cacheMode == 1) { // LOAD_CACHE_ELSE_NETWORK
    195         loadFlags |= net::LOAD_PREFERRING_CACHE;
    196         loadFlags &= ~net::LOAD_VALIDATE_CACHE;
    197     }
    198     if (m_cacheMode == 2) // LOAD_NO_CACHE
    199         loadFlags |= net::LOAD_BYPASS_CACHE;
    200     if (m_cacheMode == 3) // LOAD_CACHE_ONLY
    201         loadFlags |= net::LOAD_ONLY_FROM_CACHE;
    202 
    203     if (m_isSync)
    204         loadFlags |= net::LOAD_IGNORE_LIMITS;
    205 }
    206 
    207 void WebRequest::start()
    208 {
    209     ASSERT(m_loadState == Created, "Start called on a WebRequest not in CREATED state: (%s)", m_url.c_str());
    210 #ifdef LOG_REQUESTS
    211     android_printLog(ANDROID_LOG_DEBUG, "KM", "(%p) start (%d) (%s)", this, ++remaining, m_url.c_str());
    212     time(&m_startTime);
    213 #endif
    214 
    215     m_loadState = Started;
    216 
    217     if (m_interceptResponse != NULL)
    218         return handleInterceptedURL();
    219 
    220     // Handle data urls before we send it off to the http stack
    221     if (m_request->url().SchemeIs("data"))
    222         return handleDataURL(m_request->url());
    223 
    224     if (m_request->url().SchemeIs("browser"))
    225         return handleBrowserURL(m_request->url());
    226 
    227     // Update load flags with settings from WebSettings
    228     int loadFlags = m_request->load_flags();
    229     updateLoadFlags(loadFlags);
    230     m_request->set_load_flags(loadFlags);
    231 
    232     m_request->Start();
    233 }
    234 
    235 void WebRequest::cancel()
    236 {
    237     ASSERT(m_loadState >= Started, "Cancel called on a not started WebRequest: (%s)", m_url.c_str());
    238     ASSERT(m_loadState != Cancelled, "Cancel called on an already cancelled WebRequest: (%s)", m_url.c_str());
    239 
    240     // There is a possible race condition between the IO thread finishing the request and
    241     // the WebCore thread cancelling it. If the request has already finished, do
    242     // nothing to avoid sending duplicate finish messages to WebCore.
    243     if (m_loadState > Cancelled) {
    244         return;
    245     }
    246     ASSERT(m_request, "Request set to 0 before it is finished");
    247 
    248     m_loadState = Cancelled;
    249 
    250     m_request->Cancel();
    251     finish(true);
    252 }
    253 
    254 void WebRequest::pauseLoad(bool pause)
    255 {
    256     ASSERT(m_loadState >= GotData, "PauseLoad in state other than RESPONSE and GOTDATA");
    257     if (pause) {
    258         if (!m_isPaused)
    259             m_wantToPause = true;
    260     } else {
    261         m_wantToPause = false;
    262         if (m_isPaused) {
    263             m_isPaused = false;
    264             MessageLoop::current()->PostTask(FROM_HERE, m_runnableFactory.NewRunnableMethod(&WebRequest::startReading));
    265         }
    266     }
    267 }
    268 
    269 void WebRequest::handleInterceptedURL()
    270 {
    271     m_loadState = Response;
    272 
    273     const std::string& mime = m_interceptResponse->mimeType();
    274     // Get the MIME type from the URL. "text/html" is a last resort, hopefully overridden.
    275     std::string mimeType("text/html");
    276     if (mime == "") {
    277         // Gmail appends the MIME to the end of the URL, with a ? separator.
    278         size_t mimeTypeIndex = m_url.find_last_of('?');
    279         if (mimeTypeIndex != std::string::npos) {
    280             mimeType.assign(m_url.begin() + mimeTypeIndex + 1, m_url.end());
    281         } else {
    282             // Get the MIME type from the file extension, if any.
    283             FilePath path(m_url);
    284             net::GetMimeTypeFromFile(path, &mimeType);
    285         }
    286     } else {
    287         // Set from the intercept response.
    288         mimeType = mime;
    289     }
    290 
    291 
    292     OwnPtr<WebResponse> webResponse(new WebResponse(m_url, mimeType, 0, m_interceptResponse->encoding(), m_interceptResponse->status()));
    293     m_urlLoader->maybeCallOnMainThread(NewRunnableMethod(
    294             m_urlLoader.get(), &WebUrlLoaderClient::didReceiveResponse, webResponse.release()));
    295 
    296     do {
    297         // data is deleted in WebUrlLoaderClient::didReceiveAndroidFileData
    298         // data is sent to the webcore thread
    299         OwnPtr<std::vector<char> > data(new std::vector<char>);
    300         data->reserve(kInitialReadBufSize);
    301 
    302         // Read returns false on error and size of 0 on eof.
    303         if (!m_interceptResponse->readStream(data.get()) || data->size() == 0)
    304             break;
    305 
    306         m_loadState = GotData;
    307         m_urlLoader->maybeCallOnMainThread(NewRunnableMethod(
    308                 m_urlLoader.get(), &WebUrlLoaderClient::didReceiveAndroidFileData, data.release()));
    309     } while (true);
    310 
    311     finish(m_interceptResponse->status() == 200);
    312 }
    313 
    314 void WebRequest::handleDataURL(GURL url)
    315 {
    316     OwnPtr<std::string> data(new std::string);
    317     std::string mimeType;
    318     std::string charset;
    319 
    320     if (net::DataURL::Parse(url, &mimeType, &charset, data.get())) {
    321         // PopulateURLResponse from chrome implementation
    322         // weburlloader_impl.cc
    323         m_loadState = Response;
    324         OwnPtr<WebResponse> webResponse(new WebResponse(url.spec(), mimeType, data->size(), charset, 200));
    325         m_urlLoader->maybeCallOnMainThread(NewRunnableMethod(
    326                 m_urlLoader.get(), &WebUrlLoaderClient::didReceiveResponse, webResponse.release()));
    327 
    328         if (!data->empty()) {
    329             m_loadState = GotData;
    330             m_urlLoader->maybeCallOnMainThread(NewRunnableMethod(
    331                     m_urlLoader.get(), &WebUrlLoaderClient::didReceiveDataUrl, data.release()));
    332         }
    333     } else {
    334         // handle the failed case
    335     }
    336 
    337     finish(true);
    338 }
    339 
    340 void WebRequest::handleBrowserURL(GURL url)
    341 {
    342     std::string data("data:text/html;charset=utf-8,");
    343     if (url.spec() == "browser:incognito") {
    344         AssetManager* assetManager = globalAssetManager();
    345         Asset* asset = assetManager->open("webkit/incognito_mode_start_page.html", Asset::ACCESS_BUFFER);
    346         if (asset) {
    347             data.append((const char*)asset->getBuffer(false), asset->getLength());
    348             delete asset;
    349         }
    350     }
    351     GURL dataURL(data.c_str());
    352     handleDataURL(dataURL);
    353 }
    354 
    355 // Called upon a server-initiated redirect.  The delegate may call the
    356 // request's Cancel method to prevent the redirect from being followed.
    357 // Since there may be multiple chained redirects, there may also be more
    358 // than one redirect call.
    359 //
    360 // When this function is called, the request will still contain the
    361 // original URL, the destination of the redirect is provided in 'new_url'.
    362 // If the delegate does not cancel the request and |*defer_redirect| is
    363 // false, then the redirect will be followed, and the request's URL will be
    364 // changed to the new URL.  Otherwise if the delegate does not cancel the
    365 // request and |*defer_redirect| is true, then the redirect will be
    366 // followed once FollowDeferredRedirect is called on the URLRequest.
    367 //
    368 // The caller must set |*defer_redirect| to false, so that delegates do not
    369 // need to set it if they are happy with the default behavior of not
    370 // deferring redirect.
    371 void WebRequest::OnReceivedRedirect(net::URLRequest* newRequest, const GURL& newUrl, bool* deferRedirect)
    372 {
    373     ASSERT(m_loadState < Response, "Redirect after receiving response");
    374     ASSERT(newRequest && newRequest->status().is_success(), "Invalid redirect");
    375 
    376     OwnPtr<WebResponse> webResponse(new WebResponse(newRequest));
    377     webResponse->setUrl(newUrl.spec());
    378     m_urlLoader->maybeCallOnMainThread(NewRunnableMethod(
    379             m_urlLoader.get(), &WebUrlLoaderClient::willSendRequest, webResponse.release()));
    380 
    381     // Defer the redirect until followDeferredRedirect() is called.
    382     *deferRedirect = true;
    383 }
    384 
    385 // Called when we receive an authentication failure.  The delegate should
    386 // call request->SetAuth() with the user's credentials once it obtains them,
    387 // or request->CancelAuth() to cancel the login and display the error page.
    388 // When it does so, the request will be reissued, restarting the sequence
    389 // of On* callbacks.
    390 void WebRequest::OnAuthRequired(net::URLRequest* request, net::AuthChallengeInfo* authInfo)
    391 {
    392     ASSERT(m_loadState == Started, "OnAuthRequired called on a WebRequest not in STARTED state (state=%d)", m_loadState);
    393 
    394     scoped_refptr<net::AuthChallengeInfo> authInfoPtr(authInfo);
    395     bool firstTime = (m_authRequestCount == 0);
    396     ++m_authRequestCount;
    397 
    398     bool suppressDialog = (request->load_flags() & net::LOAD_DO_NOT_PROMPT_FOR_LOGIN);
    399 
    400     m_urlLoader->maybeCallOnMainThread(NewRunnableMethod(
    401             m_urlLoader.get(), &WebUrlLoaderClient::authRequired, authInfoPtr, firstTime, suppressDialog));
    402 }
    403 
    404 // Called when we received an SSL certificate error. The delegate will provide
    405 // the user the options to proceed, cancel, or view certificates.
    406 void WebRequest::OnSSLCertificateError(net::URLRequest* request, int cert_error, net::X509Certificate* cert)
    407 {
    408     scoped_refptr<net::X509Certificate> scoped_cert = cert;
    409     m_urlLoader->maybeCallOnMainThread(NewRunnableMethod(
    410             m_urlLoader.get(), &WebUrlLoaderClient::reportSslCertError, cert_error, scoped_cert));
    411 }
    412 
    413 void WebRequest::OnCertificateRequested(net::URLRequest* request, net::SSLCertRequestInfo* cert_request_info)
    414 {
    415     scoped_refptr<net::SSLCertRequestInfo> scoped_cert_request_info = cert_request_info;
    416     m_urlLoader->maybeCallOnMainThread(NewRunnableMethod(
    417             m_urlLoader.get(), &WebUrlLoaderClient::requestClientCert, scoped_cert_request_info));
    418 }
    419 
    420 
    421 // After calling Start(), the delegate will receive an OnResponseStarted
    422 // callback when the request has completed.  If an error occurred, the
    423 // request->status() will be set.  On success, all redirects have been
    424 // followed and the final response is beginning to arrive.  At this point,
    425 // meta data about the response is available, including for example HTTP
    426 // response headers if this is a request for a HTTP resource.
    427 void WebRequest::OnResponseStarted(net::URLRequest* request)
    428 {
    429     ASSERT(m_loadState == Started, "Got response after receiving response");
    430 
    431     m_loadState = Response;
    432     if (request && request->status().is_success()) {
    433         OwnPtr<WebResponse> webResponse(new WebResponse(request));
    434         m_urlLoader->maybeCallOnMainThread(NewRunnableMethod(
    435                 m_urlLoader.get(), &WebUrlLoaderClient::didReceiveResponse, webResponse.release()));
    436 
    437         // Start reading the response
    438         startReading();
    439     } else {
    440         finish(false);
    441     }
    442 }
    443 
    444 void WebRequest::setAuth(const string16& username, const string16& password)
    445 {
    446     ASSERT(m_loadState == Started, "setAuth called on a WebRequest not in STARTED state (state=%d)", m_loadState);
    447 
    448     m_request->SetAuth(username, password);
    449 }
    450 
    451 void WebRequest::cancelAuth()
    452 {
    453     ASSERT(m_loadState == Started, "cancelAuth called on a WebRequest not in STARTED state (state=%d)", m_loadState);
    454 
    455     m_request->CancelAuth();
    456 }
    457 
    458 void WebRequest::followDeferredRedirect()
    459 {
    460     ASSERT(m_loadState < Response, "Redirect after receiving response");
    461 
    462     m_request->FollowDeferredRedirect();
    463 }
    464 
    465 void WebRequest::proceedSslCertError()
    466 {
    467     m_request->ContinueDespiteLastError();
    468 }
    469 
    470 void WebRequest::cancelSslCertError(int cert_error)
    471 {
    472     m_request->SimulateError(cert_error);
    473 }
    474 
    475 void WebRequest::sslClientCert(EVP_PKEY* pkey, scoped_refptr<net::X509Certificate> chain)
    476 {
    477     base::ScopedOpenSSL<EVP_PKEY, EVP_PKEY_free> privateKey(pkey);
    478     if (privateKey.get() == NULL || chain.get() == NULL) {
    479         m_request->ContinueWithCertificate(NULL);
    480         return;
    481     }
    482     GURL gurl(m_url);
    483     net::OpenSSLPrivateKeyStore::GetInstance()->StorePrivateKey(gurl, privateKey.release());
    484     m_request->ContinueWithCertificate(chain.release());
    485 }
    486 
    487 void WebRequest::startReading()
    488 {
    489     ASSERT(m_networkBuffer == 0, "startReading called with a nonzero buffer");
    490     ASSERT(m_isPaused == 0, "startReading called in paused state");
    491     ASSERT(m_loadState == Response || m_loadState == GotData, "StartReading in state other than RESPONSE and GOTDATA");
    492     if (m_loadState > GotData) // We have been cancelled between reads
    493         return;
    494 
    495     if (m_wantToPause) {
    496         m_isPaused = true;
    497         return;
    498     }
    499 
    500     int bytesRead = 0;
    501 
    502     if (!read(&bytesRead)) {
    503         if (m_request && m_request->status().is_io_pending())
    504             return; // Wait for OnReadCompleted()
    505         return finish(false);
    506     }
    507 
    508     // bytesRead == 0 indicates finished
    509     if (!bytesRead)
    510         return finish(true);
    511 
    512     m_loadState = GotData;
    513     // Read ok, forward buffer to webcore
    514     m_urlLoader->maybeCallOnMainThread(NewRunnableMethod(m_urlLoader.get(), &WebUrlLoaderClient::didReceiveData, m_networkBuffer, bytesRead));
    515     m_networkBuffer = 0;
    516     MessageLoop::current()->PostTask(FROM_HERE, m_runnableFactory.NewRunnableMethod(&WebRequest::startReading));
    517 }
    518 
    519 bool WebRequest::read(int* bytesRead)
    520 {
    521     ASSERT(m_loadState == Response || m_loadState == GotData, "read in state other than RESPONSE and GOTDATA");
    522     ASSERT(m_networkBuffer == 0, "Read called with a nonzero buffer");
    523 
    524     // TODO: when asserts work, check that the buffer is 0 here
    525     m_networkBuffer = new net::IOBuffer(kInitialReadBufSize);
    526     return m_request->Read(m_networkBuffer, kInitialReadBufSize, bytesRead);
    527 }
    528 
    529 // This is called when there is data available
    530 
    531 // Called when the a Read of the response body is completed after an
    532 // IO_PENDING status from a Read() call.
    533 // The data read is filled into the buffer which the caller passed
    534 // to Read() previously.
    535 //
    536 // If an error occurred, request->status() will contain the error,
    537 // and bytes read will be -1.
    538 void WebRequest::OnReadCompleted(net::URLRequest* request, int bytesRead)
    539 {
    540     ASSERT(m_loadState == Response || m_loadState == GotData, "OnReadCompleted in state other than RESPONSE and GOTDATA");
    541 
    542     if (request->status().is_success()) {
    543         m_loadState = GotData;
    544         m_urlLoader->maybeCallOnMainThread(NewRunnableMethod(
    545                 m_urlLoader.get(), &WebUrlLoaderClient::didReceiveData, m_networkBuffer, bytesRead));
    546         m_networkBuffer = 0;
    547 
    548         // Get the rest of the data
    549         startReading();
    550     } else {
    551         finish(false);
    552     }
    553 }
    554 
    555 } // namespace android
    556