Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(438)

Side by Side Diff: base/android/java/src/org/chromium/base/library_loader/LibraryLoaderHelper.java

Issue 200753002: [Android] Workaround of an android platform bug. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: ChildProcessService needs to use the hack Created 6 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright 2014 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
6 package org.chromium.base.library_loader;
7
8 import android.content.Context;
9 import android.content.pm.ApplicationInfo;
10 import android.os.Build;
11 import android.util.Log;
12
13 import java.io.File;
14 import java.io.FileOutputStream;
15 import java.io.IOException;
16 import java.io.InputStream;
17 import java.util.zip.ZipEntry;
18 import java.util.zip.ZipFile;
19
20 /**
21 * The class provides helper functions to extract native libraries from APK,
22 * and load libraries from there.
23 *
24 * The class should be package-visible only, but made public for testing
25 * purpose.
26 */
27 public class LibraryLoaderHelper {
28 private static final String TAG = "LibraryLoaderHelper";
29
30 private static final String LIB_DIR = "lib";
31
32 /**
33 * One-way switch becomes true if native libraries were unpacked
34 * from APK.
35 */
36 private static boolean sLibrariesWereUnpacked = false;
37
38 /**
39 * Loads native libraries using workaround only, skip the library in system
40 * lib path. The method exists only for testing purpose.
41 * Caller must ensure thread safety of this method.
42 * @param context
43 */
44 public static boolean loadNativeLibrariesUsingWorkaroundForTesting(Context c ontext) {
45 // Although tryLoadLibraryUsingWorkaround might be called multiple times ,
46 // libraries should only be unpacked once, this is guaranteed by
47 // sLibrariesWereUnpacked.
48 for (String library : NativeLibraries.LIBRARIES) {
49 if (!tryLoadLibraryUsingWorkaround(context, library)) {
50 return false;
51 }
52 }
53 return true;
54 }
55
56 /**
57 * Try to load a native library using a workaround of
58 * http://b/13216167.
59 *
60 * Workaround for b/13216167 was adapted from code in
61 * https://googleplex-android-review.git.corp.google.com/#/c/433061
62 *
63 * More details about http://b/13216167:
64 * PackageManager may fail to update shared library.
65 *
66 * Native library directory in an updated package is a symbolic link
67 * to a directory in /data/app-lib/<package name>, for example:
68 * /data/data/com.android.chrome/lib -> /data/app-lib/com.android.chrome[-1] .
69 * When updating the application, the PackageManager create a new directory,
70 * e.g., /data/app-lib/com.android.chrome-2, and remove the old symlink and
71 * recreate one to the new directory. However, on some devices (e.g. Sony Xp eria),
72 * the symlink was updated, but fails to extract new native libraries from
73 * the new apk.
74
75 * We make the following changes to alleviate the issue:
76 * 1) name the native library with apk version code, e.g.,
77 * libchrome.1750.136.so, 1750.136 is Chrome version number;
78 * 2) first try to load the library using System.loadLibrary,
79 * if that failed due to the library file was not found,
80 * search the named library in a /data/data/com.android.chrome/app_lib
81 * directory. Because of change 1), each version has a different native
82 * library name, so avoid mistakenly using the old native library.
83 *
84 * If named library is not in /data/data/com.android.chrome/app_lib directo ry,
85 * extract native libraries from apk and cache in the directory.
86 *
87 * This function doesn't throw UnsatisfiedLinkError, the caller needs to
88 * check the return value.
89 */
90 static boolean tryLoadLibraryUsingWorkaround(Context context, String library ) {
91 assert context != null;
92 File libFile = getWorkaroundLibFile(context, library);
93 if (!libFile.exists() && !unpackLibrariesOnce(context)) {
94 return false;
95 }
96 try {
97 System.load(libFile.getAbsolutePath());
98 return true;
99 } catch (UnsatisfiedLinkError e) {
100 return false;
101 }
102 }
103
104 /**
105 * Returns the directory for holding extracted native libraries.
106 * It may create the directory if it doesn't exist.
107 *
108 * @param context
109 * @return the directory file object
110 */
111 public static File getWorkaroundLibDir(Context context) {
112 return context.getDir(LIB_DIR, Context.MODE_PRIVATE);
113 }
114
115 private static File getWorkaroundLibFile(Context context, String library) {
116 String libName = System.mapLibraryName(library);
117 return new File(getWorkaroundLibDir(context), libName);
118 }
119
120 /**
121 * Unpack native libraries from the APK file. The method is supposed to
122 * be called only once. It deletes existing files in unpacked directory
123 * before unpacking.
124 *
125 * @param context
126 * @return true when unpacking was successful, false when failed or called
127 * more than once.
128 */
129 private static boolean unpackLibrariesOnce(Context context) {
130 if (sLibrariesWereUnpacked) {
131 return false;
132 }
133 sLibrariesWereUnpacked = true;
134
135 File libDir = getWorkaroundLibDir(context);
136 if (libDir.exists()) {
137 assert libDir.isDirectory();
138 deleteDirectorySync(libDir);
139 }
140
141 try {
142 ApplicationInfo appInfo = context.getApplicationInfo();
143 ZipFile file = new ZipFile(new File(appInfo.sourceDir), ZipFile.OPEN _READ);
144 for (String libName : NativeLibraries.LIBRARIES) {
145 String jniNameInApk = "lib/" + Build.CPU_ABI + "/" +
146 System.mapLibraryName(libName);
147
148 final ZipEntry entry = file.getEntry(jniNameInApk);
149 if (entry == null) {
150 Log.e(TAG, appInfo.sourceDir + " doesn't have file " + jniNa meInApk);
151 file.close();
152 deleteDirectorySync(libDir);
153 return false;
154 }
155
156 File outputFile = getWorkaroundLibFile(context, libName);
157
158 Log.i(TAG, "Extracting native libraries into " + outputFile.getA bsolutePath());
159
160 assert !outputFile.exists();
161
162 try {
163 if (!outputFile.createNewFile()) {
164 throw new IOException();
165 }
166
167 InputStream is = null;
168 FileOutputStream os = null;
169 try {
170 is = file.getInputStream(entry);
171 os = new FileOutputStream(outputFile);
172 int count = 0;
173 byte[] buffer = new byte[16 * 1024];
174 while ((count = is.read(buffer)) > 0) {
175 os.write(buffer, 0, count);
176 }
177 } finally {
178 try {
179 if (is != null) is.close();
180 } finally {
181 if (os != null) os.close();
182 }
183 }
184 // Change permission to rwxr-xr-x
185 outputFile.setReadable(true, false);
186 outputFile.setExecutable(true, false);
187 outputFile.setWritable(true);
188 } catch (IOException e) {
189 if (outputFile.exists()) {
190 if (!outputFile.delete()) {
191 Log.e(TAG, "Failed to delete " + outputFile.getAbsol utePath());
192 }
193 }
194 file.close();
195 throw e;
196 }
197 }
198 file.close();
199 return true;
200 } catch (IOException e) {
201 Log.e(TAG, "Failed to unpack native libraries", e);
202 deleteDirectorySync(libDir);
203 return false;
204 }
205 }
206
207 /**
208 * Delete old library files in the backup directory.
209 * The actual deletion is done in a background thread.
210 *
211 * @param context
212 */
213 static void deleteWorkaroundLibrariesAsynchronously(Context context) {
214 // Child process should not reach here.
215 final File libDir = getWorkaroundLibDir(context);
216 if (libDir.exists()) {
217 assert libDir.isDirectory();
218 // Async deletion
219 new Thread() {
220 @Override
221 public void run() {
222 deleteDirectorySync(libDir);
223 }
224 }.start();
225 }
226 }
227
228 /**
229 * Delete the workaround libraries and directory synchronously.
230 * For testing purpose only.
231 * @param context
232 */
233 public static void deleteWorkaroundLibrariesSynchronously(Context context) {
234 File libDir = getWorkaroundLibDir(context);
235 if (libDir.exists()) {
236 deleteDirectorySync(libDir);
237 }
238 }
239
240 private static void deleteDirectorySync(File dir) {
241 try {
242 File[] files = dir.listFiles();
243 if (files != null) {
244 for (File file : files) {
245 String fileName = file.getName();
246 if (!file.delete()) {
247 Log.e(TAG, "Failed to remove " + file.getAbsolutePath());
248 }
249 }
250 }
251 if (!dir.delete()) {
252 Log.w(TAG, "Failed to remove " + dir.getAbsolutePath());
253 }
254 return;
255 } catch (Exception e) {
256 Log.e(TAG, "Failed to remove old libs, ", e);
257 }
258 }
259 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698