I have an android application which updates itself from a server, if new version of the apk is present.
The problem is, it is now not working on devices running android 10 and higher. The root cause of the problem is that ACTION_VIEW is deprecated, and no longer supported since android 10. Here is the legacy code which is not working:
private void installapk(File file) {
Log.w(TAG, "Installing new version...");
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
// Otherwise will throw exception, have to install it in new task
intent.setFlags(intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
So after searching for a while now, it seems the only option I have left is to use packagemanager class to do the job. I found this example, which compiles and runs without giving any error message, but it seems like it does nothing.
public static void install(Context context, String packageName, File file) {
InputStream in = null;
OutputStream out = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
PackageInstaller.Session session = null;
try {
in = new FileInputStream(file);
PackageInstaller packageInstaller = context.getPackageManager().getPackageInstaller();
PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
//
params.setAppPackageName(packageName);
// set params
int sessionId = packageInstaller.createSession(params);
session = packageInstaller.openSession(sessionId);
out = session.openWrite(file.getName(), 0, -1);
final byte[] buffer = new byte[65536];
int bytes_read;
while((bytes_read = in.read(buffer)) != -1){
out.write(buffer, 0, bytes_read);
}
session.fsync(out);
in.close();
out.close();
session.commit(createIntentSender(context, sessionId, ACTION_INSTALL_COMPLETE));
} catch (IOException e) {
Log.w(TAG, e.getMessage());
} finally {
;
}
}
}
private static IntentSender createIntentSender(Context context, int sessionId, String action) {
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, sessionId, new Intent(action), 0);
return pendingIntent.getIntentSender();
}
This code runs fine, but as I said, nothing happens as a result. What am I doing wrong?
Related
I have set up a Broadcast that starts up when Application is installed with startActivityForResult(). The installation Intent is created here.
private static Intent getOpenDownloadedApkIntent(Context context, File file) {
String name = getPackageNameForAPK(file.getPath(), context);
if (name != null) {
INSTALL_APK_INFO.put(name, file);
}
// The type of intent to use to open the downloaded apk changed in Android N (7.0)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Uri path = FileProvider.getUriForFile(context,
context.getApplicationContext().getPackageName() + ".utils.DownloadedFileProvider",
file);
Intent intent = new Intent(Intent.ACTION_INSTALL_PACKAGE);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
intent.setData(path);
return intent;
} else {
Uri uri = Uri.fromFile(file);
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
intent.setDataAndType(uri, "application/vnd.android.package-archive");
return intent;
}
}
private static String getPackageNameForAPK(final String archiveFilePath, Context context) {
try {
PackageInfo info = context.getPackageManager().getPackageArchiveInfo(archiveFilePath, PackageManager.GET_META_DATA);
return info.packageName;
} catch (Exception ex) {
ex.printStackTrace();
return null;
}
}
Now if it is Android 7 or higher, then the uninstall works, but if it is Android 6 or lower, then it just seems to delete the icon of the APK but not the APK itself.
Here is the code that should delete the APK:
private static BroadcastReceiver onInstallComplete = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
ApplicationInfo info = null;
try {
info = context.getPackageManager().getApplicationInfo(intent.getDataString().split(":")[1], PackageManager.GET_META_DATA);
} catch (PackageManager.NameNotFoundException ex) {
}
try {
if (info != null) {
File file = INSTALL_APK_INFO.get(info.packageName);
if (file != null) {
file.delete();
INSTALL_APK_INFO.remove(info.packageName);
}
}
} catch (NullPointerException ex) {
}
}
};
I am guessing that is has something to do with the path, but at the same time it seems to delete the icon of the APK. By icon I mean that if i remove the file.delete() then the APK is with icon in downloads folder but if I run the file.delete() then the APK is without the icon.
What am I doing wrong?
Do u mean to say that app is getting installed but without launcher icon?
FileNotFoundException occur when capture the screen and save the screenshot into SDCard after power off the cellular phone, It seems that the server to flash the buffer into disk has disconnected during shutdown, but anyone can help me explain more detail? The output error message is:
02-24 14:03:28.180 27412 27412 D TakeScreenshotService:
#isFloatingBallVisible() ,visible = false
02-24 14:03:29.402 27412 10176 E SaveImageInBackgroundTask: error in
SaveImageInBackgroundData,
exception:java.io.FileNotFoundException:
/storage/emulated/0/Pictures/Screenshots/Screenshot_20170224-140329.jpg:
open failed: ENOTCONN (Transport endpoint is not connected)
02-24 14:03:29.526 27412 27412 D TakeScreenshotService: onUnbind,
isMultiScrenshot:false intent:Intent {
cmp=com.android.systemui/.screenshot.TakeScreenshotService }
The corresponding codes are:
#Override
protected SaveImageInBackgroundData doInBackground(SaveImageInBackgroundData... params) {
Log.d(TAG, "doInBackground:");
if (params.length != 1) return null;
if (isCancelled()) {
params[0].clearImage();
params[0].clearContext();
return null;
}
// By default, AsyncTask sets the worker thread to have background thread priority, so bump
// it back up so that we save a little quicker.
Process.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND);
Context context = params[0].context;
Bitmap image = params[0].image;
Resources r = context.getResources();
try {
// Create screenshot directory if it doesn't exist
mScreenshotDir.mkdirs();
// media provider uses seconds for DATE_MODIFIED and DATE_ADDED, but milliseconds
// for DATE_TAKEN
long dateSeconds = mImageTime / 1000;
// Save
boolean compressRet = true;
OutputStream out = new FileOutputStream(mImageFilePath);
if(PhoneStatusBar.LEUI_ENABLE) {
compressRet = image.compress(Bitmap.CompressFormat.JPEG, 100, out);
} else {
compressRet = image.compress(Bitmap.CompressFormat.PNG, 100, out);
}
out.flush();
out.close();
if(!compressRet){
//When storage is full screenshot image will compress failed, so we delete the file
File f = new File(mImageFilePath);
if(f.exists()){
f.delete();
Log.d(TAG,"screenshot " + mImageFilePath + " compress failed, so we delete it");
}
params[0].clearImage();
params[0].result = 1;
}else {
// Save the screenshot to the MediaStore
ContentValues values = new ContentValues();
ContentResolver resolver = context.getContentResolver();
values.put(MediaStore.Images.ImageColumns.DATA, mImageFilePath);
values.put(MediaStore.Images.ImageColumns.TITLE, mImageFileName);
values.put(MediaStore.Images.ImageColumns.DISPLAY_NAME, mImageFileName);
values.put(MediaStore.Images.ImageColumns.DATE_TAKEN, mImageTime);
values.put(MediaStore.Images.ImageColumns.DATE_ADDED, dateSeconds);
values.put(MediaStore.Images.ImageColumns.DATE_MODIFIED, dateSeconds);
if(PhoneStatusBar.LEUI_ENABLE) {
values.put(MediaStore.Images.ImageColumns.MIME_TYPE, "image/jpeg");
} else {
values.put(MediaStore.Images.ImageColumns.MIME_TYPE, "image/png");
}
values.put(MediaStore.Images.ImageColumns.WIDTH, mImageWidth);
values.put(MediaStore.Images.ImageColumns.HEIGHT, mImageHeight);
values.put(MediaStore.Images.ImageColumns.SIZE, new File(mImageFilePath).length());
Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
// Create a share intent
String subjectDate = DateFormat.getDateTimeInstance().format(new Date(mImageTime));
Intent sharingIntent = new Intent(Intent.ACTION_SEND);
if(PhoneStatusBar.LEUI_ENABLE) {
sharingIntent.setType("image/jpeg");
} else {
sharingIntent.setType("image/png");
}
sharingIntent.putExtra(Intent.EXTRA_STREAM, uri);
sharingIntent.putExtra(Intent.EXTRA_SUBJECT, mImageFileName);
// Create a share action for the notification
final PendingIntent callback = PendingIntent.getBroadcast(context, 0,
new Intent(context, GlobalScreenshot.TargetChosenReceiver.class)
.putExtra(GlobalScreenshot.CANCEL_ID, mNotificationId),
PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
Intent chooserIntent = Intent.createChooser(sharingIntent, null,
callback.getIntentSender());
chooserIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK
| Intent.FLAG_ACTIVITY_NEW_TASK);
mNotificationBuilder.addAction(R.drawable.ic_screenshot_share,
r.getString(com.android.internal.R.string.share),
PendingIntent.getActivity(context, 0, chooserIntent,
PendingIntent.FLAG_CANCEL_CURRENT));
// Create a delete action for the notification
final PendingIntent deleteAction = PendingIntent.getBroadcast(context, 0,
new Intent(context, GlobalScreenshot.DeleteScreenshotReceiver.class)
.putExtra(GlobalScreenshot.CANCEL_ID, mNotificationId)
.putExtra(GlobalScreenshot.SCREENSHOT_URI_ID, uri.toString()),
PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
mNotificationBuilder.addAction(R.drawable.ic_screenshot_delete,
r.getString(com.android.internal.R.string.delete), deleteAction);
params[0].imageUri = uri;
params[0].image = null;
params[0].result = 0;
}
} catch (Exception e) {
// IOException/UnsupportedOperationException may be thrown if external storage is not
// mounted
Log.e(TAG, "error in SaveImageInBackgroundData, exception:" + e);
params[0].clearImage();
params[0].result = 1;
}
// Recycle the bitmap data
if (image != null) {
image.recycle();
}
return params[0];
}
I am trying to programmatically change a ringtone in API 23.
I have looked at a lot of examples on Stack Overflow and they all seem to "work" (i.e. not crash) but they do not "work" as in the mp3 doesn't become the ringtone - instead the ringtone becomes simply zero noise. So obviously /something/ happened. (no crash, now no ringtone noise)
I have split this off into a small side project to try to isolate it because it's driving me up the wall - I hope maybe you guys can see something I can't!
I have:
placed the mp3 in \res\raw
I have verified I can play the mp3 fine with this code
MediaPlayer mPlayer = MediaPlayer.create(me, R.raw.meepmeep);
mPlayer.start();
added <uses-permission android:name="android.permission.WRITE_SETTINGS"/> to the manifest
managed the api 22 permission scenario in java
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.System.canWrite(this)) {
new AlertDialog.Builder(this)
.setMessage("Please Assign Meep Meep Write Permissions")
.setPositiveButton("OK", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
Intent intent = new Intent(android.provider.Settings.ACTION_MANAGE_WRITE_SETTINGS);
intent.setData(Uri.parse("package:" + getPackageName()));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
startActivity(intent);
} catch (Exception e) {
Log.e("MainActivity", "error starting permission intent", e);
}
}
})
.show();
return;
}
used a method to carefully fetch the mp3 path from the assets folder
public String LoadFile(String fileName, boolean loadFromRawFolder) throws IOException
{
InputStream iS;
if (loadFromRawFolder)
{
int rID = resources.getIdentifier("meep.example.com.meep:raw/"+fileName, null, null);
iS = resources.openRawResource(rID);
}
else
{
iS = resources.getAssets().open(fileName);
}
byte[] buffer = new byte[iS.available()];
iS.read(buffer);
ByteArrayOutputStream oS = new ByteArrayOutputStream();
oS.write(buffer);
oS.close();
iS.close();
return oS.toString();
}
tried to carefully copy the file to the local storage (as a desperate attempt to get it to work as well as tried to assign it from the raw assets lib
String path = "";
try {
LoadFile("meepmeep", true);
} catch (IOException e) {
//display an error toast message
Toast toast = Toast.makeText(me, "File: not found!", Toast.LENGTH_LONG);
toast.show();
}
//copy file to device
File newSoundFile = new File(path);
//Uri mUri = Uri.parse("android.resource://meep.example.com.meep/R.raw.meepmeep");
Uri mUri = MediaStore.Audio.Media.getContentUriForPath(newSoundFile.getAbsolutePath());
ContentResolver mCr = getContentResolver();
AssetFileDescriptor soundFile;
try {
soundFile= mCr.openAssetFileDescriptor(mUri, "r");
try {
byte[] readData = new byte[1024];
FileInputStream fis = soundFile.createInputStream();
FileOutputStream fos = new FileOutputStream(newSoundFile);
int i = fis.read(readData);
while (i != -1) {
fos.write(readData, 0, i);
i = fis.read(readData);
}
fos.close();
} catch (IOException io) {
}
} catch (FileNotFoundException e) {
soundFile=null;
}
ContentValues values = new ContentValues();
values.put(MediaStore.MediaColumns.DATA, newSoundFile.getAbsolutePath());
values.put(MediaStore.MediaColumns.TITLE, "Meep Meep");
values.put(MediaStore.MediaColumns.MIME_TYPE, "audio/mp3");
values.put(MediaStore.MediaColumns.SIZE, newSoundFile.length());
values.put(MediaStore.Audio.Media.ARTIST, "RoadRunner");
//values.put(MediaStore.Audio.Media.DURATION, 230);
values.put(MediaStore.Audio.Media.IS_RINGTONE, true);
values.put(MediaStore.Audio.Media.IS_NOTIFICATION, true);
values.put(MediaStore.Audio.Media.IS_ALARM, true);
values.put(MediaStore.Audio.Media.IS_MUSIC, false);
//Insert it into the database
Uri uri = MediaStore.Audio.Media.getContentUriForPath(newSoundFile.getAbsolutePath());
Uri newUri = mCr.insert(uri, values);
try {
RingtoneManager.setActualDefaultRingtoneUri(
me,
RingtoneManager.TYPE_RINGTONE,
newUri
);
}
catch (Throwable t){
setMessage("meepmeep error");
}
setMessage("meepmeep set");
}
but nothing seems to work. It always fails becuase either soundFile= mCr.openAssetFileDescriptor(mUri, "r"); returns null or if I decline to use that code block and try to change ringtone direct from \res\raw\ folder then it simply gives a blank sound for ringtone.
I am totally stuck for ideas?
The requirements: ensure that the PDF document is deleted from the device after the user has left the PDF viewing screen
The problem: on certain devices (Samsung 4.4.2 and Samsung 4.1.2 for sure, but not Asus 4.2.1) only the first time that the PDF is requested after restarting the application an error message is displayed stating "This document cannot be opened". Thereafter the PDF will load normally. I'm thinking this is a timing issue due to processes that need to be started the first time, but are running after the first attempted load.
The code: note that createFile() is called first, then startActivityForIntentResult()
private File file;
private ArrayList<Uri> uriList = new ArrayList<Uri>();
private void createFile() {
int fileNameLength = pdfFileName[0].length();
String fileName = pdfFileName[0].substring(0, fileNameLength - 4) + DateTime.now();
String fileExtension = pdfFileName[0].substring(fileNameLength - 4, fileNameLength);
byte[] content = Base64.decodeBase64(pdfData[0].getBytes());
BufferedOutputStream outputStream = null;
try {
File path = new File(getExternalFilesDir(null).getAbsolutePath(), "temp");
if (!path.exists()) {
path.mkdirs();
}
file = new File(path, fileName + fileExtension);
outputStream = new BufferedOutputStream(new FileOutputStream(file));
outputStream.write(content);
file.deleteOnExit();
uriList.add(Uri.fromFile(file));
}
catch (FileNotFoundException ex) {
ex.printStackTrace();
}
catch (IOException ex) {
ex.printStackTrace();
}
finally {
try {
if (outputStream != null) {
outputStream.flush();
outputStream.close();
}
}
catch (IOException ex) {
ex.printStackTrace();
}
}
}
private static int REQUEST_CODE = 1;
private Intent intent;
private void startActivityForIntentResult() {
if (file.exists()) {
Uri targetUri = uriList.get(0);
intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(targetUri, "application/pdf");
intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
try {
startActivityForResult(intent, REQUEST_CODE);
}
catch (ActivityNotFoundException e) {
toastTitle = "Error Displaying PDF";
toastMessage = "Please make sure you have an application for viewing PDFs installed on your device and try again.";
toast = new GenericCustomToast();
toast.show(toastTitle, toastMessage, QueryForPDF.this);
}
}
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
if (resultCode == RESULT_CANCELED && requestCode == REQUEST_CODE) {
if(!file.delete()) {
file.delete();
}
}
searchAgain();
}
#Override
public void onBackPressed() {
super.onBackPressed();
if(!file.delete()) {
file.delete();
}
searchAgain();
}
#Override
public void onStop() {
super.onStop();
if(!file.delete()) {
file.delete();
}
}
#Override
public void onDestroy() {
super.onDestroy();
if(!file.delete()) {
file.delete();
}
}
EDIT: I have also tried implementing a callback to be absolutely certain that createFile() has finished it's work. I even tried adding delays (of different time increments) as well as adding (the completely unnecessary) flags for Intent.FLAG_GRANT_READ_URI_PERMISSION, Intent.FLAG_GRANT_WRITE_URI_PERMISSION, and Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION.
I still don't know why this works, but here's the solution in case anyone else runs into this issue:
It's the directory where the file is created. For some reason on the two Samsung devices there was something different in how the files were either accessed or created versus the Asus device. So File path = new File(getExternalFilesDir(null).getAbsolutePath(), "temp"); becomes File path = new File(getExternalCacheDir().getAbsolutePath()); and the problem goes away.
I am trying to download multiple file using AsyncTask.
I start each download in one AsyncTask with progress bar in notification bar but i face many problems.
If i download 4 or 5 files at the same time the one or more files
interrupted without any reason.
If i download 4 or 5 images the one or more images corrupted.
this is the code i used.
private class DownloadFile extends AsyncTask<String, Integer, Long>
{
int nID = 0;
int nDownloadCounter = 0;
public DownloadFile(String sFileName, int nNotificationID) {
this.nID = nNotificationID;
this.NotificationName = sFileName;
this.nDownloadCounter = 0;
}
protected void onPreExecute(){
this.sDownloadPath = sFilePathWithSubDir;
notification = new Notification(R.drawable.app_icon, "Download File", System.currentTimeMillis());
RemoteViews contentView = new RemoteViews(getPackageName(), R.layout.custom_notification);
Intent notificationIntent = new Intent();
File file = new File(this.sDownloadPath + this.NotificationName);
sMediaDataType = GSUtilities.sGetFileMIMEType(this.NotificationName);
notificationIntent.setAction(android.content.Intent.ACTION_VIEW);
notificationIntent.setDataAndType(Uri.fromFile(file), sMediaDataType);
PendingIntent contentIntent = PendingIntent.getActivity(getApplicationContext(), 0, notificationIntent, 0);
contentView.setTextViewText(R.id.status_percent, "0%");
contentView.setTextViewText(R.id.status_fileName, this.NotificationName);
contentView.setProgressBar(R.id.status_progress, 100, 0, false);
notification.contentView = contentView;
notification.contentIntent = contentIntent;
mNotificationManager.notify(nID, notification);
}
protected Long doInBackground(String... urls) {
long retData = 0;
OutputStream fosXSPDatabse = null;
InputStream inServerResponse = null;
URLConnection oURLConnection = null;
URL oURL = null;
try
{
oURL = new URL(oFileManager.sExpiryLink);
oURLConnection = oURL.openConnection();
if (!(oURLConnection instanceof HttpURLConnection))
throw new IOException("Not an HTTP connection");
oURLConnection.connect();
inServerResponse = new BufferedInputStream(oURL.openStream());
if (inServerResponse != null)
{
File fDirectory = new File(oFileManager.sAppFlesDirectory);
if (!fDirectory.exists())
{
if (!fDirectory.mkdir())
{}
}
fosXSPDatabse = new FileOutputStream(oFileManager.sAppFlesDirectory + "/" + oFileInfo.getFileName());
byte data[] = new byte[BUFFER_SIZE];
int nCount = 0;
long lTotalDownloaded = 0;
int nTotalSize = oURLConnection.getContentLength();
while ((nCount = inServerResponse.read(data)) != -1)
{
Log.d(String.valueOf(nID) + " - DoInBackground", String.valueOf(dNotificationbarProgress));
nDownloadCounter ++;
lTotalDownloaded += nCount;
dNotificationbarProgress = lTotalDownloaded * 100.0 / nTotalSize;
if (this.nDownloadCounter == 20 || dNotificationbarProgress == 100) {
publishProgress(Integer.parseInt(new DecimalFormat("#.##").format(dNotificationbarProgress).split("\\.")[0]));
nDownloadCounter = 0;
}
fosXSPDatabse.write(data, 0, nCount);
}
inServerResponse.close();
fosXSPDatabse.flush();
fosXSPDatabse.close();
}
}
catch (MalformedURLException e)
{}
catch (IOException e)
{}
catch (Exception e)
{}
finally
{
try
{
inServerResponse.close();
fosXSPDatabse.flush();
fosXSPDatabse.close();
}
catch (IOException e)
{}
}
return retData;
}
protected void onProgressUpdate(Integer... progress) {
try
{
Log.d(String.valueOf(nID),"onProgressUpdate");
notification.contentView.setProgressBar(R.id.status_progress, 100, progress[0], false);
notification.contentView.setTextViewText(R.id.status_fileName, this.NotificationName);
notification.contentView.setTextViewText(R.id.status_percent, String.valueOf(progress[0]) + "%");
Intent notificationIntent = new Intent();
File file = new File(this.sDownloadPath + this.NotificationName);
sMediaDataType = GSUtilities.sGetFileMIMEType(this.NotificationName);
notificationIntent.setAction(android.content.Intent.ACTION_VIEW);
notificationIntent.setDataAndType(Uri.fromFile(file), sMediaDataType);
PendingIntent contentIntent = PendingIntent.getActivity(getApplicationContext(), 0, notificationIntent, 0);
notification.contentIntent = contentIntent;
mNotificationManager.notify(this.nID, notification);
}
catch(Exception e)
{}
}
protected void onPostExecute(Long result) {
notification.contentView.setTextViewText(R.id.status_fileName, "Successfully installed " + this.NotificationName);
notification.contentView.setTextViewText(R.id.status_percent, "100%");
mNotificationManager.notify(this.nID, notification);
}
}
First, it's never a good idea to silently ignore Exceptions. You should at minumum log them to logcat. It think this is the problem.
Second, you're closing the streams multiple times.
Where are you calling the AsyncTask from?
If it's from an activity and the user navigates away from it, that might be terminated any time the OS wants to free up memory.
If it's from a service, that could be terminated too, but at least it is restarted later. If the service was started from a different process, such as a Sync Adapter, you should also post the PreExecute and PublishProgress and PostExecute code to the UI thread with the application looper handler as a message.
In adition, try limiting the number of concurrent downloads to max 3. Otherwise, an OutOfMemory might occur. You can do this with a BlockingQueue. If concurrency is not important, consider using an IntentService instead of an AsyncTask.
The Best way to handle download multiple files is to use the Service.