• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 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.server.pm;
18 
19 import android.annotation.AppIdInt;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.annotation.UserIdInt;
23 import android.content.Context;
24 import android.content.pm.PackageStats;
25 import android.os.Build;
26 import android.os.IBinder;
27 import android.os.IBinder.DeathRecipient;
28 import android.os.IInstalld;
29 import android.os.RemoteException;
30 import android.os.ServiceManager;
31 import android.text.format.DateUtils;
32 import android.util.Slog;
33 
34 import com.android.internal.os.BackgroundThread;
35 import com.android.server.SystemService;
36 
37 import dalvik.system.VMRuntime;
38 
39 import java.io.FileDescriptor;
40 
41 public class Installer extends SystemService {
42     private static final String TAG = "Installer";
43 
44     /* ***************************************************************************
45      * IMPORTANT: These values are passed to native code. Keep them in sync with
46      * frameworks/native/cmds/installd/installd.h
47      * **************************************************************************/
48     /** Application should be visible to everyone */
49     public static final int DEXOPT_PUBLIC         = 1 << 1;
50     /** Application wants to allow debugging of its code */
51     public static final int DEXOPT_DEBUGGABLE     = 1 << 2;
52     /** The system boot has finished */
53     public static final int DEXOPT_BOOTCOMPLETE   = 1 << 3;
54     /** Hint that the dexopt type is profile-guided. */
55     public static final int DEXOPT_PROFILE_GUIDED = 1 << 4;
56     /** The compilation is for a secondary dex file. */
57     public static final int DEXOPT_SECONDARY_DEX  = 1 << 5;
58     /** Ignore the result of dexoptNeeded and force compilation. */
59     public static final int DEXOPT_FORCE          = 1 << 6;
60     /** Indicates that the dex file passed to dexopt in on CE storage. */
61     public static final int DEXOPT_STORAGE_CE     = 1 << 7;
62     /** Indicates that the dex file passed to dexopt in on DE storage. */
63     public static final int DEXOPT_STORAGE_DE     = 1 << 8;
64     /** Indicates that dexopt is invoked from the background service. */
65     public static final int DEXOPT_IDLE_BACKGROUND_JOB = 1 << 9;
66     /** Indicates that dexopt should restrict access to private APIs. */
67     public static final int DEXOPT_ENABLE_HIDDEN_API_CHECKS = 1 << 10;
68     /** Indicates that dexopt should convert to CompactDex. */
69     public static final int DEXOPT_GENERATE_COMPACT_DEX = 1 << 11;
70     /** Indicates that dexopt should generate an app image */
71     public static final int DEXOPT_GENERATE_APP_IMAGE = 1 << 12;
72 
73     // NOTE: keep in sync with installd
74     public static final int FLAG_CLEAR_CACHE_ONLY = 1 << 8;
75     public static final int FLAG_CLEAR_CODE_CACHE_ONLY = 1 << 9;
76     public static final int FLAG_USE_QUOTA = 1 << 12;
77     public static final int FLAG_FREE_CACHE_V2 = 1 << 13;
78     public static final int FLAG_FREE_CACHE_V2_DEFY_QUOTA = 1 << 14;
79     public static final int FLAG_FREE_CACHE_NOOP = 1 << 15;
80     public static final int FLAG_FORCE = 1 << 16;
81 
82     private final boolean mIsolated;
83 
84     private volatile IInstalld mInstalld;
85     private volatile Object mWarnIfHeld;
86 
Installer(Context context)87     public Installer(Context context) {
88         this(context, false);
89     }
90 
91     /**
92      * @param isolated indicates if this object should <em>not</em> connect to
93      *            the real {@code installd}. All remote calls will be ignored
94      *            unless you extend this class and intercept them.
95      */
Installer(Context context, boolean isolated)96     public Installer(Context context, boolean isolated) {
97         super(context);
98         mIsolated = isolated;
99     }
100 
101     /**
102      * Yell loudly if someone tries making future calls while holding a lock on
103      * the given object.
104      */
setWarnIfHeld(Object warnIfHeld)105     public void setWarnIfHeld(Object warnIfHeld) {
106         mWarnIfHeld = warnIfHeld;
107     }
108 
109     @Override
onStart()110     public void onStart() {
111         if (mIsolated) {
112             mInstalld = null;
113         } else {
114             connect();
115         }
116     }
117 
connect()118     private void connect() {
119         IBinder binder = ServiceManager.getService("installd");
120         if (binder != null) {
121             try {
122                 binder.linkToDeath(new DeathRecipient() {
123                     @Override
124                     public void binderDied() {
125                         Slog.w(TAG, "installd died; reconnecting");
126                         connect();
127                     }
128                 }, 0);
129             } catch (RemoteException e) {
130                 binder = null;
131             }
132         }
133 
134         if (binder != null) {
135             mInstalld = IInstalld.Stub.asInterface(binder);
136             try {
137                 invalidateMounts();
138             } catch (InstallerException ignored) {
139             }
140         } else {
141             Slog.w(TAG, "installd not found; trying again");
142             BackgroundThread.getHandler().postDelayed(() -> {
143                 connect();
144             }, DateUtils.SECOND_IN_MILLIS);
145         }
146     }
147 
148     /**
149      * Do several pre-flight checks before making a remote call.
150      *
151      * @return if the remote call should continue.
152      */
checkBeforeRemote()153     private boolean checkBeforeRemote() {
154         if (mWarnIfHeld != null && Thread.holdsLock(mWarnIfHeld)) {
155             Slog.wtf(TAG, "Calling thread " + Thread.currentThread().getName() + " is holding 0x"
156                     + Integer.toHexString(System.identityHashCode(mWarnIfHeld)), new Throwable());
157         }
158         if (mIsolated) {
159             Slog.i(TAG, "Ignoring request because this installer is isolated");
160             return false;
161         } else {
162             return true;
163         }
164     }
165 
createAppData(String uuid, String packageName, int userId, int flags, int appId, String seInfo, int targetSdkVersion)166     public long createAppData(String uuid, String packageName, int userId, int flags, int appId,
167             String seInfo, int targetSdkVersion) throws InstallerException {
168         if (!checkBeforeRemote()) return -1;
169         try {
170             return mInstalld.createAppData(uuid, packageName, userId, flags, appId, seInfo,
171                     targetSdkVersion);
172         } catch (Exception e) {
173             throw InstallerException.from(e);
174         }
175     }
176 
restoreconAppData(String uuid, String packageName, int userId, int flags, int appId, String seInfo)177     public void restoreconAppData(String uuid, String packageName, int userId, int flags, int appId,
178             String seInfo) throws InstallerException {
179         if (!checkBeforeRemote()) return;
180         try {
181             mInstalld.restoreconAppData(uuid, packageName, userId, flags, appId, seInfo);
182         } catch (Exception e) {
183             throw InstallerException.from(e);
184         }
185     }
186 
migrateAppData(String uuid, String packageName, int userId, int flags)187     public void migrateAppData(String uuid, String packageName, int userId, int flags)
188             throws InstallerException {
189         if (!checkBeforeRemote()) return;
190         try {
191             mInstalld.migrateAppData(uuid, packageName, userId, flags);
192         } catch (Exception e) {
193             throw InstallerException.from(e);
194         }
195     }
196 
clearAppData(String uuid, String packageName, int userId, int flags, long ceDataInode)197     public void clearAppData(String uuid, String packageName, int userId, int flags,
198             long ceDataInode) throws InstallerException {
199         if (!checkBeforeRemote()) return;
200         try {
201             mInstalld.clearAppData(uuid, packageName, userId, flags, ceDataInode);
202         } catch (Exception e) {
203             throw InstallerException.from(e);
204         }
205     }
206 
destroyAppData(String uuid, String packageName, int userId, int flags, long ceDataInode)207     public void destroyAppData(String uuid, String packageName, int userId, int flags,
208             long ceDataInode) throws InstallerException {
209         if (!checkBeforeRemote()) return;
210         try {
211             mInstalld.destroyAppData(uuid, packageName, userId, flags, ceDataInode);
212         } catch (Exception e) {
213             throw InstallerException.from(e);
214         }
215     }
216 
fixupAppData(String uuid, int flags)217     public void fixupAppData(String uuid, int flags) throws InstallerException {
218         if (!checkBeforeRemote()) return;
219         try {
220             mInstalld.fixupAppData(uuid, flags);
221         } catch (Exception e) {
222             throw InstallerException.from(e);
223         }
224     }
225 
moveCompleteApp(String fromUuid, String toUuid, String packageName, String dataAppName, int appId, String seInfo, int targetSdkVersion)226     public void moveCompleteApp(String fromUuid, String toUuid, String packageName,
227             String dataAppName, int appId, String seInfo, int targetSdkVersion)
228             throws InstallerException {
229         if (!checkBeforeRemote()) return;
230         try {
231             mInstalld.moveCompleteApp(fromUuid, toUuid, packageName, dataAppName, appId, seInfo,
232                     targetSdkVersion);
233         } catch (Exception e) {
234             throw InstallerException.from(e);
235         }
236     }
237 
getAppSize(String uuid, String[] packageNames, int userId, int flags, int appId, long[] ceDataInodes, String[] codePaths, PackageStats stats)238     public void getAppSize(String uuid, String[] packageNames, int userId, int flags, int appId,
239             long[] ceDataInodes, String[] codePaths, PackageStats stats)
240             throws InstallerException {
241         if (!checkBeforeRemote()) return;
242         try {
243             final long[] res = mInstalld.getAppSize(uuid, packageNames, userId, flags,
244                     appId, ceDataInodes, codePaths);
245             stats.codeSize += res[0];
246             stats.dataSize += res[1];
247             stats.cacheSize += res[2];
248             stats.externalCodeSize += res[3];
249             stats.externalDataSize += res[4];
250             stats.externalCacheSize += res[5];
251         } catch (Exception e) {
252             throw InstallerException.from(e);
253         }
254     }
255 
getUserSize(String uuid, int userId, int flags, int[] appIds, PackageStats stats)256     public void getUserSize(String uuid, int userId, int flags, int[] appIds, PackageStats stats)
257             throws InstallerException {
258         if (!checkBeforeRemote()) return;
259         try {
260             final long[] res = mInstalld.getUserSize(uuid, userId, flags, appIds);
261             stats.codeSize += res[0];
262             stats.dataSize += res[1];
263             stats.cacheSize += res[2];
264             stats.externalCodeSize += res[3];
265             stats.externalDataSize += res[4];
266             stats.externalCacheSize += res[5];
267         } catch (Exception e) {
268             throw InstallerException.from(e);
269         }
270     }
271 
getExternalSize(String uuid, int userId, int flags, int[] appIds)272     public long[] getExternalSize(String uuid, int userId, int flags, int[] appIds)
273             throws InstallerException {
274         if (!checkBeforeRemote()) return new long[6];
275         try {
276             return mInstalld.getExternalSize(uuid, userId, flags, appIds);
277         } catch (Exception e) {
278             throw InstallerException.from(e);
279         }
280     }
281 
setAppQuota(String uuid, int userId, int appId, long cacheQuota)282     public void setAppQuota(String uuid, int userId, int appId, long cacheQuota)
283             throws InstallerException {
284         if (!checkBeforeRemote()) return;
285         try {
286             mInstalld.setAppQuota(uuid, userId, appId, cacheQuota);
287         } catch (Exception e) {
288             throw InstallerException.from(e);
289         }
290     }
291 
dexopt(String apkPath, int uid, @Nullable String pkgName, String instructionSet, int dexoptNeeded, @Nullable String outputPath, int dexFlags, String compilerFilter, @Nullable String volumeUuid, @Nullable String sharedLibraries, @Nullable String seInfo, boolean downgrade, int targetSdkVersion, @Nullable String profileName, @Nullable String dexMetadataPath, @Nullable String compilationReason)292     public void dexopt(String apkPath, int uid, @Nullable String pkgName, String instructionSet,
293             int dexoptNeeded, @Nullable String outputPath, int dexFlags,
294             String compilerFilter, @Nullable String volumeUuid, @Nullable String sharedLibraries,
295             @Nullable String seInfo, boolean downgrade, int targetSdkVersion,
296             @Nullable String profileName, @Nullable String dexMetadataPath,
297             @Nullable String compilationReason) throws InstallerException {
298         assertValidInstructionSet(instructionSet);
299         if (!checkBeforeRemote()) return;
300         try {
301             mInstalld.dexopt(apkPath, uid, pkgName, instructionSet, dexoptNeeded, outputPath,
302                     dexFlags, compilerFilter, volumeUuid, sharedLibraries, seInfo, downgrade,
303                     targetSdkVersion, profileName, dexMetadataPath, compilationReason);
304         } catch (Exception e) {
305             throw InstallerException.from(e);
306         }
307     }
308 
mergeProfiles(int uid, String packageName, String profileName)309     public boolean mergeProfiles(int uid, String packageName, String profileName)
310             throws InstallerException {
311         if (!checkBeforeRemote()) return false;
312         try {
313             return mInstalld.mergeProfiles(uid, packageName, profileName);
314         } catch (Exception e) {
315             throw InstallerException.from(e);
316         }
317     }
318 
dumpProfiles(int uid, String packageName, String profileName, String codePath)319     public boolean dumpProfiles(int uid, String packageName, String profileName, String codePath)
320             throws InstallerException {
321         if (!checkBeforeRemote()) return false;
322         try {
323             return mInstalld.dumpProfiles(uid, packageName, profileName, codePath);
324         } catch (Exception e) {
325             throw InstallerException.from(e);
326         }
327     }
328 
copySystemProfile(String systemProfile, int uid, String packageName, String profileName)329     public boolean copySystemProfile(String systemProfile, int uid, String packageName,
330                 String profileName) throws InstallerException {
331         if (!checkBeforeRemote()) return false;
332         try {
333             return mInstalld.copySystemProfile(systemProfile, uid, packageName, profileName);
334         } catch (Exception e) {
335             throw InstallerException.from(e);
336         }
337     }
338 
idmap(String targetApkPath, String overlayApkPath, int uid)339     public void idmap(String targetApkPath, String overlayApkPath, int uid)
340             throws InstallerException {
341         if (!checkBeforeRemote()) return;
342         try {
343             mInstalld.idmap(targetApkPath, overlayApkPath, uid);
344         } catch (Exception e) {
345             throw InstallerException.from(e);
346         }
347     }
348 
removeIdmap(String overlayApkPath)349     public void removeIdmap(String overlayApkPath) throws InstallerException {
350         if (!checkBeforeRemote()) return;
351         try {
352             mInstalld.removeIdmap(overlayApkPath);
353         } catch (Exception e) {
354             throw InstallerException.from(e);
355         }
356     }
357 
rmdex(String codePath, String instructionSet)358     public void rmdex(String codePath, String instructionSet) throws InstallerException {
359         assertValidInstructionSet(instructionSet);
360         if (!checkBeforeRemote()) return;
361         try {
362             mInstalld.rmdex(codePath, instructionSet);
363         } catch (Exception e) {
364             throw InstallerException.from(e);
365         }
366     }
367 
rmPackageDir(String packageDir)368     public void rmPackageDir(String packageDir) throws InstallerException {
369         if (!checkBeforeRemote()) return;
370         try {
371             mInstalld.rmPackageDir(packageDir);
372         } catch (Exception e) {
373             throw InstallerException.from(e);
374         }
375     }
376 
clearAppProfiles(String packageName, String profileName)377     public void clearAppProfiles(String packageName, String profileName) throws InstallerException {
378         if (!checkBeforeRemote()) return;
379         try {
380             mInstalld.clearAppProfiles(packageName, profileName);
381         } catch (Exception e) {
382             throw InstallerException.from(e);
383         }
384     }
385 
destroyAppProfiles(String packageName)386     public void destroyAppProfiles(String packageName) throws InstallerException {
387         if (!checkBeforeRemote()) return;
388         try {
389             mInstalld.destroyAppProfiles(packageName);
390         } catch (Exception e) {
391             throw InstallerException.from(e);
392         }
393     }
394 
createUserData(String uuid, int userId, int userSerial, int flags)395     public void createUserData(String uuid, int userId, int userSerial, int flags)
396             throws InstallerException {
397         if (!checkBeforeRemote()) return;
398         try {
399             mInstalld.createUserData(uuid, userId, userSerial, flags);
400         } catch (Exception e) {
401             throw InstallerException.from(e);
402         }
403     }
404 
destroyUserData(String uuid, int userId, int flags)405     public void destroyUserData(String uuid, int userId, int flags) throws InstallerException {
406         if (!checkBeforeRemote()) return;
407         try {
408             mInstalld.destroyUserData(uuid, userId, flags);
409         } catch (Exception e) {
410             throw InstallerException.from(e);
411         }
412     }
413 
markBootComplete(String instructionSet)414     public void markBootComplete(String instructionSet) throws InstallerException {
415         assertValidInstructionSet(instructionSet);
416         if (!checkBeforeRemote()) return;
417         try {
418             mInstalld.markBootComplete(instructionSet);
419         } catch (Exception e) {
420             throw InstallerException.from(e);
421         }
422     }
423 
freeCache(String uuid, long targetFreeBytes, long cacheReservedBytes, int flags)424     public void freeCache(String uuid, long targetFreeBytes, long cacheReservedBytes, int flags)
425             throws InstallerException {
426         if (!checkBeforeRemote()) return;
427         try {
428             mInstalld.freeCache(uuid, targetFreeBytes, cacheReservedBytes, flags);
429         } catch (Exception e) {
430             throw InstallerException.from(e);
431         }
432     }
433 
434     /**
435      * Links the 32 bit native library directory in an application's data
436      * directory to the real location for backward compatibility. Note that no
437      * such symlink is created for 64 bit shared libraries.
438      */
linkNativeLibraryDirectory(String uuid, String packageName, String nativeLibPath32, int userId)439     public void linkNativeLibraryDirectory(String uuid, String packageName, String nativeLibPath32,
440             int userId) throws InstallerException {
441         if (!checkBeforeRemote()) return;
442         try {
443             mInstalld.linkNativeLibraryDirectory(uuid, packageName, nativeLibPath32, userId);
444         } catch (Exception e) {
445             throw InstallerException.from(e);
446         }
447     }
448 
createOatDir(String oatDir, String dexInstructionSet)449     public void createOatDir(String oatDir, String dexInstructionSet)
450             throws InstallerException {
451         if (!checkBeforeRemote()) return;
452         try {
453             mInstalld.createOatDir(oatDir, dexInstructionSet);
454         } catch (Exception e) {
455             throw InstallerException.from(e);
456         }
457     }
458 
linkFile(String relativePath, String fromBase, String toBase)459     public void linkFile(String relativePath, String fromBase, String toBase)
460             throws InstallerException {
461         if (!checkBeforeRemote()) return;
462         try {
463             mInstalld.linkFile(relativePath, fromBase, toBase);
464         } catch (Exception e) {
465             throw InstallerException.from(e);
466         }
467     }
468 
moveAb(String apkPath, String instructionSet, String outputPath)469     public void moveAb(String apkPath, String instructionSet, String outputPath)
470             throws InstallerException {
471         if (!checkBeforeRemote()) return;
472         try {
473             mInstalld.moveAb(apkPath, instructionSet, outputPath);
474         } catch (Exception e) {
475             throw InstallerException.from(e);
476         }
477     }
478 
deleteOdex(String apkPath, String instructionSet, String outputPath)479     public void deleteOdex(String apkPath, String instructionSet, String outputPath)
480             throws InstallerException {
481         if (!checkBeforeRemote()) return;
482         try {
483             mInstalld.deleteOdex(apkPath, instructionSet, outputPath);
484         } catch (Exception e) {
485             throw InstallerException.from(e);
486         }
487     }
488 
installApkVerity(String filePath, FileDescriptor verityInput, int contentSize)489     public void installApkVerity(String filePath, FileDescriptor verityInput, int contentSize)
490             throws InstallerException {
491         if (!checkBeforeRemote()) return;
492         try {
493             mInstalld.installApkVerity(filePath, verityInput, contentSize);
494         } catch (Exception e) {
495             throw InstallerException.from(e);
496         }
497     }
498 
assertFsverityRootHashMatches(String filePath, @NonNull byte[] expectedHash)499     public void assertFsverityRootHashMatches(String filePath, @NonNull byte[] expectedHash)
500             throws InstallerException {
501         if (!checkBeforeRemote()) return;
502         try {
503             mInstalld.assertFsverityRootHashMatches(filePath, expectedHash);
504         } catch (Exception e) {
505             throw InstallerException.from(e);
506         }
507     }
508 
reconcileSecondaryDexFile(String apkPath, String packageName, int uid, String[] isas, @Nullable String volumeUuid, int flags)509     public boolean reconcileSecondaryDexFile(String apkPath, String packageName, int uid,
510             String[] isas, @Nullable String volumeUuid, int flags) throws InstallerException {
511         for (int i = 0; i < isas.length; i++) {
512             assertValidInstructionSet(isas[i]);
513         }
514         if (!checkBeforeRemote()) return false;
515         try {
516             return mInstalld.reconcileSecondaryDexFile(apkPath, packageName, uid, isas,
517                     volumeUuid, flags);
518         } catch (Exception e) {
519             throw InstallerException.from(e);
520         }
521     }
522 
hashSecondaryDexFile(String dexPath, String packageName, int uid, @Nullable String volumeUuid, int flags)523     public byte[] hashSecondaryDexFile(String dexPath, String packageName, int uid,
524             @Nullable String volumeUuid, int flags) throws InstallerException {
525         if (!checkBeforeRemote()) return new byte[0];
526         try {
527             return mInstalld.hashSecondaryDexFile(dexPath, packageName, uid, volumeUuid, flags);
528         } catch (Exception e) {
529             throw InstallerException.from(e);
530         }
531     }
532 
createProfileSnapshot(int appId, String packageName, String profileName, String classpath)533     public boolean createProfileSnapshot(int appId, String packageName, String profileName,
534             String classpath) throws InstallerException {
535         if (!checkBeforeRemote()) return false;
536         try {
537             return mInstalld.createProfileSnapshot(appId, packageName, profileName, classpath);
538         } catch (Exception e) {
539             throw InstallerException.from(e);
540         }
541     }
542 
destroyProfileSnapshot(String packageName, String profileName)543     public void destroyProfileSnapshot(String packageName, String profileName)
544             throws InstallerException {
545         if (!checkBeforeRemote()) return;
546         try {
547             mInstalld.destroyProfileSnapshot(packageName, profileName);
548         } catch (Exception e) {
549             throw InstallerException.from(e);
550         }
551     }
552 
invalidateMounts()553     public void invalidateMounts() throws InstallerException {
554         if (!checkBeforeRemote()) return;
555         try {
556             mInstalld.invalidateMounts();
557         } catch (Exception e) {
558             throw InstallerException.from(e);
559         }
560     }
561 
isQuotaSupported(String volumeUuid)562     public boolean isQuotaSupported(String volumeUuid) throws InstallerException {
563         if (!checkBeforeRemote()) return false;
564         try {
565             return mInstalld.isQuotaSupported(volumeUuid);
566         } catch (Exception e) {
567             throw InstallerException.from(e);
568         }
569     }
570 
prepareAppProfile(String pkg, @UserIdInt int userId, @AppIdInt int appId, String profileName, String codePath, String dexMetadataPath)571     public boolean prepareAppProfile(String pkg, @UserIdInt int userId, @AppIdInt int appId,
572             String profileName, String codePath, String dexMetadataPath) throws InstallerException {
573         if (!checkBeforeRemote()) return false;
574         try {
575             return mInstalld.prepareAppProfile(pkg, userId, appId, profileName, codePath,
576                     dexMetadataPath);
577         } catch (Exception e) {
578             throw InstallerException.from(e);
579         }
580     }
581 
assertValidInstructionSet(String instructionSet)582     private static void assertValidInstructionSet(String instructionSet)
583             throws InstallerException {
584         for (String abi : Build.SUPPORTED_ABIS) {
585             if (VMRuntime.getInstructionSet(abi).equals(instructionSet)) {
586                 return;
587             }
588         }
589         throw new InstallerException("Invalid instruction set: " + instructionSet);
590     }
591 
592     public static class InstallerException extends Exception {
InstallerException(String detailMessage)593         public InstallerException(String detailMessage) {
594             super(detailMessage);
595         }
596 
from(Exception e)597         public static InstallerException from(Exception e) throws InstallerException {
598             throw new InstallerException(e.toString());
599         }
600     }
601 }
602