3

Programmatically install an apk in Android 7 / api24 answers a similar question, but I want to programmatically install an APK from app internal storage (on an SD card) in Android 5.0 - 6.0 too.

If it weren't from app internal storage, I could startActivity() with an intent whose action is ACTION_VIEW and whose data URI has a file:// scheme (using Uri.fromFile()). But I've tried that and it won't work with app internal storage, which isn't readable by other apps. I'm basing this on log messages like

W/asset: Asset path /storage/C5DF-1113/Android/data/com.example/files/foo.apk is neither a directory nor file (type=1).
W/InstallFlowAnalytics: Failed to hash APK contents
  java.io.FileNotFoundException: /storage/C5DF-1113/Android/data/com.example/files/foo.apk: open failed: ENOENT (No such file or directory)

I've tried subclassing ContentProvider, using a content:// scheme. But apparently (and see CommonsWare's answer here), the package installer doesn't support the content:// scheme until Android 7.0. I get log messages like the following in Android 6.0:

I/ActivityManager: START u0 {act=android.intent.action.VIEW dat=content://com.example.provider/internal/foo.apk typ=application/vnd.android.package-archive flg=0x10000001} from uid 10159 on display 0
E/Updates: installNewApk
     android.content.ActivityNotFoundException: No Activity found to handle Intent { act=android.intent.action.VIEW dat=content://com.example.provider/internal/foo.apk typ=application/vnd.android.package-archive flg=0x10000001 }
        at android.app.Instrumentation.checkStartActivityResult(Instrumentation.java:1805)

where the only apparent difference between an intent that matches the package installer and an intent that doesn't, is the data URI changing from file://... to content://....

There are answers elsewhere on installing an app silently, which is not a requirement for my project. There are also solutions that use reflection to get at undocumented features, or that require the device to be rooted, but those techniques are too brittle or user-unfriendly for this project.

There is also the option to copy the APK from app internal storage to external storage before launching the installer. We might have to go there, but the reason the APK is in internal storage in the first place is for purposes of privacy and security. (I realize this is far from bulletproof, but it is better than nothing and it's what our clients want.)

I'm willing to do a conditional based on the present version of Android, but at this point I still don't know what to do in 5.0 - 6.0.

Thanks for any suggestions.

LarsH
  • 27,481
  • 8
  • 94
  • 152
  • "internal storage" is usually reserved for things like `getFilesDir()`. An SD card is better referred to as "removable storage". With regards to your problem, AFAIK, you're out of luck -- the APK has to be on external storage (`getExternalFilesDir()`, etc.) prior to Android 7.0. I complained about this loudly, to no avail. – CommonsWare Nov 02 '17 at 20:50
  • @CommonsWare: Thanks for the answer. Regarding "removable storage", I agree that's a good term for storage on an SD card, but the most important distinction for this question is that it's not in a publically-readable area. If it were, I could just use `Uri.fromFile()` without a ContentProvider (prior to 7.0). AFAIK, the fact that it's on removable storage doesn't really affect this question. – LarsH Nov 02 '17 at 21:30
  • "AFAIK, the fact that it's on removable storage doesn't really affect this question" -- as of Android 4.4, apps do not have arbitrary read/write access to removable storage, which is why `Uri.fromFile()` not working for you. Only you have access to that directory; other apps, including the installer, do not. You would run into the same problem using `getFilesDir()`, as other apps do not have access to that location. And you cannot use `content` as a scheme until 7.0, which leaves you with external storage... and that's it, AFAIK. – CommonsWare Nov 02 '17 at 21:43
  • @CommonsWare: True, but apps also don't have arbitrary read/write access to *non*-removable storage. That's why I say it doesn't really affect this question. – LarsH Nov 02 '17 at 21:50
  • "but apps also don't have arbitrary read/write access to non-removable storage" -- yes, but the installer app has access to external storage, but not to your particular segment of removable storage or to your particular segment of internal storage. Hence, there is a very big difference between external storage and removable storage in your scenario. – CommonsWare Nov 02 '17 at 21:57
  • OK, so your emphasis is on the difference between removable storage and "*external* storage," not between removable and non-removable storage. – LarsH Nov 03 '17 at 00:48
  • Well, all three have their own characteristics. External storage is the only one that can be shared among all apps, or at least the subset that has requested permission from the user. – CommonsWare Nov 03 '17 at 01:13

1 Answers1

1

to get the Uri for apk file from internal storage directory you can use this method it works me :

  public static Uri getApkUri(Activity activity, String path) {

    // Before N, a MODE_WORLD_READABLE file could be passed via the ACTION_INSTALL_PACKAGE
    // Intent. Since N, MODE_WORLD_READABLE files are forbidden, and a FileProvider is
    // recommended.
    boolean useFileProvider = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
    int fileMode = useFileProvider ? Context.MODE_PRIVATE : Context.MODE_WORLD_READABLE;

    String tempFilename = "tmp.apk";
    byte[] buffer = new byte[16384];

    try (InputStream is = new FileInputStream(path);
         FileOutputStream fout = activity.openFileOutput(tempFilename, fileMode)) {
        int n;
        while ((n = is.read(buffer)) >= 0) {
            fout.write(buffer, 0, n);
        }
    } catch (IOException e) {
        Logger.e(TAG, ":getApkUri + " + "Failed to write temporary APK file", e);
    }

    if (useFileProvider) {
        File toInstall = new File(activity.getFilesDir(), tempFilename);
        return FileProvider.getUriForFile(activity, BuildConfig.APPLICATION_ID, toInstall);
    } else {
        return Uri.fromFile(activity.getFileStreamPath(tempFilename));
    }
}
Wadelzubair
  • 31
  • 1
  • 1
  • 9