// Copyright 2015 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "sky/shell/android/update_service_android.h" #include "base/android/jni_string.h" #include "base/bind.h" #include "base/files/file_util.h" #include "base/logging.h" #include "base/threading/worker_pool.h" #include "jni/UpdateService_jni.h" #include "mojo/data_pipe_utils/data_pipe_utils.h" #include "mojo/public/cpp/application/connect.h" #include "mojo/public/interfaces/application/service_provider.mojom.h" #include "mojo/services/network/public/interfaces/network_service.mojom.h" #include "sky/shell/shell.h" namespace sky { namespace shell { namespace { // TODO(mpcomplete): make this a utility method? static mojo::URLLoaderPtr FetchURL( mojo::NetworkService* network_service, const std::string& url, base::Callback callback) { mojo::URLLoaderPtr loader; network_service->CreateURLLoader(GetProxy(&loader)); mojo::URLRequestPtr request = mojo::URLRequest::New(); request->url = url; request->auto_follow_redirects = true; loader->Start(request.Pass(), callback); return loader.Pass(); } } // namespace static jlong CheckForUpdates(JNIEnv* env, jobject jcaller, jstring j_data_dir) { std::string data_dir = base::android::ConvertJavaStringToUTF8(env, j_data_dir); scoped_ptr task(new UpdateTask(env, jcaller, data_dir)); // TODO(mpcomplete): download a manifest and check the version. task->DownloadAppBundle("file://" + data_dir + "/app.skyx"); return reinterpret_cast(task.release()); } bool RegisterUpdateService(JNIEnv* env) { return RegisterNativesImpl(env); } UpdateTask::UpdateTask(JNIEnv* env, jobject update_service, std::string data_dir) : final_path_(base::FilePath(data_dir).AppendASCII("app.skyx")) { update_service_.Reset(env, update_service); mojo::ServiceProviderPtr service_provider = CreateServiceProvider(Shell::Shared().service_provider_context()); mojo::ConnectToService(service_provider.get(), &network_service_); } void UpdateTask::DownloadAppBundle(const std::string& url) { LOG(INFO) << "Update downloading " << url << " to: " << final_path_.value(); if (!base::CreateTemporaryFile(&temp_path_)) { CallOnFinished(); return; } url_loader_ = FetchURL(network_service_.get(), url, base::Bind(&UpdateTask::OnResponse, base::Unretained(this))); } void UpdateTask::Detach(JNIEnv* env, jobject jcaller) { delete this; } void UpdateTask::OnResponse(mojo::URLResponsePtr response) { mojo::ScopedDataPipeConsumerHandle data; if (response->status_code == 200) data = response->body.Pass(); if (!data.is_valid()) { LOG(ERROR) << "Update failed: Server responded " << response->status_code; CallOnFinished(); return; } scoped_refptr worker_runner = base::WorkerPool::GetTaskRunner(true); mojo::common::CopyToFile( data.Pass(), temp_path_, worker_runner.get(), base::Bind(&UpdateTask::OnCopied, base::Unretained(this))); } void UpdateTask::OnCopied(bool success) { int64 size = 0; GetFileSize(temp_path_, &size); // This assumes temp files are created on the same volume as the data_dir. // TODO(mpcomplete): only replace on next startup. Otherwise, a currently // running version of the app will get confused. bool rv = base::ReplaceFile(temp_path_, final_path_, nullptr); LOG(INFO) << "Update finished: " << rv << " filesize(" << final_path_.value() << ")=" << size; CallOnFinished(); } void UpdateTask::CallOnFinished() { // The Java side is responsible for deleting the UpdateTask when finished. Java_UpdateService_onUpdateFinished(base::android::AttachCurrentThread(), update_service_.obj()); } } // namespace shell } // namespace sky