• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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 com.android.internal.content.om;
18 
19 import static android.content.Context.MODE_PRIVATE;
20 import static android.content.om.OverlayManagerTransaction.Request.BUNDLE_FABRICATED_OVERLAY;
21 import static android.content.om.OverlayManagerTransaction.Request.TYPE_REGISTER_FABRICATED;
22 import static android.content.om.OverlayManagerTransaction.Request.TYPE_UNREGISTER_FABRICATED;
23 
24 import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
25 import static com.android.internal.content.om.OverlayConfig.DEFAULT_PRIORITY;
26 
27 import android.annotation.NonNull;
28 import android.annotation.NonUiContext;
29 import android.content.Context;
30 import android.content.om.OverlayIdentifier;
31 import android.content.om.OverlayInfo;
32 import android.content.om.OverlayManagerTransaction;
33 import android.content.om.OverlayManagerTransaction.Request;
34 import android.content.pm.ApplicationInfo;
35 import android.content.pm.PackageManager;
36 import android.content.pm.parsing.FrameworkParsingPackageUtils;
37 import android.content.res.AssetManager;
38 import android.content.res.Flags;
39 import android.os.FabricatedOverlayInfo;
40 import android.os.FabricatedOverlayInternal;
41 import android.os.FabricatedOverlayInternalEntry;
42 import android.os.FileUtils;
43 import android.os.Process;
44 import android.os.UserHandle;
45 import android.text.TextUtils;
46 import android.util.Log;
47 
48 import com.android.internal.annotations.VisibleForTesting;
49 import com.android.internal.util.Preconditions;
50 
51 import java.io.File;
52 import java.io.IOException;
53 import java.nio.file.FileVisitResult;
54 import java.nio.file.Files;
55 import java.nio.file.Path;
56 import java.nio.file.SimpleFileVisitor;
57 import java.nio.file.attribute.BasicFileAttributes;
58 import java.util.ArrayList;
59 import java.util.Iterator;
60 import java.util.List;
61 import java.util.Objects;
62 
63 /**
64  * This class provides the functionalities for managing self-targeting overlays, including
65  * registering an overlay, unregistering an overlay, and getting the list of overlays information.
66  */
67 public class OverlayManagerImpl {
68     private static final String TAG = "OverlayManagerImpl";
69     private static final boolean DEBUG = false;
70 
71     private static final String FRRO_EXTENSION = ".frro";
72 
73     private static final String IDMAP_EXTENSION = ".idmap";
74 
75     @VisibleForTesting(visibility = PRIVATE)
76     public static final String SELF_TARGET = ".self_target";
77 
78     @NonNull
79     private final Context mContext;
80     private Path mBasePath;
81 
82     /**
83      * Init a OverlayManagerImpl by the context.
84      *
85      * @param context the context to create overlay environment
86      */
OverlayManagerImpl(@onNull Context context)87     public OverlayManagerImpl(@NonNull Context context) {
88         mContext = Objects.requireNonNull(context);
89 
90         if (!Process.myUserHandle().equals(context.getUser())) {
91             throw new SecurityException("Self-Targeting doesn't support multiple user now!");
92         }
93     }
94 
cleanExpiredOverlays(Path selfTargetingBasePath, Path folderForCurrentBaseApk)95     private static void cleanExpiredOverlays(Path selfTargetingBasePath,
96             Path folderForCurrentBaseApk) {
97         try {
98             final String currentBaseFolder = folderForCurrentBaseApk.toString();
99             final String selfTargetingDir = selfTargetingBasePath.getFileName().toString();
100             Files.walkFileTree(
101                     selfTargetingBasePath,
102                     new SimpleFileVisitor<>() {
103                         @Override
104                         public FileVisitResult preVisitDirectory(Path dir,
105                                                                  BasicFileAttributes attrs)
106                                 throws IOException {
107                             final String fileName = dir.getFileName().toString();
108                             return fileName.equals(currentBaseFolder)
109                                     ? FileVisitResult.SKIP_SUBTREE
110                                     : super.preVisitDirectory(dir, attrs);
111                         }
112 
113                         @Override
114                         public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
115                                 throws IOException {
116                             if (!file.toFile().delete()) {
117                                 Log.w(TAG, "Failed to delete file " + file);
118                             }
119                             return super.visitFile(file, attrs);
120                         }
121 
122                         @Override
123                         public FileVisitResult postVisitDirectory(Path dir, IOException exc)
124                                 throws IOException {
125                             final String fileName = dir.getFileName().toString();
126                             if (!fileName.equals(currentBaseFolder)
127                                     && !fileName.equals(selfTargetingDir)) {
128                                 if (!dir.toFile().delete()) {
129                                     Log.w(TAG, "Failed to delete dir " + dir);
130                                 }
131                             }
132                             return super.postVisitDirectory(dir, exc);
133                         }
134                     });
135         } catch (IOException e) {
136             Log.w(TAG, "Unknown fail " + e);
137         }
138     }
139 
140     /** Ensure the base dir for self-targeting is valid. */
141     @VisibleForTesting
142     @NonUiContext
ensureBaseDir()143     public void ensureBaseDir() {
144         final String baseApkPath = mContext.getApplicationInfo().getBaseCodePath();
145         final Path baseApkFolderName = Path.of(baseApkPath).getParent().getFileName();
146         final File selfTargetingBaseFile = mContext.getDir(SELF_TARGET, MODE_PRIVATE);
147         Preconditions.checkArgument(
148                 selfTargetingBaseFile.isDirectory()
149                         && selfTargetingBaseFile.exists()
150                         && selfTargetingBaseFile.canWrite()
151                         && selfTargetingBaseFile.canRead()
152                         && selfTargetingBaseFile.canExecute(),
153                 "Can't work for this context");
154         cleanExpiredOverlays(selfTargetingBaseFile.toPath(), baseApkFolderName);
155 
156         final File baseFile = new File(selfTargetingBaseFile, baseApkFolderName.toString());
157         if (!baseFile.exists()) {
158             if (!baseFile.mkdirs()) {
159                 Log.w(TAG, "Failed to create directory " + baseFile);
160             }
161 
162             // It fails to create frro and idmap files without this setting.
163             FileUtils.setPermissions(
164                     baseFile,
165                     FileUtils.S_IRWXU,
166                     -1 /* uid unchanged */,
167                     -1 /* gid unchanged */);
168         }
169         Preconditions.checkArgument(
170                 baseFile.isDirectory()
171                         && baseFile.exists()
172                         && baseFile.canWrite()
173                         && baseFile.canRead()
174                         && baseFile.canExecute(), // 'list' capability
175                 "Can't create a workspace for this context");
176 
177         mBasePath = baseFile.toPath();
178     }
179 
isSameWithTargetSignature(final String targetPackage)180     private boolean isSameWithTargetSignature(final String targetPackage) {
181         final PackageManager packageManager = mContext.getPackageManager();
182         final String packageName = mContext.getPackageName();
183         if (TextUtils.equals(packageName, targetPackage)) {
184             return true;
185         }
186         return packageManager.checkSignatures(packageName, targetPackage)
187                 == PackageManager.SIGNATURE_MATCH;
188     }
189 
190     /**
191      * Check if the overlay name is valid or not.
192      *
193      * @param name the non-check overlay name
194      * @return the valid overlay name
195      */
checkOverlayNameValid(@onNull String name)196     public static String checkOverlayNameValid(@NonNull String name) {
197         final String overlayName =
198                 Preconditions.checkStringNotEmpty(
199                         name, "overlayName should be neither empty nor null string");
200         final String checkOverlayNameResult =
201                 FrameworkParsingPackageUtils.validateName(
202                         overlayName, false /* requireSeparator */, true /* requireFilename */);
203         Preconditions.checkArgument(
204                 checkOverlayNameResult == null,
205                 TextUtils.formatSimple(
206                         "Invalid overlayName \"%s\". The check result is %s.",
207                         overlayName, checkOverlayNameResult));
208         return overlayName;
209     }
210 
checkPackageName(@onNull String packageName)211     private void checkPackageName(@NonNull String packageName) {
212         Preconditions.checkStringNotEmpty(packageName);
213         Preconditions.checkArgument(
214                 TextUtils.equals(mContext.getPackageName(), packageName),
215                 TextUtils.formatSimple(
216                         "UID %d doesn't own the package %s", Process.myUid(), packageName));
217     }
218 
219     /**
220      * Save FabricatedOverlay instance as frro and idmap files.
221      *
222      * <p>In order to fill the overlayable policy, it's necessary to collect the information from
223      * app. And then, the information is passed to native layer to fill the overlayable policy
224      *
225      * @param overlayInternal the FabricatedOverlayInternal to be saved.
226      */
227     @NonUiContext
registerFabricatedOverlay(@onNull FabricatedOverlayInternal overlayInternal)228     public void registerFabricatedOverlay(@NonNull FabricatedOverlayInternal overlayInternal)
229             throws IOException, PackageManager.NameNotFoundException {
230         ensureBaseDir();
231         Objects.requireNonNull(overlayInternal);
232         final List<FabricatedOverlayInternalEntry> entryList =
233                 Objects.requireNonNull(overlayInternal.entries);
234         Preconditions.checkArgument(!entryList.isEmpty(), "overlay entries shouldn't be empty");
235         final String overlayName = checkOverlayNameValid(overlayInternal.overlayName);
236         checkPackageName(overlayInternal.packageName);
237         if (Flags.selfTargetingAndroidResourceFrro()) {
238             Preconditions.checkStringNotEmpty(overlayInternal.targetPackageName);
239         } else {
240             checkPackageName(overlayInternal.targetPackageName);
241             Preconditions.checkStringNotEmpty(
242                     overlayInternal.targetOverlayable,
243                     "Target overlayable should be neither null nor empty string.");
244         }
245 
246         final ApplicationInfo applicationInfo = mContext.getApplicationInfo();
247         String targetPackage = null;
248         if (Flags.selfTargetingAndroidResourceFrro() && TextUtils.equals(
249                 overlayInternal.targetPackageName, "android")) {
250             targetPackage = AssetManager.FRAMEWORK_APK_PATH;
251         } else {
252             targetPackage = Preconditions.checkStringNotEmpty(
253                     applicationInfo.getBaseCodePath());
254         }
255         final Path frroPath = mBasePath.resolve(overlayName + FRRO_EXTENSION);
256         final Path idmapPath = mBasePath.resolve(overlayName + IDMAP_EXTENSION);
257 
258         createFrroFile(frroPath.toString(), overlayInternal);
259         try {
260             createIdmapFile(
261                     targetPackage,
262                     frroPath.toString(),
263                     idmapPath.toString(),
264                     overlayName,
265                     applicationInfo.isSystemApp() || applicationInfo.isSystemExt() /* isSystem */,
266                     applicationInfo.isVendor(),
267                     applicationInfo.isProduct(),
268                     isSameWithTargetSignature(overlayInternal.targetPackageName),
269                     applicationInfo.isOdm(),
270                     applicationInfo.isOem());
271         } catch (IOException e) {
272             if (!frroPath.toFile().delete()) {
273                 Log.w(TAG, "Failed to delete file " + frroPath);
274             }
275             throw e;
276         }
277     }
278 
279     /**
280      * Remove the overlay with the specific name
281      *
282      * @param overlayName the specific name
283      */
284     @NonUiContext
unregisterFabricatedOverlay(@onNull String overlayName)285     public void unregisterFabricatedOverlay(@NonNull String overlayName) {
286         ensureBaseDir();
287         checkOverlayNameValid(overlayName);
288         final Path frroPath = mBasePath.resolve(overlayName + FRRO_EXTENSION);
289         final Path idmapPath = mBasePath.resolve(overlayName + IDMAP_EXTENSION);
290 
291         if (!frroPath.toFile().delete()) {
292             Log.w(TAG, "Failed to delete file " + frroPath);
293         }
294         if (!idmapPath.toFile().delete()) {
295             Log.w(TAG, "Failed to delete file " + idmapPath);
296         }
297     }
298 
299     /**
300      * Commit the overlay manager transaction
301      *
302      * @param transaction the overlay manager transaction
303      */
304     @NonUiContext
commit(@onNull OverlayManagerTransaction transaction)305     public void commit(@NonNull OverlayManagerTransaction transaction)
306             throws PackageManager.NameNotFoundException, IOException {
307         Objects.requireNonNull(transaction);
308 
309         for (Iterator<Request> it = transaction.getRequests(); it.hasNext(); ) {
310             final Request request = it.next();
311             if (request.type == TYPE_REGISTER_FABRICATED) {
312                 final FabricatedOverlayInternal fabricatedOverlayInternal =
313                         Objects.requireNonNull(
314                                 request.extras.getParcelable(
315                                         BUNDLE_FABRICATED_OVERLAY,
316                                         FabricatedOverlayInternal.class));
317 
318                 // populate the mandatory data
319                 if (TextUtils.isEmpty(fabricatedOverlayInternal.packageName)) {
320                     fabricatedOverlayInternal.packageName = mContext.getPackageName();
321                 } else {
322                     if (!TextUtils.equals(
323                             fabricatedOverlayInternal.packageName, mContext.getPackageName())) {
324                         throw new IllegalArgumentException("Unknown package name in transaction");
325                     }
326                 }
327 
328                 registerFabricatedOverlay(fabricatedOverlayInternal);
329             } else if (request.type == TYPE_UNREGISTER_FABRICATED) {
330                 final OverlayIdentifier overlayIdentifier = Objects.requireNonNull(request.overlay);
331                 unregisterFabricatedOverlay(overlayIdentifier.getOverlayName());
332             } else {
333                 throw new IllegalArgumentException("Unknown request in transaction " + request);
334             }
335         }
336     }
337 
338     /**
339      * Get the list of overlays information for the target package name.
340      *
341      * @param targetPackage the target package name
342      * @return the list of overlays information.
343      */
344     @NonNull
getOverlayInfosForTarget(@onNull String targetPackage)345     public List<OverlayInfo> getOverlayInfosForTarget(@NonNull String targetPackage) {
346         ensureBaseDir();
347 
348         final File base = mBasePath.toFile();
349         final File[] frroFiles = base.listFiles((dir, name) -> {
350             if (!name.endsWith(FRRO_EXTENSION)) {
351                 return false;
352             }
353 
354             final String idmapFileName = name.substring(0, name.length() - FRRO_EXTENSION.length())
355                     + IDMAP_EXTENSION;
356             final File idmapFile = new File(dir, idmapFileName);
357             return idmapFile.exists();
358         });
359 
360         final ArrayList<OverlayInfo> overlayInfos = new ArrayList<>();
361         for (File file : frroFiles) {
362             final FabricatedOverlayInfo fabricatedOverlayInfo;
363             try {
364                 fabricatedOverlayInfo = getFabricatedOverlayInfo(file.getAbsolutePath());
365             } catch (IOException e) {
366                 Log.w(TAG, "can't load " + file);
367                 continue;
368             }
369             if (!TextUtils.equals(targetPackage, fabricatedOverlayInfo.targetPackageName)) {
370                 continue;
371             }
372             if (DEBUG) {
373                 Log.i(TAG, "load " + file);
374             }
375 
376             final OverlayInfo overlayInfo =
377                     new OverlayInfo(
378                             fabricatedOverlayInfo.packageName,
379                             fabricatedOverlayInfo.overlayName,
380                             fabricatedOverlayInfo.targetPackageName,
381                             fabricatedOverlayInfo.targetOverlayable,
382                             null,
383                             file.getAbsolutePath(),
384                             OverlayInfo.STATE_ENABLED,
385                             UserHandle.myUserId(),
386                             DEFAULT_PRIORITY,
387                             true /* isMutable */,
388                             true /* isFabricated */);
389             overlayInfos.add(overlayInfo);
390         }
391         return overlayInfos;
392     }
393 
createFrroFile( @onNull String frroFile, @NonNull FabricatedOverlayInternal fabricatedOverlayInternal)394     private static native void createFrroFile(
395             @NonNull String frroFile, @NonNull FabricatedOverlayInternal fabricatedOverlayInternal)
396             throws IOException;
397 
createIdmapFile( @onNull String targetPath, @NonNull String overlayPath, @NonNull String idmapPath, @NonNull String overlayName, boolean isSystem, boolean isVendor, boolean isProduct, boolean isSameWithTargetSignature, boolean isOdm, boolean isOem)398     private static native void createIdmapFile(
399             @NonNull String targetPath,
400             @NonNull String overlayPath,
401             @NonNull String idmapPath,
402             @NonNull String overlayName,
403             boolean isSystem,
404             boolean isVendor,
405             boolean isProduct,
406             boolean isSameWithTargetSignature,
407             boolean isOdm,
408             boolean isOem)
409             throws IOException;
410 
getFabricatedOverlayInfo( @onNull String overlayPath)411     private static native FabricatedOverlayInfo getFabricatedOverlayInfo(
412             @NonNull String overlayPath) throws IOException;
413 }
414