OLD | NEW |
| (Empty) |
1 // Copyright 2015 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 package org.chromium.webapk.shell_apk; | |
6 | |
7 import android.app.Service; | |
8 import android.content.Context; | |
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; | |
14 import android.os.IBinder; | |
15 import android.util.Log; | |
16 | |
17 import org.chromium.webapk.lib.common.WebApkUtils; | |
18 | |
19 import java.io.File; | |
20 import java.lang.reflect.Constructor; | |
21 import java.util.Scanner; | |
22 | |
23 /** | |
24 * Shell class for services provided by WebAPK to Chrome. Extracts code with imp
lementation of | |
25 * services from .dex file in Chrome APK. | |
26 */ | |
27 public class WebApkServiceFactory extends Service { | |
28 private static final String TAG = "cr.WebApkServiceFactory"; | |
29 | |
30 /** | |
31 * Name of the class with IBinder API implementation. | |
32 */ | |
33 private static final String MINTED_SERVICE_IMPL_CLASS_NAME = | |
34 "org.chromium.webapk.lib.runtime_library.WebApkServiceImpl"; | |
35 | |
36 private static final String KEY_APP_ICON_ID = "app_icon_id"; | |
37 private static final String KEY_EXPECTED_HOST_BROWSER = "expected_host_brows
er"; | |
38 | |
39 /** | |
40 * Name of the shared preferences file. | |
41 */ | |
42 private static final String PREF_FILE_NAME = "MINT_PREFS"; | |
43 | |
44 /** | |
45 * Name of the shared preference for Chrome's version code. | |
46 */ | |
47 private static final String REMOTE_VERSION_CODE_PREF = | |
48 "webapk_service_factory_remote_version_code"; | |
49 | |
50 /** | |
51 * Name of the shared preference for the version number of the dynamically l
oaded dex. | |
52 */ | |
53 private static final String RUNTIME_DEX_VERSION_PREF = | |
54 "webapk_service_factory_runtime_dex_version"; | |
55 | |
56 /* | |
57 * ClassLoader for loading {@link MINTED_SERVICE_IMPL_CLASS_NAME}. Static so
that all | |
58 * {@link WebApkServiceFactory} service instatiations use the same ClassLoad
er during the app's | |
59 * lifetime. | |
60 */ | |
61 private static ClassLoader sClassLoader; | |
62 | |
63 @Override | |
64 public IBinder onBind(Intent intent) { | |
65 if (sClassLoader == null) { | |
66 sClassLoader = createClassLoader(this); | |
67 if (sClassLoader == null) { | |
68 Log.w(TAG, "Unable to create ClassLoader."); | |
69 return null; | |
70 } | |
71 } | |
72 | |
73 try { | |
74 Class<?> mintedServiceImplClass = | |
75 sClassLoader.loadClass(MINTED_SERVICE_IMPL_CLASS_NAME); | |
76 Constructor<?> mintedServiceImplConstructor = | |
77 mintedServiceImplClass.getConstructor(Context.class, Bundle.
class); | |
78 String expectedHostPackageName = WebApkUtils.getHostBrowserPackageNa
me(this); | |
79 Bundle bundle = new Bundle(); | |
80 bundle.putInt(KEY_APP_ICON_ID, R.drawable.app_icon); | |
81 bundle.putString(KEY_EXPECTED_HOST_BROWSER, expectedHostPackageName)
; | |
82 return (IBinder) mintedServiceImplConstructor.newInstance(new Object
[] {this, bundle}); | |
83 } catch (Exception e) { | |
84 Log.w(TAG, "Unable to create WebApkServiceImpl."); | |
85 e.printStackTrace(); | |
86 return null; | |
87 } | |
88 } | |
89 | |
90 /** | |
91 * Creates ClassLoader for loading {@link MINTED_SERVICE_IMPL_CLASS_NAME}. | |
92 * @param context WebAPK's context. | |
93 * @return The ClassLoader. | |
94 */ | |
95 private static ClassLoader createClassLoader(Context context) { | |
96 Context remoteContext = WebApkUtils.getHostBrowserContext(context); | |
97 if (remoteContext == null) { | |
98 Log.w(TAG, "Failed to get remote context."); | |
99 return null; | |
100 } | |
101 | |
102 SharedPreferences preferences = context.getSharedPreferences(PREF_FILE_N
AME, MODE_PRIVATE); | |
103 | |
104 int runtimeDexVersion = preferences.getInt(RUNTIME_DEX_VERSION_PREF, -1)
; | |
105 int newRuntimeDexVersion = checkForNewRuntimeDexVersion(preferences, rem
oteContext); | |
106 if (newRuntimeDexVersion == -1) { | |
107 newRuntimeDexVersion = runtimeDexVersion; | |
108 } | |
109 File localDexDir = context.getDir("dex", Context.MODE_PRIVATE); | |
110 if (newRuntimeDexVersion != runtimeDexVersion) { | |
111 Log.w(TAG, "Delete cached dex files."); | |
112 DexLoader.deleteCachedDexes(localDexDir); | |
113 } | |
114 | |
115 String dexAssetName = WebApkUtils.getRuntimeDexName(newRuntimeDexVersion
); | |
116 File remoteDexFile = | |
117 new File(remoteContext.getDir("dex", Context.MODE_PRIVATE), dexA
ssetName); | |
118 return DexLoader.load(remoteContext, dexAssetName, MINTED_SERVICE_IMPL_C
LASS_NAME, | |
119 remoteDexFile, localDexDir); | |
120 } | |
121 | |
122 /** | |
123 * Checks if there is a new "runtime dex" version number. If there is a new
version number, | |
124 * updates SharedPreferences. | |
125 * @param preferences WebAPK's SharedPreferences. | |
126 * @param remoteContext | |
127 * @return The new "runtime dex" version number. -1 if there is no new versi
on number. | |
128 */ | |
129 private static int checkForNewRuntimeDexVersion( | |
130 SharedPreferences preferences, Context remoteContext) { | |
131 // The "runtime dex" version only changes when {@link remoteContext}'s A
PK version code | |
132 // changes. Checking the APK's version code is less expensive than readi
ng from the APK's | |
133 // assets. | |
134 PackageInfo remotePackageInfo = null; | |
135 try { | |
136 remotePackageInfo = remoteContext.getPackageManager().getPackageInfo
( | |
137 remoteContext.getPackageName(), 0); | |
138 } catch (PackageManager.NameNotFoundException e) { | |
139 Log.e(TAG, "Failed to get remote package info."); | |
140 return -1; | |
141 } | |
142 | |
143 int cachedRemoteVersionCode = preferences.getInt(REMOTE_VERSION_CODE_PRE
F, -1); | |
144 if (cachedRemoteVersionCode == remotePackageInfo.versionCode) { | |
145 return -1; | |
146 } | |
147 | |
148 int runtimeDexVersion = readAssetContentsToInt(remoteContext, "web_apk_d
ex_version.txt"); | |
149 SharedPreferences.Editor editor = preferences.edit(); | |
150 editor.putInt(REMOTE_VERSION_CODE_PREF, remotePackageInfo.versionCode); | |
151 editor.putInt(RUNTIME_DEX_VERSION_PREF, runtimeDexVersion); | |
152 editor.apply(); | |
153 return runtimeDexVersion; | |
154 } | |
155 | |
156 /** | |
157 * Returns the first integer in an asset file's contents. | |
158 * @param context | |
159 * @param assetName The name of the asset. | |
160 */ | |
161 private static int readAssetContentsToInt(Context context, String assetName)
{ | |
162 try (Scanner scanner = new Scanner(context.getAssets().open(assetName)))
{ | |
163 return scanner.nextInt(); | |
164 } catch (Exception e) { | |
165 return -1; | |
166 } | |
167 } | |
168 } | |
OLD | NEW |