• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.os.incremental;
18 
19 import android.annotation.IntDef;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.annotation.SystemService;
23 import android.content.Context;
24 import android.content.pm.DataLoaderParams;
25 import android.content.pm.IDataLoaderStatusListener;
26 import android.os.RemoteException;
27 import android.util.SparseArray;
28 
29 import com.android.internal.annotations.GuardedBy;
30 
31 import java.io.File;
32 import java.io.IOException;
33 import java.lang.annotation.Retention;
34 import java.lang.annotation.RetentionPolicy;
35 import java.nio.file.FileVisitResult;
36 import java.nio.file.Files;
37 import java.nio.file.Path;
38 import java.nio.file.Paths;
39 import java.nio.file.SimpleFileVisitor;
40 import java.nio.file.attribute.BasicFileAttributes;
41 
42 /**
43  * Provides operations to open or create an IncrementalStorage, using IIncrementalService
44  * service. Example Usage:
45  *
46  * <blockquote><pre>
47  * IncrementalManager manager = (IncrementalManager) getSystemService(Context.INCREMENTAL_SERVICE);
48  * IncrementalStorage storage = manager.openStorage("/path/to/incremental/dir");
49  * </pre></blockquote>
50  *
51  * @hide
52  */
53 @SystemService(Context.INCREMENTAL_SERVICE)
54 public final class IncrementalManager {
55     private static final String TAG = "IncrementalManager";
56 
57     private static final String ALLOWED_PROPERTY = "incremental.allowed";
58 
59     public static final int CREATE_MODE_TEMPORARY_BIND =
60             IIncrementalService.CREATE_MODE_TEMPORARY_BIND;
61     public static final int CREATE_MODE_PERMANENT_BIND =
62             IIncrementalService.CREATE_MODE_PERMANENT_BIND;
63     public static final int CREATE_MODE_CREATE =
64             IIncrementalService.CREATE_MODE_CREATE;
65     public static final int CREATE_MODE_OPEN_EXISTING =
66             IIncrementalService.CREATE_MODE_OPEN_EXISTING;
67 
68     @Retention(RetentionPolicy.SOURCE)
69     @IntDef(prefix = {"CREATE_MODE_"}, value = {
70             CREATE_MODE_TEMPORARY_BIND,
71             CREATE_MODE_PERMANENT_BIND,
72             CREATE_MODE_CREATE,
73             CREATE_MODE_OPEN_EXISTING,
74     })
75     public @interface CreateMode {
76     }
77 
78     private final @Nullable IIncrementalService mService;
79     @GuardedBy("mStorages")
80     private final SparseArray<IncrementalStorage> mStorages = new SparseArray<>();
81 
IncrementalManager(IIncrementalService service)82     public IncrementalManager(IIncrementalService service) {
83         mService = service;
84     }
85 
86     /**
87      * Returns a storage object given a storage ID.
88      *
89      * @param storageId The storage ID to identify the storage object.
90      * @return IncrementalStorage object corresponding to storage ID.
91      */
92     // TODO(b/136132412): remove this
93     @Nullable
getStorage(int storageId)94     public IncrementalStorage getStorage(int storageId) {
95         synchronized (mStorages) {
96             return mStorages.get(storageId);
97         }
98     }
99 
100     /**
101      * Opens or create an Incremental File System mounted directory and returns an
102      * IncrementalStorage object.
103      *
104      * @param path                Absolute path to mount Incremental File System on.
105      * @param params              IncrementalDataLoaderParams object to configure data loading.
106      * @param createMode          Mode for opening an old Incremental File System mount or creating
107      *                            a new mount.
108      * @param autoStartDataLoader Set true to immediately start data loader after creating storage.
109      * @return IncrementalStorage object corresponding to the mounted directory.
110      */
111     @Nullable
createStorage(@onNull String path, @NonNull DataLoaderParams params, @CreateMode int createMode, boolean autoStartDataLoader, @Nullable IDataLoaderStatusListener statusListener, @Nullable StorageHealthCheckParams healthCheckParams, @Nullable IStorageHealthListener healthListener)112     public IncrementalStorage createStorage(@NonNull String path,
113             @NonNull DataLoaderParams params,
114             @CreateMode int createMode,
115             boolean autoStartDataLoader,
116             @Nullable IDataLoaderStatusListener statusListener,
117             @Nullable StorageHealthCheckParams healthCheckParams,
118             @Nullable IStorageHealthListener healthListener) {
119         try {
120             final int id = mService.createStorage(path, params.getData(), createMode,
121                     statusListener, healthCheckParams, healthListener);
122             if (id < 0) {
123                 return null;
124             }
125             final IncrementalStorage storage = new IncrementalStorage(mService, id);
126             synchronized (mStorages) {
127                 mStorages.put(id, storage);
128             }
129             if (autoStartDataLoader) {
130                 storage.startLoading();
131             }
132             return storage;
133         } catch (RemoteException e) {
134             throw e.rethrowFromSystemServer();
135         }
136     }
137 
138     /**
139      * Opens an existing Incremental File System mounted directory and returns an IncrementalStorage
140      * object.
141      *
142      * @param path Absolute target path that Incremental File System has been mounted on.
143      * @return IncrementalStorage object corresponding to the mounted directory.
144      */
145     @Nullable
openStorage(@onNull String path)146     public IncrementalStorage openStorage(@NonNull String path) {
147         try {
148             final int id = mService.openStorage(path);
149             if (id < 0) {
150                 return null;
151             }
152             final IncrementalStorage storage = new IncrementalStorage(mService, id);
153             synchronized (mStorages) {
154                 mStorages.put(id, storage);
155             }
156             return storage;
157         } catch (RemoteException e) {
158             throw e.rethrowFromSystemServer();
159         }
160     }
161 
162     /**
163      * Opens or creates an IncrementalStorage that is linked to another IncrementalStorage.
164      *
165      * @return IncrementalStorage object corresponding to the linked storage.
166      */
167     @Nullable
createStorage(@onNull String path, @NonNull IncrementalStorage linkedStorage, @CreateMode int createMode)168     public IncrementalStorage createStorage(@NonNull String path,
169             @NonNull IncrementalStorage linkedStorage, @CreateMode int createMode) {
170         try {
171             final int id = mService.createLinkedStorage(
172                     path, linkedStorage.getId(), createMode);
173             if (id < 0) {
174                 return null;
175             }
176             final IncrementalStorage storage = new IncrementalStorage(mService, id);
177             synchronized (mStorages) {
178                 mStorages.put(id, storage);
179             }
180             return storage;
181         } catch (RemoteException e) {
182             throw e.rethrowFromSystemServer();
183         }
184     }
185 
186     /**
187      * Set up an app's code path. The expected outcome of this method is:
188      * 1) The actual apk directory under /data/incremental is bind-mounted to the parent directory
189      * of {@code afterCodeFile}.
190      * 2) All the files under {@code beforeCodeFile} will show up under {@code afterCodeFile}.
191      *
192      * @param beforeCodeFile Path that is currently bind-mounted and have APKs under it.
193      *                       Should no longer have any APKs after this method is called.
194      *                       Example: /data/app/vmdl*tmp
195      * @param afterCodeFile Path that should will have APKs after this method is called. Its parent
196      *                      directory should be bind-mounted to a directory under /data/incremental.
197      *                      Example: /data/app/~~[randomStringA]/[packageName]-[randomStringB]
198      * @throws IllegalArgumentException
199      * @throws IOException
200      * TODO(b/147371381): add unit tests
201      */
renameCodePath(File beforeCodeFile, File afterCodeFile)202     public void renameCodePath(File beforeCodeFile, File afterCodeFile)
203             throws IllegalArgumentException, IOException {
204         final File beforeCodeAbsolute = beforeCodeFile.getAbsoluteFile();
205         final IncrementalStorage apkStorage = openStorage(beforeCodeAbsolute.toString());
206         if (apkStorage == null) {
207             throw new IllegalArgumentException("Not an Incremental path: " + beforeCodeAbsolute);
208         }
209         final String targetStorageDir = afterCodeFile.getAbsoluteFile().getParent();
210         final IncrementalStorage linkedApkStorage =
211                 createStorage(targetStorageDir, apkStorage,
212                         IncrementalManager.CREATE_MODE_CREATE
213                                 | IncrementalManager.CREATE_MODE_PERMANENT_BIND);
214         if (linkedApkStorage == null) {
215             throw new IOException("Failed to create linked storage at dir: " + targetStorageDir);
216         }
217         try {
218             final String afterCodePathName = afterCodeFile.getName();
219             linkFiles(apkStorage, beforeCodeAbsolute, "", linkedApkStorage, afterCodePathName);
220             apkStorage.unBind(beforeCodeAbsolute.toString());
221         } catch (Exception e) {
222             linkedApkStorage.unBind(targetStorageDir);
223             throw e;
224         }
225     }
226 
227     /**
228      * Recursively set up directories and link all the files from source storage to target storage.
229      *
230      * @param sourceStorage The storage that has all the files and directories underneath.
231      * @param sourceAbsolutePath The absolute path of the directory that holds all files and dirs.
232      * @param sourceRelativePath The relative path on the source directory, e.g., "" or "lib".
233      * @param targetStorage The target storage that will have the same files and directories.
234      * @param targetRelativePath The relative path to the directory on the target storage that
235      *                           should have all the files and dirs underneath,
236      *                           e.g., "packageName-random".
237      * @throws IOException When makeDirectory or makeLink fails on the Incremental File System.
238      */
linkFiles(IncrementalStorage sourceStorage, File sourceAbsolutePath, String sourceRelativePath, IncrementalStorage targetStorage, String targetRelativePath)239     private void linkFiles(IncrementalStorage sourceStorage, File sourceAbsolutePath,
240             String sourceRelativePath, IncrementalStorage targetStorage,
241             String targetRelativePath) throws IOException {
242         final Path sourceBase = sourceAbsolutePath.toPath().resolve(sourceRelativePath);
243         final Path targetRelative = Paths.get(targetRelativePath);
244         Files.walkFileTree(sourceAbsolutePath.toPath(), new SimpleFileVisitor<Path>() {
245             @Override
246             public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
247                     throws IOException {
248                 final Path relativeDir = sourceBase.relativize(dir);
249                 targetStorage.makeDirectory(targetRelative.resolve(relativeDir).toString());
250                 return FileVisitResult.CONTINUE;
251             }
252 
253             @Override
254             public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
255                     throws IOException {
256                 final Path relativeFile = sourceBase.relativize(file);
257                 sourceStorage.makeLink(
258                         file.toAbsolutePath().toString(), targetStorage,
259                         targetRelative.resolve(relativeFile).toString());
260                 return FileVisitResult.CONTINUE;
261             }
262         });
263     }
264 
265     /**
266      * Closes a storage specified by the absolute path. If the path is not Incremental, do nothing.
267      * Unbinds the target dir and deletes the corresponding storage instance.
268      */
closeStorage(@onNull String path)269     public void closeStorage(@NonNull String path) {
270         try {
271             final int id = mService.openStorage(path);
272             if (id < 0) {
273                 return;
274             }
275             mService.deleteStorage(id);
276             synchronized (mStorages) {
277                 mStorages.remove(id);
278             }
279         } catch (RemoteException e) {
280             throw e.rethrowFromSystemServer();
281         }
282     }
283 
284     /**
285      * Checks if Incremental feature is enabled on this device.
286      */
isFeatureEnabled()287     public static boolean isFeatureEnabled() {
288         return nativeIsEnabled();
289     }
290 
291     /**
292      * Checks if Incremental installations are allowed.
293      * A developer can disable Incremental installations by setting the property.
294      */
isAllowed()295     public static boolean isAllowed() {
296         return isFeatureEnabled() && android.os.SystemProperties.getBoolean(ALLOWED_PROPERTY, true);
297     }
298 
299     /**
300      * Checks if path is mounted on Incremental File System.
301      */
isIncrementalPath(@onNull String path)302     public static boolean isIncrementalPath(@NonNull String path) {
303         return nativeIsIncrementalPath(path);
304     }
305 
306     /**
307      * Returns raw signature for file if it's on Incremental File System.
308      * Unsafe, use only if you are sure what you are doing.
309      */
unsafeGetFileSignature(@onNull String path)310     public static @Nullable byte[] unsafeGetFileSignature(@NonNull String path) {
311         return nativeUnsafeGetFileSignature(path);
312     }
313 
314     /* Native methods */
nativeIsEnabled()315     private static native boolean nativeIsEnabled();
nativeIsIncrementalPath(@onNull String path)316     private static native boolean nativeIsIncrementalPath(@NonNull String path);
nativeUnsafeGetFileSignature(@onNull String path)317     private static native byte[] nativeUnsafeGetFileSignature(@NonNull String path);
318 }
319