I updated my targetSdkVersion to 30, so I had to update my creation File with SAF (storage access framework).
I was able to create the file, to choose location, to get the Uri in result (it is in the form content://), but when I try to send file as attach, the file is not attached. Gmail for example show me a messagge "Unable to attach file".
Am I missing something?
Any help would be appreciated. Thanks all
Here is my code:
int WRITE_REQUEST_CODE = 336;
private void createFile() {
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("application/pdf");
intent.putExtra(Intent.EXTRA_TITLE, "dettaglio_utente.pdf");
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivityForResult(intent, WRITE_REQUEST_CODE);
}
private void alterDocument(Uri uri) {
try {
ParcelFileDescriptor pfd = getContentResolver().
openFileDescriptor(uri, "w");
FileOutputStream fileOutputStream =
new FileOutputStream(pfd.getFileDescriptor());
byte[] pdfAsBytes = Base64.decode(pdfBase64, 0);
fileOutputStream.write(pdfAsBytes);
// Let the document provider know you're done by closing the stream.
fileOutputStream.close();
pfd.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
#Override
protected void onActivityResult(int requestCode, int resultCode, #Nullable Intent data) {
if (resultCode == RESULT_OK && requestCode == WRITE_REQUEST_CODE) {
try {
if (data != null && data.getData() != null) {
Uri path = data.getData();
alterDocument(path);
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setData(Uri.parse("mailto:")); // only email apps should handle this
intent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.privacy_mail_subject));
intent.putExtra(Intent.EXTRA_TEXT, getString(R.string.privacy_mail_body));
intent.putExtra(Intent.EXTRA_STREAM, path);
if (intent.resolveActivity(getPackageManager()) != null) {
startActivity(intent);
}
}
} catch (Exception e) {
Toast.makeText(this, "something went wrong" + e.getMessage(), Toast.LENGTH_SHORT).show();
e.printStackTrace();
}
}
}
I also added the DocumentProvider in Manifest as indicated by Android Documentation StorageProvider
<provider
android:name=".MyDocumentProvider"
android:authorities="${applicationId}.documents"
android:enabled="true"
android:exported="true"
android:grantUriPermissions="true"
android:permission="android.permission.MANAGE_DOCUMENTS">
<intent-filter>
<action android:name="android.content.action.DOCUMENTS_PROVIDER" />
</intent-filter>
</provider>
You add FLAG_GRANT_READ_URI_PERMISSION and/or FLAG_GRANT_READ_URI_PERMISSION when you provide a Uri to somebody else.
So, with respect to your code:
You can remove intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); from your createFile() method, as you are looking to receive a Uri
You need to add intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); to your ACTION_SEND Intent, before using that Intent, since you want to provide a Uri and you want the recipient to be able to read the contents identified by that Uri
Also:
Remove android:permission="android.permission.MANAGE_DOCUMENTS" from your <provider>, as the other app (e.g., Gmail) most likely will not hold that permission
Note that resolveActivity() may not work on Android 11+ without adding a <queries> element to your manifest — you might want to skip the resolveActivity() call and just wrap your startActivity() call in a try/catch and deal with the ActivityNotFoundException
Problem
In Cordova Android when clicking on an html type="file" input a system file picker appears instead of camera options.
What is expected to happen?
If I perform the same action in the system browser I get the expected dialog:
Version information
"cordova-android": "^8.0.0",
"cordova-plugin-camera": "^4.1.0"
cordova-cli 9.0.0
Full test case repo here
This is my first Android Cordova application, am I missing a certain plugin? I don't remember being asked for permission to use the camera at any stage so I wonder if I'm missing something in the config.xml linked above?
You need to set the capture and accept attributes like below:
<input type="file" capture="camera" accept="image/*" />
EDIT
Also, make sure your platforms/android/AndroidManifest.xml includes the following elements:
<application android:allowBackup="false" android:hardwareAccelerated="true" android:icon="#mipmap/icon" android:label="#string/app_name">
<provider android:authorities="${applicationId}.fileprovider" android:exported="false" android:grantUriPermissions="true" android:name="android.support.v4.content.FileProvider">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="#xml/file_paths" />
</provider>
<provider android:authorities="${applicationId}.provider" android:exported="false" android:grantUriPermissions="true" android:name="org.apache.cordova.camera.FileProvider">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="#xml/camera_provider_paths" />
</provider>
</application>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
If they don't already exist, create file platforms/android/res/xml/file_paths.xml containing:
<!-- file_paths.xml -->
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<cache-path name="photos" path="photos/" />
</paths>
and file platforms/android/res/xml/camera_provider_paths.xml:
<!-- camera_provider_paths.xml -->
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_files" path="."/>
</paths>
If that still does not seem to do the trick, you may additionally need to add/modify the SystemWebChromeClient.java file in the platforms/android/CordovaLib/src/org/apache/cordova/engine directory to include the following:
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
#Override
public boolean onShowFileChooser(WebView webView, final ValueCallback<Uri[]> filePathsCallback, final WebChromeClient.FileChooserParams fileChooserParams) {
// Image from file intent
boolean multiple = fileChooserParams.getMode() == WebChromeClient.FileChooserParams.MODE_OPEN_MULTIPLE;
String type = "*/*";
if (fileChooserParams.getAcceptTypes() != null && fileChooserParams.getAcceptTypes().length > 0) {
type = fileChooserParams.getAcceptTypes()[0];
}
Intent fileIntent = new Intent(Intent.ACTION_GET_CONTENT);
fileIntent.addCategory(Intent.CATEGORY_OPENABLE);
fileIntent.setTypeAndNormalize(type);
fileIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, multiple);
// Image from camera intent
Uri tempUri = null;
Intent captureIntent = null;
if (fileChooserParams.isCaptureEnabled()) {
captureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
Context context = parentEngine.getView().getContext();
if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA) && captureIntent.resolveActivity(context.getPackageManager()) != null) {
try {
File tempFile = createTempFile(context);
Log.d(LOG_TAG, "Temporary photo capture file: " + tempFile);
tempUri = createUriForFile(context, tempFile);
Log.d(LOG_TAG, "Temporary photo capture URI: " + tempUri);
captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, tempUri);
} catch (IOException e) {
Log.e(LOG_TAG, "Unable to create temporary file for photo capture", e);
captureIntent = null;
}
} else {
Log.w(LOG_TAG, "Device does not support photo capture");
captureIntent = null;
}
}
final Uri captureUri = tempUri;
// Chooser intent
Intent chooserIntent = Intent.createChooser(fileIntent, null);
if (captureIntent != null) {
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Intent[] { captureIntent });
}
try {
Log.i(LOG_TAG, "Starting intent for file chooser");
parentEngine.cordova.startActivityForResult(new CordovaPlugin() {
#Override
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
// Handle result
Uri[] result = null;
if (resultCode == Activity.RESULT_OK) {
List<Uri> uris = new ArrayList<Uri>();
if (intent == null && captureUri != null) { // camera
Log.v(LOG_TAG, "Adding camera capture: " + captureUri);
uris.add(captureUri);
} else if (intent.getClipData() != null) { // multiple files
ClipData clipData = intent.getClipData();
int count = clipData.getItemCount();
for (int i = 0; i < count; i++) {
Uri uri = clipData.getItemAt(i).getUri();
Log.v(LOG_TAG, "Adding file (multiple): " + uri);
if (uri != null) {
uris.add(uri);
}
}
} else if (intent.getData() != null) { // single file
Log.v(LOG_TAG, "Adding file (single): " + intent.getData());
uris.add(intent.getData());
}
if (!uris.isEmpty()) {
Log.d(LOG_TAG, "Receive file chooser URL: " + uris.toString());
result = uris.toArray(new Uri[uris.size()]);
}
}
filePathsCallback.onReceiveValue(result);
}
}, chooserIntent, FILECHOOSER_RESULTCODE);
} catch (ActivityNotFoundException e) {
Log.w("No activity found to handle file chooser intent.", e);
filePathsCallback.onReceiveValue(null);
}
return true;
}
After reading this documentation https://developer.android.com/training/camera/photobasics I want to take a photo and to store the uri of the photo into a given variable. The problem is that I'm storing the variable name and the uri into the intent with takePictureIntent.putExtra(Constants.INTENT_EXTRA_VARNAME, variable) and takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI), but in onActivityResult the intent data is empty, and neither the variable and the uri stored are empty. I need to know the uri and the variable. What am I doing wrong?
I need to pass the information as intent data because the class which is firing the action of displaying the camera is differente from the activity class.
class with the action:
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
takePictureIntent.putExtra(Constants.INTENT_EXTRA_VARNAME, variable);
// Ensure that there's a camera activity to handle the intent
if (takePictureIntent.resolveActivity(SectionManager.getInstance().getCurrentActivity().getPackageManager()) != null) {
// Create the File where the photo should go
File photoFile = null;
try {
photoFile = createImageFile();
} catch (IOException ex) {
ex.printStackTrace();
}
// Continue only if the File was successfully created
if (photoFile != null) {
Uri photoURI = FileProvider.getUriForFile(SectionManager.getInstance().getCurrentActivity(), SectionManager.getInstance().getCurrentActivity().getPackageName(), photoFile);
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
SectionManager.getInstance().getCurrentActivity().startActivityForResult(takePictureIntent, Constants.REQUEST_IMAGE_CAPTURE);
}
}
my activity which receives the result:
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
if( requestCode == Constants.REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK){
String varName;
String path;
if(intent != null) {
varName = intent.getStringExtra(Constants.INTENT_EXTRA_VARNAME);
path = Util.getRealPathFromURI(SectionManager.getInstance().getCurrentActivity(), intent.getData());
Log.d(DEBUG_TAG, "onActivityResult-> varName: "+varName+" path: "+path);
if (varName != null && path != null) {
VariableManager.put(varName, path);
}
}
}
}
manifest, permission write_external_storage and this code:
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_paths"></meta-data>
</provider>
file_paths.xml file:
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="my_images" path="/" />
</paths>
You already have photoURI which you send with MediaStore.EXTRA_OUTPUT so You can simply use it . Save photoURI as globally and directly use it . See if RESULT_OK is emitted then it means picture was clicked and it will be saved at the EXTRA_OUTPUT location.
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
if( requestCode == Constants.REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK){
// USe photoURi here
}
}
You can not expect some custom key which is Constants.INTENT_EXTRA_VARNAME will be return with System camera Intent. I do not have any knowledge of such thing.
If you specified MediaStore.EXTRA_OUTPUT the image taken will be written to that path, and no data will given to onActivityResult so you need to persist the file path.
PS : You can save it globally and also make sure you should persist it during onSaveInstanceState().
I want to open a saved image in gallery on Android Nougat but what I get is a black gallery page with message "Can't load the photo".
That's my code:
Manifest
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/provider_paths"/>
</provider>
provider_paths.xml
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_files" path="."/>
</paths>
Path generated in DrawView
public static boolean save(Bitmap bitmap){
Date now = new Date();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd-MM-yyyy'_'HH:mm");
File folder = new File(Environment.getExternalStorageDirectory() +
File.separator + "Crash");
if (!folder.exists()) {
folder.mkdirs();
}
FileOutputStream fos = null;
try {
lastImagePath = new File(Environment.getExternalStorageDirectory().toString() + "/Crash/" + simpleDateFormat.format(now) + ".jpg");
fos = new FileOutputStream(lastImagePath);
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
fos.flush();
fos.close();
fos = null;
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
}
finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {}
}
}
}
Open Image Listener
private class OpenImageListener implements View.OnClickListener{
#Override
public void onClick(View v) {
if(Build.VERSION.SDK_INT < Build.VERSION_CODES.N){
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.parse("file://" + DrawView.getLastImagePath().getAbsolutePath()), "image/*");
startActivity(intent);
} else {
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
Uri photoUri = FileProvider.getUriForFile(MainActivity.this, BuildConfig.APPLICATION_ID + ".provider", DrawView.getLastImagePath());
intent.setData(photoUri);
startActivity(intent);
}
}
}
Maybe I generate a wrong path for the image, but with old version it works (I tried on Marshmallow and works great).
Can someone help me? Thanks.
In your else block in onClick(), after calling setData() on your Intent to set the Uri, call addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) on the Intent.
As it stands, the other app has no rights to work with the content identified by the Uri. Adding FLAG_GRANT_READ_URI_PERMISSION does this.
This is covered in the FileProvider documentation, along with modern books on Android app development.
I do expire the same issue with almost all apps I tried, my code is the same as yours and as far as the google docs say it looks to be correct...
The only "solution" I can offer you is to compile against version 23(https://source.android.com/source/build-numbers.html) to avoid the forced strict mode for file URIs.
Of course you also have to downgrade your Android support libs in case you are using them.
If someone has a better idea please share...
To view image from mobile emulated gallery is no more difficult but in nougat is quite different i have run time image created and wants to see that image its failed many times but finally its run with this code
1.after a long struggle i write this code , dont forget to add, fil provider in mainfest file as you may know .
MimeTypeMap map = MimeTypeMap.getSingleton();
String ext = MimeTypeMap.getFileExtensionFromUrl(file.getName());
String type = map.getMimeTypeFromExtension(ext);
if (type == null)
type = "*/*";
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// only for gingerbread and newer versions
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
Uri photoUri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", file);
intent.setDataAndType(photoUri, type);
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION|Intent.FLAG_GRANT_READ_URI_PERMISSION);
context.startActivity(intent);
}
else{
Intent intent = new Intent(Intent.ACTION_VIEW);
Uri data = Uri.fromFile(file);
intent.setDataAndType(data, type);
context.startActivity(intent);
}
Is possible to programmatically install a dynamically downloaded apk from a custom Android application.
You can easily launch a market link or an install prompt:
Intent promptInstall = new Intent(Intent.ACTION_VIEW)
.setDataAndType(Uri.parse("file:///path/to/your.apk"),
"application/vnd.android.package-archive");
startActivity(promptInstall);
source
Intent goToMarket = new Intent(Intent.ACTION_VIEW)
.setData(Uri.parse("market://details?id=com.package.name"));
startActivity(goToMarket);
source
However, you cannot install .apks without user's explicit permission; not unless the device and your program is rooted.
File file = new File(dir, "App.apk");
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
startActivity(intent);
I had the same problem and after several attempts, it worked out for me this way. I don't know why, but setting data and type separately screwed up my intent.
The solutions provided to this question are all applicable to targetSdkVersion s of 23 and below. For Android N, i.e. API level 24, and above, however, they do not work and crash with the following Exception:
android.os.FileUriExposedException: file:///storage/emulated/0/... exposed beyond app through Intent.getData()
This is due to the fact that starting from Android 24, the Uri for addressing the downloaded file has changed. For instance, an installation file named appName.apk stored on the primary external filesystem of the app with package name com.example.test would be as
file:///storage/emulated/0/Android/data/com.example.test/files/appName.apk
for API 23 and below, whereas something like
content://com.example.test.authorityStr/pathName/Android/data/com.example.test/files/appName.apk
for API 24 and above.
More details on this can be found here and I am not going to go through it.
To answer the question for targetSdkVersion of 24 and above, one has to follow these steps:
Add the following to the AndroidManifest.xml:
<application
android:allowBackup="true"
android:label="#string/app_name">
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.authorityStr"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/paths"/>
</provider>
</application>
2. Add the following paths.xml file to the xml folder on res in src, main:
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path
name="pathName"
path="pathValue"/>
</paths>
The pathName is that shown in the exemplary content uri example above and pathValue is the actual path on the system.
It would be a good idea to put a "." (without quotes) for pathValue in the above if you do not want to add any extra subdirectory.
Write the following code to install the apk with the name appName.apk on the primary external filesystem:
File directory = context.getExternalFilesDir(null);
File file = new File(directory, fileName);
Uri fileUri = Uri.fromFile(file);
if (Build.VERSION.SDK_INT >= 24) {
fileUri = FileProvider.getUriForFile(context, context.getPackageName(),
file);
}
Intent intent = new Intent(Intent.ACTION_VIEW, fileUri);
intent.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true);
intent.setDataAndType(fileUri, "application/vnd.android" + ".package-archive");
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
context.startActivity(intent);
activity.finish();
No permission is also necessary when writing to your own app's private directory on the external filesystem.
I have written an AutoUpdate library here in which I have used the above.
Well, I dug deeper, and found sources of PackageInstaller application from Android Source.
https://github.com/android/platform_packages_apps_packageinstaller
From manifest I found that it require permission:
<uses-permission android:name="android.permission.INSTALL_PACKAGES" />
And the actual process of installation occurs after confirmation
Intent newIntent = new Intent();
newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO, mPkgInfo.applicationInfo);
newIntent.setData(mPackageURI);
newIntent.setClass(this, InstallAppProgress.class);
String installerPackageName = getIntent().getStringExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME);
if (installerPackageName != null) {
newIntent.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME, installerPackageName);
}
startActivity(newIntent);
I just want to share the fact that my apk file was saved to my app "Data" directory and that I needed to change the permissions on the apk file to be world readable in order to allow it to be installed that way, otherwise the system was throwing "Parse error: There is a Problem Parsing the Package"; so using solution from #Horaceman that makes:
File file = new File(dir, "App.apk");
file.setReadable(true, false);
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
startActivity(intent);
This can help others a lot!
First:
private static final String APP_DIR = Environment.getExternalStorageDirectory().getAbsolutePath() + "/MyAppFolderInStorage/";
private void install() {
File file = new File(APP_DIR + fileName);
if (file.exists()) {
Intent intent = new Intent(Intent.ACTION_VIEW);
String type = "application/vnd.android.package-archive";
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Uri downloadedApk = FileProvider.getUriForFile(getContext(), "ir.greencode", file);
intent.setDataAndType(downloadedApk, type);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
} else {
intent.setDataAndType(Uri.fromFile(file), type);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
getContext().startActivity(intent);
} else {
Toast.makeText(getContext(), "ّFile not found!", Toast.LENGTH_SHORT).show();
}
}
Second: For android 7 and above you should define a provider in manifest like below!
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="ir.greencode"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/paths" />
</provider>
Third: Define path.xml in res/xml folder like below!
I'm using this path for internal storage if you want to change it to something else there is a few way! You can go to this link:
FileProvider
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="your_folder_name" path="MyAppFolderInStorage/"/>
</paths>
Forth: You should add this permission in manifest:
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
Allows an application to request installing packages. Apps targeting APIs greater than 25 must hold this permission in order to use Intent.ACTION_INSTALL_PACKAGE.
Please make sure the provider authorities are the same!
In Android Oreo and above version we have to approach different methods to install apk programatically.
private void installApkProgramatically() {
try {
File path = activity.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS);
File file = new File(path, filename);
Uri uri;
if (file.exists()) {
Intent unKnownSourceIntent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES).setData(Uri.parse(String.format("package:%s", activity.getPackageName())));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (!activity.getPackageManager().canRequestPackageInstalls()) {
startActivityForResult(unKnownSourceIntent, Constant.UNKNOWN_RESOURCE_INTENT_REQUEST_CODE);
} else {
Uri fileUri = FileProvider.getUriForFile(activity.getBaseContext(), activity.getApplicationContext().getPackageName() + ".provider", file);
Intent intent = new Intent(Intent.ACTION_VIEW, fileUri);
intent.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true);
intent.setDataAndType(fileUri, "application/vnd.android" + ".package-archive");
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(intent);
alertDialog.dismiss();
}
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Intent intent1 = new Intent(Intent.ACTION_INSTALL_PACKAGE);
uri = FileProvider.getUriForFile(activity.getApplicationContext(), BuildConfig.APPLICATION_ID + ".provider", file);
activity.grantUriPermission("com.abcd.xyz", uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
activity.grantUriPermission("com.abcd.xyz", uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
intent1.setDataAndType(uri,
"application/*");
intent1.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent1.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent1.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
startActivity(intent1);
} else {
Intent intent = new Intent(Intent.ACTION_VIEW);
uri = Uri.fromFile(file);
intent.setDataAndType(uri,
"application/vnd.android.package-archive");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
} else {
Log.i(TAG, " file " + file.getPath() + " does not exist");
}
} catch (Exception e) {
Log.i(TAG, "" + e.getMessage());
}
}
In Oreo and above version we need unknown resource installation permission. so in activity result u have to check the result for the permission
#Override
public void onActivityResult(int requestCode, int resultCode, #Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case Constant.UNKNOWN_RESOURCE_INTENT_REQUEST_CODE:
switch (resultCode) {
case Activity.RESULT_OK:
installApkProgramatically();
break;
case Activity.RESULT_CANCELED:
//unknown resouce installation cancelled
break;
}
break;
}
}
Do not forget to request permissions:
android.Manifest.permission.WRITE_EXTERNAL_STORAGE
android.Manifest.permission.READ_EXTERNAL_STORAGE
Add in AndroidManifest.xml the provider and permission:
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
...
<application>
...
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/provider_paths"/>
</provider>
</application>
Create XML file provider res/xml/provider_paths.xml
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path
name="external"
path="." />
<external-files-path
name="external_files"
path="." />
<cache-path
name="cache"
path="." />
<external-cache-path
name="external_cache"
path="." />
<files-path
name="files"
path="." />
</paths>
Use below example code:
public class InstallManagerApk extends AppCompatActivity {
static final String NAME_APK_FILE = "some.apk";
public static final int REQUEST_INSTALL = 0;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// required permission:
// android.Manifest.permission.WRITE_EXTERNAL_STORAGE
// android.Manifest.permission.READ_EXTERNAL_STORAGE
installApk();
}
...
/**
* Install APK File
*/
private void installApk() {
try {
File filePath = Environment.getExternalStorageDirectory();// path to file apk
File file = new File(filePath, LoadManagerApkFile.NAME_APK_FILE);
Uri uri = getApkUri( file.getPath() ); // get Uri for each SDK Android
Intent intent = new Intent(Intent.ACTION_INSTALL_PACKAGE);
intent.setData( uri );
intent.setFlags( Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_ACTIVITY_NEW_TASK );
intent.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true);
intent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
intent.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME, getApplicationInfo().packageName);
if ( getPackageManager().queryIntentActivities(intent, 0 ) != null ) {// checked on start Activity
startActivityForResult(intent, REQUEST_INSTALL);
} else {
throw new Exception("don`t start Activity.");
}
} catch ( Exception e ) {
Log.i(TAG + ":InstallApk", "Failed installl APK file", e);
Toast.makeText(getApplicationContext(), e.getMessage(), Toast.LENGTH_LONG)
.show();
}
}
/**
* Returns a Uri pointing to the APK to install.
*/
private Uri getApkUri(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;
String tempFilename = "tmp.apk";
byte[] buffer = new byte[16384];
int fileMode = useFileProvider ? Context.MODE_PRIVATE : Context.MODE_WORLD_READABLE;
try (InputStream is = new FileInputStream(new File(path));
FileOutputStream fout = openFileOutput(tempFilename, fileMode)) {
int n;
while ((n = is.read(buffer)) >= 0) {
fout.write(buffer, 0, n);
}
} catch (IOException e) {
Log.i(TAG + ":getApkUri", "Failed to write temporary APK file", e);
}
if (useFileProvider) {
File toInstall = new File(this.getFilesDir(), tempFilename);
return FileProvider.getUriForFile(this, BuildConfig.APPLICATION_ID, toInstall);
} else {
return Uri.fromFile(getFileStreamPath(tempFilename));
}
}
/**
* Listener event on installation APK file
*/
#Override
protected void onActivityResult(int requestCode, int resultCode, #Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if(requestCode == REQUEST_INSTALL) {
if (resultCode == Activity.RESULT_OK) {
Toast.makeText(this,"Install succeeded!", Toast.LENGTH_SHORT).show();
} else if (resultCode == Activity.RESULT_CANCELED) {
Toast.makeText(this,"Install canceled!", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this,"Install Failed!", Toast.LENGTH_SHORT).show();
}
}
}
...
}
Another solution that doesn't not require to hard-code the receiving app and that is therefore safer:
Intent intent = new Intent(Intent.ACTION_INSTALL_PACKAGE);
intent.setData( Uri.fromFile(new File(pathToApk)) );
startActivity(intent);
It's worth noting that if you use the DownloadManager to kick off your download, be sure to save it to an external location e.g. setDestinationInExternalFilesDir(c, null, "<your name here>).apk";. The intent with a package-archive type doesn't appear to like the content: scheme used with downloads to an internal location, but does like file:. (Trying to wrap the internal path into a File object and then getting the path doesn't work either, even though it results in a file: url, as the app won't parse the apk; looks like it must be external.)
Example:
int uriIndex = cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI);
String downloadedPackageUriString = cursor.getString(uriIndex);
File mFile = new File(Uri.parse(downloadedPackageUriString).getPath());
Intent promptInstall = new Intent(Intent.ACTION_VIEW)
.setDataAndType(Uri.fromFile(mFile), "application/vnd.android.package-archive")
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
appContext.startActivity(promptInstall);
Came at this some two months ago and supposedly figured it out. Came back today and couldn't make heads or tails of it. Nothing had changed in my setup as far as I am aware, so apparently whatever past me had come up with wasn't robust enough for present me. I finally managed to get something working again, so documenting it here for future me and anyone else who might stand to benefit from yet another attempt.
This attempt represents a direct Xamarin C# translation of the original Android Java Install APK - Session API example. It could probably use some additional work, but it's at least a start. I have it running on an Android 9 device, though I have the project targetting Android 11.
InstallApkSessionApi.cs
namespace LauncherDemo.Droid
{
using System;
using System.IO;
using Android.App;
using Android.Content;
using Android.Content.PM;
using Android.OS;
using Android.Widget;
[Activity(Label = "InstallApkSessionApi", LaunchMode = LaunchMode.SingleTop)]
public class InstallApkSessionApi : Activity
{
private static readonly string PACKAGE_INSTALLED_ACTION =
"com.example.android.apis.content.SESSION_API_PACKAGE_INSTALLED";
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
this.SetContentView(Resource.Layout.install_apk_session_api);
// Watch for button clicks.
Button button = this.FindViewById<Button>(Resource.Id.install);
button.Click += this.Button_Click;
}
private void Button_Click(object sender, EventArgs e)
{
PackageInstaller.Session session = null;
try
{
PackageInstaller packageInstaller = this.PackageManager.PackageInstaller;
PackageInstaller.SessionParams #params = new PackageInstaller.SessionParams(
PackageInstallMode.FullInstall);
int sessionId = packageInstaller.CreateSession(#params);
session = packageInstaller.OpenSession(sessionId);
this.AddApkToInstallSession("HelloActivity.apk", session);
// Create an install status receiver.
Context context = this;
Intent intent = new Intent(context, typeof(InstallApkSessionApi));
intent.SetAction(PACKAGE_INSTALLED_ACTION);
PendingIntent pendingIntent = PendingIntent.GetActivity(context, 0, intent, 0);
IntentSender statusReceiver = pendingIntent.IntentSender;
// Commit the session (this will start the installation workflow).
session.Commit(statusReceiver);
}
catch (IOException ex)
{
throw new InvalidOperationException("Couldn't install package", ex);
}
catch
{
if (session != null)
{
session.Abandon();
}
throw;
}
}
private void AddApkToInstallSession(string assetName, PackageInstaller.Session session)
{
// It's recommended to pass the file size to openWrite(). Otherwise installation may fail
// if the disk is almost full.
using Stream packageInSession = session.OpenWrite("package", 0, -1);
using Stream #is = this.Assets.Open(assetName);
byte[] buffer = new byte[16384];
int n;
while ((n = #is.Read(buffer)) > 0)
{
packageInSession.Write(buffer, 0, n);
}
}
// Note: this Activity must run in singleTop launchMode for it to be able to receive the intent
// in onNewIntent().
protected override void OnNewIntent(Intent intent)
{
Bundle extras = intent.Extras;
if (PACKAGE_INSTALLED_ACTION.Equals(intent.Action))
{
PackageInstallStatus status = (PackageInstallStatus)extras.GetInt(PackageInstaller.ExtraStatus);
string message = extras.GetString(PackageInstaller.ExtraStatusMessage);
switch (status)
{
case PackageInstallStatus.PendingUserAction:
// This test app isn't privileged, so the user has to confirm the install.
Intent confirmIntent = (Intent) extras.Get(Intent.ExtraIntent);
this.StartActivity(confirmIntent);
break;
case PackageInstallStatus.Success:
Toast.MakeText(this, "Install succeeded!", ToastLength.Short).Show();
break;
case PackageInstallStatus.Failure:
case PackageInstallStatus.FailureAborted:
case PackageInstallStatus.FailureBlocked:
case PackageInstallStatus.FailureConflict:
case PackageInstallStatus.FailureIncompatible:
case PackageInstallStatus.FailureInvalid:
case PackageInstallStatus.FailureStorage:
Toast.MakeText(this, "Install failed! " + status + ", " + message,
ToastLength.Short).Show();
break;
default:
Toast.MakeText(this, "Unrecognized status received from installer: " + status,
ToastLength.Short).Show();
break;
}
}
}
}
}
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="com.companyname.launcherdemo" android:installLocation="auto">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="30" />
<application android:label="LauncherDemo.Android" android:theme="#style/MainTheme" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
</manifest>
What I particularly like about this approach is that it does not require any special work in the manifest - no broadcast receivers, file providers, etc. Granted, this takes as its source some APK in the app's assets, whereas a more useful system will probably use some given APK path. I imagine that will introduce a certain level of additional complexity. In addition, I never ran into any issues here (at least as far as I could tell) with the Xamarin GC closing streams before Android was done with them. Nor did I have any issues with the APK not being parsed. I made sure to use a signed APK (the one generated by Visual Studio when deploying to the device worked just fine), and again I did not run into any file access permission issues simply due to using an APK from the app's assets in this example.
One thing that some of the other answers here provided was the idea of making the sideloading permission grant more streamlined. The answer by Yabaze Cool provided this feature:
Intent unKnownSourceIntent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES).setData(Uri.parse(String.format("package:%s", activity.getPackageName())));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (!activity.getPackageManager().canRequestPackageInstalls()) {
startActivityForResult(unKnownSourceIntent, Constant.UNKNOWN_RESOURCE_INTENT_REQUEST_CODE);
...
When I tested my translation, I uninstalled both the launcher demo and the app it installed. Not providing the check to canRequestPackageInstalls made it to where I had to manually press an additional Settings button to take me to the same dialog as does the ACTION_MANAGE_UNKNOWN_APP_SOURCES intent above. So adding this logic can help to somewhat simplify the installation process for the user.
Yes it's possible. But for that you need the phone to install unverified sources. For example, slideMe does that. I think the best thing you can do is to check if the application is present and send an intent for the Android Market. you should use something the url scheme for android Market.
market://details?id=package.name
I don't know exactly how to start the activity but if you start an activity with that kind of url. It should open the android market and give you the choice to install the apps.
Just an extension, if anyone need a library then this might help. Thanks to Raghav
try this
String filePath = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME));
String title = filePath.substring( filePath.lastIndexOf('/')+1, filePath.length() );
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(new File(filePath)), "application/vnd.android.package-archive");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // without this flag android returned a intent error!
MainActivity.this.startActivity(intent);
UpdateNode provides an API for Android to install APK packages from inside another App.
You can just define your Update online and integrate the API into your App - that's it.
Currently the API is in Beta state, but you can already do some tests yourself.
Beside that, UpdateNode offers also displaying messages though the system - pretty useful if you want to tell something important to your users.
I am part of the client dev team and am using at least the message functionality for my own Android App.
See here a description how to integrate the API
Try this
- Write on Manifest:
uses-permission android:name="android.permission.INSTALL_PACKAGES"
tools:ignore="ProtectedPermissions"
Write the Code:
File sdCard = Environment.getExternalStorageDirectory();
String fileStr = sdCard.getAbsolutePath() + "/Download";// + "app-release.apk";
File file = new File(fileStr, "app-release.apk");
Intent promptInstall = new Intent(Intent.ACTION_VIEW).setDataAndType(Uri.fromFile(file),
"application/vnd.android.package-archive");
startActivity(promptInstall);
First add the following line to AndroidManifest.xml :
<uses-permission android:name="android.permission.INSTALL_PACKAGES"
tools:ignore="ProtectedPermissions" />
Then use the following code to install apk:
File sdCard = Environment.getExternalStorageDirectory();
String fileStr = sdCard.getAbsolutePath() + "/MyApp";// + "app-release.apk";
File file = new File(fileStr, "TaghvimShamsi.apk");
Intent promptInstall = new Intent(Intent.ACTION_VIEW).setDataAndType(Uri.fromFile(file),
"application/vnd.android.package-archive");
startActivity(promptInstall);
Based on answer #Uroš Podkrižnik.
Installing the application through the APK may differ for various versions of android (API Levels 21-30):
private var uri: Uri? = null
private var manager: DownloadManager? = null
private var file: File? = null
private var request: DownloadManager.Request? = null
private val REQUEST_WRITE_PERMISSION = 786
private val REQUEST_INSTALL_PACKAGE = 1234
private var receiver: BroadcastReceiver? = null
private var installIntent: Intent? = null
...
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val externalStorageDir = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
context?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)
} else {
#Suppress("DEPRECATION")
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
}
val destination = "$externalStorageDir/Application.apk"
uri = Uri.parse("file://$destination")
file = File(destination)
file?.let { if (it.exists()) it.delete() }
request = DownloadManager.Request(Uri.parse("https://path_to_file/application.apk"))
request?.let {
it.setDescription("Update App")
it.setTitle("Application")
it.setDestinationUri(uri)
}
manager = context?.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
// for level android api >= 23 needs permission to write to external storage
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (ContextCompat.checkSelfPermission(context!!, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
// here you can display the loading diagram
registerReceiver()
} else {
// request for permission to write to external storage
requestPermissions(
arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
REQUEST_WRITE_PERMISSION
)
}
} else {
// here you can display the loading diagram
registerReceiver()
}
}
Create and register receiver:
private val onDownloadComplete = object : BroadcastReceiver() {
// install app when apk is loaded
override fun onReceive(ctxt: Context, intent: Intent) {
val mimeType = "application/vnd.android.package-archive"
receiver = this
try {
installIntent = Intent(Intent.ACTION_VIEW)
installIntent?.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
// for android api >= 24 requires FileProvider
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
installIntent?.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
val fileProviderURI = FileProvider.getUriForFile(
context!!,
context!!.applicationContext.packageName + ".provider",
file!!)
installIntent?.setDataAndType(fileProviderURI, mimeType)
// for android api >= 26 requires permission to install from APK in settings
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (context!!.applicationContext.packageManager.canRequestPackageInstalls()) {
installFromAPK()
} else goToSecuritySettings()
} else installFromAPK()
} else {
// for android api < 24 used file:// instead content://
// (no need to use FileProvider)
installIntent?.setDataAndType(uri, mimeType)
installFromAPK()
}
} catch (e: Exception) {
// view error message
}
}
}
private fun registerReceiver() {
manager!!.enqueue(request)
context?.registerReceiver(
onDownloadComplete,
IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)
)
}
private fun installFromAPK() {
try {
startActivity(installIntent)
context?.unregisterReceiver(receiver)
activity?.finish()
} catch (e: Exception) {
// view error message
}
}
// go to settings for get permission install from APK
private fun goToSecuritySettings() {
val intent = Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES).setData(
Uri.parse(String.format(
"package:%s",
context!!.applicationContext.packageName
))
)
try {
startActivityForResult(intent, REQUEST_INSTALL_PACKAGE)
} catch (e: Exception) {
// view error message
}
}
Intercept the result of the permission request WRITE_EXTERNAL_STORAGE:
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == REQUEST_WRITE_PERMISSION
&& grantResults.isNotEmpty()
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
try {
// here you can display the loading diagram
registerReceiver()
} catch (e: Exception) {
// view error message
}
}
}
Intercept the result of user selection in security settings:
#RequiresApi(api = Build.VERSION_CODES.O)
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == REQUEST_INSTALL_PACKAGE
&& resultCode == AppCompatActivity.RESULT_OK) {
if (context!!.applicationContext.packageManager.canRequestPackageInstalls()) {
installFromAPK()
}
} else {
// view error message
}
}
Add to your manifest:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
<application...>
...
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/provider_paths" />
</provider>
...
</application>
Add provider_paths.xml file to res/xml:
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path name="external_files" path="."/>
</paths>
For android API level = 30, return from security settings does not work,
so used installation via browser:
try {
val intent = Intent(Intent.ACTION_VIEW)
intent.data = Uri.parse("https://path_to_file/application.apk")
startActivity(intent)
activity?.finish()
} catch (e: ActivityNotFoundException) { }