Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 package org.chromium.webapk.shell_apk; | 5 package org.chromium.webapk.shell_apk; |
| 6 | 6 |
| 7 import android.app.Service; | 7 import android.app.Service; |
| 8 import android.content.Context; | 8 import android.content.Context; |
| 9 import android.content.Intent; | 9 import android.content.Intent; |
| 10 import android.content.SharedPreferences; | |
| 11 import android.content.pm.PackageInfo; | |
| 12 import android.content.pm.PackageManager; | |
| 13 import android.os.Bundle; | 10 import android.os.Bundle; |
| 14 import android.os.IBinder; | 11 import android.os.IBinder; |
| 15 import android.util.Log; | 12 import android.util.Log; |
| 16 | 13 |
| 17 import org.chromium.webapk.lib.common.WebApkUtils; | 14 import org.chromium.webapk.lib.common.WebApkUtils; |
| 18 | 15 |
| 19 import java.io.File; | |
| 20 import java.lang.reflect.Constructor; | 16 import java.lang.reflect.Constructor; |
| 21 import java.util.Scanner; | |
| 22 | 17 |
| 23 /** | 18 /** |
| 24 * Shell class for services provided by WebAPK to Chrome. Extracts code with imp lementation of | 19 * Shell class for services provided by WebAPK to Chrome. Extracts code with imp lementation of |
| 25 * services from .dex file in Chrome APK. | 20 * services from .dex file in Chrome APK. |
| 26 */ | 21 */ |
| 27 public class WebApkServiceFactory extends Service { | 22 public class WebApkServiceFactory extends Service { |
| 28 private static final String TAG = "cr_WebApkServiceFactory"; | 23 private static final String TAG = "cr_WebApkServiceFactory"; |
| 29 | 24 |
| 30 /** | 25 /** |
| 31 * Name of the class with IBinder API implementation. | 26 * Name of the class with IBinder API implementation. |
| 32 */ | 27 */ |
| 33 private static final String WEBAPK_SERVICE_IMPL_CLASS_NAME = | 28 private static final String WEBAPK_SERVICE_IMPL_CLASS_NAME = |
| 34 "org.chromium.webapk.lib.runtime_library.WebApkServiceImpl"; | 29 "org.chromium.webapk.lib.runtime_library.WebApkServiceImpl"; |
| 35 | 30 |
| 36 /** | 31 /** |
| 37 * Name of the shared preferences file. | |
| 38 */ | |
| 39 private static final String PREF_PACKAGE = "org.chromium.webapk.shell_apk"; | |
| 40 | |
| 41 /** | |
| 42 * Name of the shared preference for Chrome's version code. | |
| 43 */ | |
| 44 private static final String REMOTE_VERSION_CODE_PREF = | |
| 45 "org.chromium.webapk.shell_apk.version_code"; | |
| 46 | |
| 47 /** | |
| 48 * Name of the shared preference for the version number of the dynamically l oaded dex. | |
| 49 */ | |
| 50 private static final String RUNTIME_DEX_VERSION_PREF = | |
| 51 "org.chromium.webapk.shell_apk.dex_version"; | |
| 52 | |
| 53 /** | |
| 54 * Key for passing id of icon to represent WebAPK notifications in status ba r. | 32 * Key for passing id of icon to represent WebAPK notifications in status ba r. |
| 55 */ | 33 */ |
| 56 private static final String KEY_SMALL_ICON_ID = "small_icon_id"; | 34 private static final String KEY_SMALL_ICON_ID = "small_icon_id"; |
| 57 | 35 |
| 58 /** | 36 /** |
| 59 * Key for passing package name of only process allowed to call the service' s methods. | 37 * Key for passing package name of only process allowed to call the service' s methods. |
| 60 */ | 38 */ |
| 61 private static final String KEY_HOST_BROWSER_PACKAGE = "host_browser_package "; | 39 private static final String KEY_HOST_BROWSER_PACKAGE = "host_browser_package "; |
| 62 | 40 |
| 63 /* | |
| 64 * ClassLoader for loading {@link WEBAPK_SERVICE_IMPL_CLASS_NAME}. Static so that all | |
| 65 * {@link WebApkServiceFactory} service instatiations use the same ClassLoad er during the app's | |
| 66 * lifetime. | |
| 67 */ | |
| 68 private static ClassLoader sClassLoader; | |
| 69 | |
| 70 @Override | 41 @Override |
| 71 public IBinder onBind(Intent intent) { | 42 public IBinder onBind(Intent intent) { |
| 72 ClassLoader webApkClassLoader = getClassLoaderInstance(this); | 43 ClassLoader webApkClassLoader = |
| 44 HostBrowserClassLoader.getClassLoaderInstance(this, WEBAPK_SERVI CE_IMPL_CLASS_NAME); | |
|
Xi Han
2016/06/27 18:27:08
Is it good to move the call to get ClassLoader in
pkotwicz
2016/06/27 20:44:28
It is not important that interacting with the WebA
Xi Han
2016/06/28 13:53:39
I don't have strong opinion on this, it seems ok s
| |
| 73 if (webApkClassLoader == null) { | 45 if (webApkClassLoader == null) { |
| 74 Log.w(TAG, "Unable to create ClassLoader."); | 46 Log.w(TAG, "Unable to create ClassLoader."); |
| 75 return null; | 47 return null; |
| 76 } | 48 } |
| 77 | 49 |
| 78 try { | 50 try { |
| 79 Class<?> webApkServiceImplClass = | 51 Class<?> webApkServiceImplClass = |
| 80 webApkClassLoader.loadClass(WEBAPK_SERVICE_IMPL_CLASS_NAME); | 52 webApkClassLoader.loadClass(WEBAPK_SERVICE_IMPL_CLASS_NAME); |
| 81 Constructor<?> webApkServiceImplConstructor = | 53 Constructor<?> webApkServiceImplConstructor = |
| 82 webApkServiceImplClass.getConstructor(Context.class, Bundle. class); | 54 webApkServiceImplClass.getConstructor(Context.class, Bundle. class); |
| 83 String hostPackageName = WebApkUtils.getHostBrowserPackageName(this) ; | 55 String hostPackageName = WebApkUtils.getHostBrowserPackageName(this) ; |
| 84 Bundle bundle = new Bundle(); | 56 Bundle bundle = new Bundle(); |
| 85 bundle.putInt(KEY_SMALL_ICON_ID, R.drawable.app_icon); | 57 bundle.putInt(KEY_SMALL_ICON_ID, R.drawable.app_icon); |
| 86 bundle.putString(KEY_HOST_BROWSER_PACKAGE, hostPackageName); | 58 bundle.putString(KEY_HOST_BROWSER_PACKAGE, hostPackageName); |
| 87 return (IBinder) webApkServiceImplConstructor.newInstance(new Object [] {this, bundle}); | 59 return (IBinder) webApkServiceImplConstructor.newInstance(new Object [] {this, bundle}); |
| 88 } catch (Exception e) { | 60 } catch (Exception e) { |
| 89 Log.w(TAG, "Unable to create WebApkServiceImpl."); | 61 Log.w(TAG, "Unable to create WebApkServiceImpl."); |
| 90 e.printStackTrace(); | 62 e.printStackTrace(); |
| 91 return null; | 63 return null; |
| 92 } | 64 } |
| 93 } | 65 } |
| 94 | |
| 95 /** | |
| 96 * Gets / creates ClassLoader for loading {@link WEBAPK_SERVICE_IMPL_CLASS_N AME}. | |
| 97 * @param context WebAPK's context. | |
| 98 * @return The ClassLoader. | |
| 99 */ | |
| 100 private static ClassLoader getClassLoaderInstance(Context context) { | |
| 101 if (sClassLoader == null) { | |
| 102 sClassLoader = createClassLoader(context); | |
| 103 } | |
| 104 return sClassLoader; | |
| 105 } | |
| 106 | |
| 107 /** | |
| 108 * Creates ClassLoader for loading {@link WEBAPK_SERVICE_IMPL_CLASS_NAME}. | |
| 109 * @param context WebAPK's context. | |
| 110 * @return The ClassLoader. | |
| 111 */ | |
| 112 private static ClassLoader createClassLoader(Context context) { | |
| 113 Context remoteContext = WebApkUtils.getHostBrowserContext(context); | |
| 114 if (remoteContext == null) { | |
| 115 Log.w(TAG, "Failed to get remote context."); | |
| 116 return null; | |
| 117 } | |
| 118 | |
| 119 SharedPreferences preferences = context.getSharedPreferences(PREF_PACKAG E, MODE_PRIVATE); | |
| 120 | |
| 121 int runtimeDexVersion = preferences.getInt(RUNTIME_DEX_VERSION_PREF, -1) ; | |
| 122 int newRuntimeDexVersion = checkForNewRuntimeDexVersion(preferences, rem oteContext); | |
| 123 if (newRuntimeDexVersion == -1) { | |
| 124 newRuntimeDexVersion = runtimeDexVersion; | |
| 125 } | |
| 126 File localDexDir = context.getDir("dex", Context.MODE_PRIVATE); | |
| 127 if (newRuntimeDexVersion != runtimeDexVersion) { | |
| 128 Log.w(TAG, "Delete cached dex files."); | |
| 129 DexLoader.deleteCachedDexes(localDexDir); | |
| 130 } | |
| 131 | |
| 132 String dexAssetName = WebApkUtils.getRuntimeDexName(newRuntimeDexVersion ); | |
| 133 File remoteDexFile = | |
| 134 new File(remoteContext.getDir("dex", Context.MODE_PRIVATE), dexA ssetName); | |
| 135 return DexLoader.load(remoteContext, dexAssetName, WEBAPK_SERVICE_IMPL_C LASS_NAME, | |
| 136 remoteDexFile, localDexDir); | |
| 137 } | |
| 138 | |
| 139 /** | |
| 140 * Checks if there is a new "runtime dex" version number. If there is a new version number, | |
| 141 * updates SharedPreferences. | |
| 142 * @param preferences WebAPK's SharedPreferences. | |
| 143 * @param remoteContext | |
| 144 * @return The new "runtime dex" version number. -1 if there is no new versi on number. | |
| 145 */ | |
| 146 private static int checkForNewRuntimeDexVersion( | |
| 147 SharedPreferences preferences, Context remoteContext) { | |
| 148 // The "runtime dex" version only changes when {@link remoteContext}'s A PK version code | |
| 149 // changes. Checking the APK's version code is less expensive than readi ng from the APK's | |
| 150 // assets. | |
| 151 PackageInfo remotePackageInfo = null; | |
| 152 try { | |
| 153 remotePackageInfo = remoteContext.getPackageManager().getPackageInfo ( | |
| 154 remoteContext.getPackageName(), 0); | |
| 155 } catch (PackageManager.NameNotFoundException e) { | |
| 156 Log.e(TAG, "Failed to get remote package info."); | |
| 157 return -1; | |
| 158 } | |
| 159 | |
| 160 int cachedRemoteVersionCode = preferences.getInt(REMOTE_VERSION_CODE_PRE F, -1); | |
| 161 if (cachedRemoteVersionCode == remotePackageInfo.versionCode) { | |
| 162 return -1; | |
| 163 } | |
| 164 | |
| 165 int runtimeDexVersion = readAssetContentsToInt(remoteContext, "webapk_de x_version.txt"); | |
| 166 SharedPreferences.Editor editor = preferences.edit(); | |
| 167 editor.putInt(REMOTE_VERSION_CODE_PREF, remotePackageInfo.versionCode); | |
| 168 editor.putInt(RUNTIME_DEX_VERSION_PREF, runtimeDexVersion); | |
| 169 editor.apply(); | |
| 170 return runtimeDexVersion; | |
| 171 } | |
| 172 | |
| 173 /** | |
| 174 * Returns the first integer in an asset file's contents. | |
| 175 * @param context | |
| 176 * @param assetName The name of the asset. | |
| 177 * @return The first integer. | |
| 178 */ | |
| 179 private static int readAssetContentsToInt(Context context, String assetName) { | |
| 180 Scanner scanner = null; | |
| 181 int value = -1; | |
| 182 try { | |
| 183 scanner = new Scanner(context.getAssets().open(assetName)); | |
| 184 value = scanner.nextInt(); | |
| 185 scanner.close(); | |
| 186 } catch (Exception e) { | |
| 187 } finally { | |
| 188 if (scanner != null) { | |
| 189 try { | |
| 190 scanner.close(); | |
| 191 } catch (Exception e) { | |
| 192 } | |
| 193 } | |
| 194 } | |
| 195 return value; | |
| 196 } | |
| 197 } | 66 } |
| OLD | NEW |