Use FileProvider (instead of a world-readable file) in the APK installer demo.

World-readable files have been disallowed since N.

Test: Manually.
Change-Id: Iab174799403023378200d75dac5e8cd02e64dbf8
This commit is contained in:
Peter Visontay
2017-11-23 13:29:42 +00:00
parent 6e294637ac
commit 48f2a3142e
3 changed files with 58 additions and 6 deletions

View File

@@ -1311,6 +1311,17 @@
</intent-filter> </intent-filter>
</activity> </activity>
<!-- A FileProvider for passing the APK to be installed to the system. -->
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.example.android.apis.installapkprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
<activity android:name=".content.InstallApkSessionApi" <activity android:name=".content.InstallApkSessionApi"
android:label="@string/activity_install_apk_session_api" android:label="@string/activity_install_apk_session_api"
android:launchMode="singleTop"> android:launchMode="singleTop">

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2017 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Provides access to the installable APK in the package installer demo. -->
<files-path name="all" path=""/>
</paths>

View File

@@ -24,7 +24,9 @@ import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.content.FileProvider;
import android.util.Log; import android.util.Log;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener; import android.view.View.OnClickListener;
@@ -88,7 +90,8 @@ public class InstallApk extends Activity {
private OnClickListener mUnknownSourceListener = new OnClickListener() { private OnClickListener mUnknownSourceListener = new OnClickListener() {
public void onClick(View v) { public void onClick(View v) {
Intent intent = new Intent(Intent.ACTION_INSTALL_PACKAGE); Intent intent = new Intent(Intent.ACTION_INSTALL_PACKAGE);
intent.setData(Uri.fromFile(prepareApk("HelloActivity.apk"))); intent.setData(getApkUri("HelloActivity.apk"));
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(intent); startActivity(intent);
} }
}; };
@@ -96,7 +99,8 @@ public class InstallApk extends Activity {
private OnClickListener mMySourceListener = new OnClickListener() { private OnClickListener mMySourceListener = new OnClickListener() {
public void onClick(View v) { public void onClick(View v) {
Intent intent = new Intent(Intent.ACTION_INSTALL_PACKAGE); Intent intent = new Intent(Intent.ACTION_INSTALL_PACKAGE);
intent.setData(Uri.fromFile(prepareApk("HelloActivity.apk"))); intent.setData(getApkUri("HelloActivity.apk"));
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true); intent.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true);
intent.putExtra(Intent.EXTRA_RETURN_RESULT, true); intent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
intent.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME, intent.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME,
@@ -124,20 +128,37 @@ public class InstallApk extends Activity {
} }
}; };
private File prepareApk(String assetName) { /**
* Returns a Uri pointing to the APK to install.
*/
private Uri getApkUri(String assetName) {
// 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;
// Copy the given asset out into a file so that it can be installed. // Copy the given asset out into a file so that it can be installed.
// Returns the path to the file. // Returns the path to the file.
String tempFilename = "tmp.apk";
byte[] buffer = new byte[16384]; byte[] buffer = new byte[16384];
int fileMode = useFileProvider ? Context.MODE_PRIVATE : Context.MODE_WORLD_READABLE;
try (InputStream is = getAssets().open(assetName); try (InputStream is = getAssets().open(assetName);
FileOutputStream fout = openFileOutput("tmp.apk", Context.MODE_WORLD_READABLE)) { FileOutputStream fout = openFileOutput(tempFilename, fileMode)) {
int n; int n;
while ((n=is.read(buffer)) >= 0) { while ((n=is.read(buffer)) >= 0) {
fout.write(buffer, 0, n); fout.write(buffer, 0, n);
} }
} catch (IOException e) { } catch (IOException e) {
Log.i("InstallApk", "Failed transferring", e); Log.i("InstallApk", "Failed to write temporary APK file", e);
} }
return getFileStreamPath("tmp.apk"); if (useFileProvider) {
File toInstall = new File(this.getFilesDir(), tempFilename);
return FileProvider.getUriForFile(
this, "com.example.android.apis.installapkprovider", toInstall);
} else {
return Uri.fromFile(getFileStreamPath(tempFilename));
}
} }
} }