From d05b9480e6c0c0d3ecc6382a9b979013f681c48b Mon Sep 17 00:00:00 2001 From: Matt Perry Date: Wed, 2 Sep 2015 15:32:09 -0400 Subject: [PATCH] Introduce an UpdateService for android. Very simple so far. This schedules an alarm to fire once a day, kicking off a service that downloads a new app.skyx from a hardcoded URL. The new skyx replaces the current one. --- examples/fitness/apk/AndroidManifest.xml | 6 +- sky/build/sky_app.gni | 5 +- sky/shell/BUILD.gn | 3 + sky/shell/android/AndroidManifest.xml | 4 + sky/shell/android/library_loader.cc | 2 + .../org/domokit/sky/shell/SkyApplication.java | 4 +- .../org/domokit/sky/shell/UpdateService.java | 86 +++++++++++++ sky/shell/android/update_service_android.cc | 118 ++++++++++++++++++ sky/shell/android/update_service_android.h | 41 ++++++ 9 files changed, 266 insertions(+), 3 deletions(-) create mode 100644 sky/shell/android/org/domokit/sky/shell/UpdateService.java create mode 100644 sky/shell/android/update_service_android.cc create mode 100644 sky/shell/android/update_service_android.h diff --git a/examples/fitness/apk/AndroidManifest.xml b/examples/fitness/apk/AndroidManifest.xml index 7ddc4d3db52..83967b2cfe2 100644 --- a/examples/fitness/apk/AndroidManifest.xml +++ b/examples/fitness/apk/AndroidManifest.xml @@ -15,5 +15,9 @@ + - \ No newline at end of file + diff --git a/sky/build/sky_app.gni b/sky/build/sky_app.gni index ce66f6b5340..612edb67dde 100644 --- a/sky/build/sky_app.gni +++ b/sky/build/sky_app.gni @@ -47,8 +47,11 @@ template("sky_app") { sources = [ "$root_build_dir/icudtl.dat", - "$target_gen_dir/app.skyx", + "$target_gen_dir/app.skyx" ] + if (defined(invoker.manifest)) { + sources += [ invoker.manifest ] + } deps = [ ":$skyx_target_name", ":gen_${bundle_prefix}_bundle", diff --git a/sky/shell/BUILD.gn b/sky/shell/BUILD.gn index 6df0f3b4c0d..4b8c3505003 100644 --- a/sky/shell/BUILD.gn +++ b/sky/shell/BUILD.gn @@ -90,6 +90,7 @@ if (is_android) { "android/org/domokit/sky/shell/PlatformViewAndroid.java", "android/org/domokit/sky/shell/SkyMain.java", "android/org/domokit/sky/shell/TracingController.java", + "android/org/domokit/sky/shell/UpdateService.java", ] jni_package = "sky/shell" } @@ -105,6 +106,7 @@ if (is_android) { "android/sky_main.h", "android/tracing_controller.cc", "android/tracing_controller.h", + "android/update_service_android.cc", ] deps = common_deps + [ @@ -131,6 +133,7 @@ if (is_android) { "android/org/domokit/sky/shell/SkyApplication.java", "android/org/domokit/sky/shell/SkyMain.java", "android/org/domokit/sky/shell/TracingController.java", + "android/org/domokit/sky/shell/UpdateService.java", ] deps = [ diff --git a/sky/shell/android/AndroidManifest.xml b/sky/shell/android/AndroidManifest.xml index a3df5dc5fa1..ca7c18534a4 100644 --- a/sky/shell/android/AndroidManifest.xml +++ b/sky/shell/android/AndroidManifest.xml @@ -21,5 +21,9 @@ + diff --git a/sky/shell/android/library_loader.cc b/sky/shell/android/library_loader.cc index 5cb62e3e9b7..35e16052498 100644 --- a/sky/shell/android/library_loader.cc +++ b/sky/shell/android/library_loader.cc @@ -15,6 +15,7 @@ #include "sky/shell/android/platform_view_android.h" #include "sky/shell/android/sky_main.h" #include "sky/shell/android/tracing_controller.h" +#include "sky/shell/android/update_service_android.h" namespace { @@ -25,6 +26,7 @@ base::android::RegistrationMethod kSkyRegisteredMethods[] = { {"PlatformViewAndroid", sky::shell::PlatformViewAndroid::Register}, {"SkyMain", sky::shell::RegisterSkyMain}, {"TracingController", sky::shell::RegisterTracingController}, + {"UpdateService", sky::shell::RegisterUpdateService}, }; bool RegisterJNI(JNIEnv* env) { diff --git a/sky/shell/android/org/domokit/sky/shell/SkyApplication.java b/sky/shell/android/org/domokit/sky/shell/SkyApplication.java index ca27b163730..27f0abf403f 100644 --- a/sky/shell/android/org/domokit/sky/shell/SkyApplication.java +++ b/sky/shell/android/org/domokit/sky/shell/SkyApplication.java @@ -34,10 +34,11 @@ import org.domokit.vsync.VSyncProviderImpl; public class SkyApplication extends BaseChromiumApplication { static final String SNAPSHOT = "snapshot_blob.bin"; static final String APP_BUNDLE = "app.skyx"; + static final String MANIFEST = "sky.yaml"; private static final String TAG = "SkyApplication"; private static final String PRIVATE_DATA_DIRECTORY_SUFFIX = "sky_shell"; - private static final String[] SKY_RESOURCES = {"icudtl.dat", SNAPSHOT, APP_BUNDLE}; + private static final String[] SKY_RESOURCES = {"icudtl.dat", SNAPSHOT, APP_BUNDLE, MANIFEST}; private ResourceExtractor mResourceExtractor; @@ -51,6 +52,7 @@ public class SkyApplication extends BaseChromiumApplication { initJavaUtils(); initResources(); initNative(); + UpdateService.init(getApplicationContext()); onServiceRegistryAvailable(ServiceRegistry.SHARED); } diff --git a/sky/shell/android/org/domokit/sky/shell/UpdateService.java b/sky/shell/android/org/domokit/sky/shell/UpdateService.java new file mode 100644 index 00000000000..93cec09fe3b --- /dev/null +++ b/sky/shell/android/org/domokit/sky/shell/UpdateService.java @@ -0,0 +1,86 @@ +// 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. + +package org.domokit.sky.shell; + +import android.app.AlarmManager; +import android.app.Service; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.os.IBinder; +import android.util.Log; +import java.io.File; +import org.chromium.base.CalledByNative; +import org.chromium.base.JNINamespace; +import org.chromium.base.PathUtils; + +/** + * A class that schedules and runs periodic autoupdate checks. + */ +@JNINamespace("sky::shell") +public class UpdateService extends Service { + private static final String TAG = "UpdateService"; + private static final int REQUEST_CODE = 0; // Not sure why this is needed. + private static final boolean ENABLED = false; + + private long mNativePtr = 0; + + public static void init(Context context) { + if (ENABLED) + maybeScheduleUpdateCheck(context); + } + + private static void maybeScheduleUpdateCheck(Context context) { + Intent alarm = new Intent(context, UpdateService.class); + PendingIntent existingIntent = PendingIntent.getService( + context, REQUEST_CODE, alarm, PendingIntent.FLAG_NO_CREATE); + if (existingIntent != null) { + Log.i(TAG, "Update alarm exists: " + PathUtils.getDataDirectory(context)); + return; + } + + PendingIntent pendingIntent = PendingIntent.getService( + context, REQUEST_CODE, alarm, PendingIntent.FLAG_UPDATE_CURRENT); + AlarmManager manager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); + manager.setInexactRepeating(AlarmManager.RTC, System.currentTimeMillis(), + AlarmManager.INTERVAL_DAY, pendingIntent); + + Log.i(TAG, "Update scheduled: " + PathUtils.getDataDirectory(context)); + } + + @Override + public void onCreate() { + super.onCreate(); + SkyMain.ensureInitialized(getApplicationContext(), null); + } + + @Override + public void onDestroy() { + if (mNativePtr != 0) + nativeDetach(mNativePtr); + mNativePtr = 0; + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + final File dataDir = new File(PathUtils.getDataDirectory(this)); + mNativePtr = nativeCheckForUpdates(dataDir.getPath()); + return START_NOT_STICKY; + } + + @Override + public IBinder onBind(Intent intent) { + return null; + } + + @SuppressWarnings("unused") + @CalledByNative + public void onUpdateFinished() { + stopSelf(); + } + + private native long nativeCheckForUpdates(String dataDir); + private native void nativeDetach(long nativeUpdateTask); +} diff --git a/sky/shell/android/update_service_android.cc b/sky/shell/android/update_service_android.cc new file mode 100644 index 00000000000..f3f2911f0c7 --- /dev/null +++ b/sky/shell/android/update_service_android.cc @@ -0,0 +1,118 @@ +// 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 diff --git a/sky/shell/android/update_service_android.h b/sky/shell/android/update_service_android.h new file mode 100644 index 00000000000..e6498c2cc1f --- /dev/null +++ b/sky/shell/android/update_service_android.h @@ -0,0 +1,41 @@ +// 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. + +#ifndef SKY_SHELL_UPDATE_SERVICE_H_ +#define SKY_SHELL_UPDATE_SERVICE_H_ + +#include + +#include "base/android/jni_string.h" +#include "base/files/file_path.h" +#include "mojo/services/network/public/interfaces/network_service.mojom.h" + +namespace sky { +namespace shell { + +class UpdateTask { + public: + UpdateTask(JNIEnv* env, jobject update_service, std::string data_dir); + + void DownloadAppBundle(const std::string& url); + void Detach(JNIEnv* env, jobject jcaller); + + private: + void OnResponse(mojo::URLResponsePtr response); + void OnCopied(bool success); + void CallOnFinished(); + + base::android::ScopedJavaGlobalRef update_service_; + mojo::NetworkServicePtr network_service_; + base::FilePath temp_path_; + base::FilePath final_path_; + mojo::URLLoaderPtr url_loader_; +}; + +bool RegisterUpdateService(JNIEnv* env); + +} // namespace shell +} // namespace sky + +#endif // SKY_SHELL_UPDATE_SERVICE_H_