Can't upload canvas image to server using retrofit - java

I'm trying to send an image that has been created using Canvas. there is no problem with the image and it can be displayed in ImageView.
this is the error I get:
D/onFailure: /storage/emulated/0/sign_3.png: open failed: ENOENT (No such file or directory)
I got the error even though I got permission to write and read external storage. where I found out from the following logs:
V/ContentValues: Permission is granted
this is my code to send the image:
mainactivityBinding.ivSignature.invalidate();
BitmapDrawable drawable = (BitmapDrawable)
mainactivityBinding.ivSignature.getDrawable();
Bitmap bitmap = drawable.getBitmap();
if(isStoragePermissionGranted()){
String root = Environment.getExternalStorageDirectory().toString();
File myDir = new File(root);
myDir.mkdirs();
String fname = "sign_"+ Preferences.getKeyIdLogin(getBaseContext()) +".png";
file = new File (myDir, fname);
if (file.exists ()) file.delete ();
try {
FileOutputStream out = new FileOutputStream(file);
bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
out.flush();
out.close();
} catch (Exception e) {
e.printStackTrace();
};
}
String token = Preferences.getKeyTokenLogin(getBaseContext());
RequestBody postToken = RequestBody.create(okhttp3.MultipartBody.FORM, token);
RequestBody reqFile = RequestBody.create(MediaType.parse("image/*"), file);
MultipartBody.Partbody = MultipartBody.Part.createFormData("upload", file.getName(), reqFile);
Call<ResponseSign> call =getApi().postSign(postToken, body);
call.enqueue(new Callback<ResponseSign>() {
#Override
public void onResponse(Call<ResponseSign> call, Response<ResponseSign> response) {
String message = response.body().getMessage();
Log.d("RESPON", "onResponse: " + message);
}
#Override
public void onFailure(Call<ResponseEditProfil> call, Throwable t) {
String message = response.body().getMessage();
Log.d("RESPON", "onResponse: " + message);
}
});
this is the code for the given storage permission:
public boolean isStoragePermissionGranted() {
if (Build.VERSION.SDK_INT >= 23) {
if (checkSelfPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED &&
checkSelfPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
Log.v(TAG,"Permission is granted");
return true;
} else {
Log.v(TAG,"Permission is revoked");
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, android.Manifest.permission.READ_EXTERNAL_STORAGE}, 1);
return false;
}
}
else { //permission is automatically granted on sdk<23 upon installation
Log.v(TAG,"Permission is granted");
return true;
}
}
#Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if(grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
Log.v(TAG,"Permission: "+permissions[0]+ "was "+grantResults[0]);
//resume tasks needing this permission
}
}
this is my Retrofit Interface:
#Multipart
#POST("postSign")
Call<ResponseSign> postSign(#Part("token") RequestBody token,
#Part MultipartBody.Part image);
this is the contents of my manifest:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.example.myapps">
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:requestLegacyExternalStorage="true"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/Theme.MyTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
I tried this app on android 11.
what should I do so that the image can be sent to the server? does the image really have to be saved in a file? or it can be sent directly without having to save to a file?
it's my first time trying to upload an image to the server, so sorry if there are some basics I don't understand.
Thank you.

I know where I went wrong, I should have checked whether my device has external memory or not.
If the device does not have external memory, the file will be stored on the internal memory.
if you want to know how to check internal and external memory you can see here:
how to check internal and external storage if exist

Related

Parse error when programmatically installing an APK from Assets Folder

I am trying to install apk programatically from assets folder, But Parse Error
I have Googled for days and fine almost everything is fine but still error exists
Here is my code
AndroidManifest.xml
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_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/file_provider_paths" />
</provider>
</application
file_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="." />
<root-path name="root" path="." />
</paths>
MainActivity
private void installApk() {
File f = new File("file:///android_asset/app.apk");
f.setReadable(true, false);
Uri apkUri = FileProvider.getUriForFile(this, BuildConfig.APPLICATION_ID + ".provider",f);
Intent installIntent = new Intent(Intent.ACTION_INSTALL_PACKAGE);
installIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
installIntent.setDataAndType(apkUri, "application/vnd.android.package-archive");
installIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(installIntent);
}
Checking Permission
private void checkWriteExternalStoragePermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (!getPackageManager().canRequestPackageInstalls()) {
startActivity(new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES).setData(Uri.parse(String.format("package:%s", getPackageName()))));
}
}
if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
installApk();
} else {
installApk();
}
}
private void requestWriteExternalStoragePermission() {
if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MainActivity.this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, MY_PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE);
} else{
ActivityCompat.requestPermissions(MainActivity.this,new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, MY_PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE);
}
}
#Override
public void onRequestPermissionsResult(int requestCode, #NonNull String[] permissions, #NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode==MY_PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE && grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
installApk();
} else {
Toast.makeText(MainActivity.this, "Permission Not Granted.", Toast.LENGTH_SHORT).show();
}
}
File app.apk location : main->assets
Please Note even I write file as
File f = new File(getFilesDir(), "app.apk");
and still parse error
TargetSDK = 31
You cannot install APK directly from assets folder because that folder is not accessible to system's package installer. You must first copy that APK to a shared folder like public documents directory and then pass the URI of file from that directory to intent
val publicDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS)
Edit: Steps to copy file to external storage:
val file = new File("file:///android_asset/app.apk");
val publicDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS)
val destinationAPKFile = File(publicDir, "your_apk_name.apk")
try {
FileUtils.copyFile(file, destinationAPKFile)
}
catch (e: Exception)
{
e.printStackTrace();
}
Then you can use the URI of destinationAPKFile and install APK as you are doing.
Edit 2: In java
File file = new File("file:///android_asset/app.apk");
File publicDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS);
File destinationAPKFile = File(publicDir, "your_apk_name.apk");
try {
FileUtils.copyFile(file, destinationAPKFile)
}
catch (Exception e)
{
e.printStackTrace();
}
Edit 3:
To use FileUtils.copyTo, add following dependency to your app level gradle
implementation group: 'commons-io', name: 'commons-io', version: '2.6'

Take and save photo to network folder Android

I spend +50h in this and I cant find a solution, need some help.
I'm trying to take a photo and save it on NAS folder.
I actually can connect to NAS from Android and create a folder but cant get the photo from camera to copy it on NAS.
Method to open the camera.
private void dispatchTakePictureIntent() {
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
// Ensure that there's a camera activity to handle the intent
if (takePictureIntent.resolveActivity(getApplication().getPackageManager()) != null) {
// Create the File where the photo should go
File photoFile = null;
try {
photoFile = createImageFile();
if (photoFile != null) {
photoURI = FileProvider.getUriForFile(getApplicationContext(),
"p.cristian.sealed.provider",
photoFile);
deleteFile(photoFile.getName());
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
// takePictureIntent.putExtra("picname", photoURI);
startActivityForResult(takePictureIntent, REQUEST_TAKE_PHOTO);
photoFile.deleteOnExit();
}
} catch (Exception ex) {
Log.e("ERROR", ex.getMessage());
}
}
}
private File createImageFile() throws IOException {
String imageFileName = "IMG";
File storageDir =
getApplication().getExternalFilesDir(Environment.DIRECTORY_PICTURES);
File image = null;
image = File.createTempFile(
imageFileName,
".jpg",
storageDir
);
currentPhotoPath = image.getAbsolutePath();
return image;
}
private void algo(String nombreCarpetaNueva, String rutaImagenACopiar){
SmbFileOutputStream out = null;
try {
NtlmPasswordAuthenticator auth = new NtlmPasswordAuthenticator("MyDomain",
"MyUser", "MyPass");
SmbFile smbFile = new SmbFile("smb://" + auth.getUserDomain() + ";" +
auth.getUsername() + ":" + auth.getPassword() + "#" + "192.168.1.50/NAS/Software/" +
nombreCarpetaNueva);
if (!smbFile.exists()) {
smbFile.mkdirs();
}
smbFile.connect();
SmbFile nuevoArchivo = new SmbFile(smbFile + "/" + rutaImagenACopiar);
int cursor;
jcifs.smb1.smb1.SmbFileInputStream in = new
jcifs.smb1.smb1.SmbFileInputStream(nuevoArchivo);
out = new SmbFileOutputStream(nuevoArchivo);
while ((cursor = in.read()) != -1) {
out.write(cursor);
}
out.flush();
out.close();
}
catch (Exception e) {
String msg = "ERROR: " + e.getLocalizedMessage();
Log.i("asdf:",msg);
}
}
#Override
protected void onActivityResult(int requestCode, int resultCode, #Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {
Bundle extras = data.getExtras(); // ALWAYS NULL
String fileName = extras.getString("picname"); //NOT WORKING IS NULL
algo("R_folder", fileName); //this just create the folder but never copy the image.
//algo("R_folder", fileName+".jpg");
}
}
MANIFEST
<uses-permission
android:name="android.permission.AUTHENTICATE_ACCOUNTS"
android:maxSdkVersion="22" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.GET_ACCOUNTS"/>
<uses-permission android:name="android.permission.USE_CREDENTIALS"/>
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS"/>
<uses-feature android:name="android.hardware.camera" android:required="true" />
<uses-feature android:name="android.hardware.camera.autofocus" />
...
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="p.cristian.sealed.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_paths" />
</provider>
FILE_PATHS FILE
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-files-path name="my_images" path="/" />
</paths>
I tested a lot of stackoverflow answers but never got the correct one in my case :(
Some site answers tested from:
Android Camera Intent: how to get full sized photo?
Get file path of image on Android
How to get path of picture in onActivityresult (Intent data is null)
Android doc, and much more sites..
EDIT - GOT THE SOLUTION
private void algo(String rutaImagenACopiar){
SmbFileOutputStream out = null;
try {
NtlmPasswordAuthenticator auth = new NtlmPasswordAuthenticator("MyDomain", "myUser", "MyPass");
File file = new File(rutaImagenACopiar);
String fileName = file.getName();
SmbFile smbFile = new SmbFile("smb://" + auth.getUserDomain() + ";" + auth.getUsername() + ":" + auth.getPassword() + "#" + "192.168.1.50/NAS/Software/" + fileName);
if (!smbFile.exists()) {
smbFile.createNewFile();
//smbFile.mkdirs();
}
smbFile.connect();
InputStream inNew = new FileInputStream(file);
SmbFileOutputStream outNew = new SmbFileOutputStream(smbFile);
byte[] buf = new byte[8192];
int len;
while ((len = inNew.read(buf)) > 0) {
outNew.write(buf, 0, len);
}
inNew.close();
outNew.close();
}
Now I just need to get the correct buffer size

How to fix the Camera API?

Hi i am trying to open a camera and i saw the documentation of android on how to use it.
so i just copy paste it to my code, and i already set the permission of it, but when i am clicking the button, the camera is not opening.
#Override
protected void onActivityResult(int requestCode, int resultCode, #Nullable Intent data) {
if(requestCode == CAMERA_REQUEST_CODE){
Log.d("ano_code",Integer.toString(requestCode));
if(resultCode == Activity.RESULT_OK){
File file = new File(currentPhotoPath);
selectedImage.setImageURI(Uri.fromFile(file));
}else{
Log.d("mali","mali nga");
}
}
if(requestCode == GALLERY_REQUEST_CODE){
if(resultCode == Activity.RESULT_OK){
Uri contentUri = data.getData();
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
String imageFileName = "JPEG_" + timeStamp +"."+getFileExt(contentUri);
Log.d("tag", "onActivityResult: Gallery Image Uri: " + imageFileName);
selectedImage.setImageURI(contentUri);
}
}
}
private String getFileExt(Uri contentUri) {
ContentResolver c = getContentResolver();
MimeTypeMap mime = MimeTypeMap.getSingleton();
return mime.getExtensionFromMimeType(c.getType(contentUri));
}
private File createImageFile() throws IOException {
// Create an image file name
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
String imageFileName = "JPEG_" + timeStamp + "_";
//File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES);
File storageDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
File image = File.createTempFile(
imageFileName, /* prefix */
".jpg", /* suffix */
storageDir /* directory */
);
// Save a file: path for use with ACTION_VIEW intents
currentPhotoPath = image.getAbsolutePath();
return image;
}
private void dispatchTakePictureIntent() {
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
// Ensure that there's a camera activity to handle the intent
if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
// Create the File where the photo should go
File photoFile = null;
try {
photoFile = createImageFile();
} catch (IOException ex) {
}
// Continue only if the File was successfully created
if (photoFile != null) {
Log.d("qqqqq","dito na ao4");
Uri photoURI = FileProvider.getUriForFile(this,
"com.example.test.android.fileprovider",
photoFile);
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
startActivityForResult(takePictureIntent, CAMERA_REQUEST_CODE);
}
}
}
Manifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.test">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-feature android:name="android.hardware.camera" android:required="true"/>
<!--This should have the Activity files ONLY-->
<application
android:hardwareAccelerated="false"
android:largeHeap="true"
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/AppTheme.NoActionBar">
<activity
android:name=".Utilities.SplashScreen"
android:theme="#style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.example.test.android.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_paths"></meta-data>
</provider>
</application>
</manifest>
i really don't know what to do, there is no error, does anyone encounter this too?
here is the link of the documentation.
https://developer.android.com/training/camera/photobasics
can u declare provider in manifest? yes then paste error message from logcat.

Having trouble exporting SQLite Database in Android

I have an application that I want to export the current SQLite database to the Internal storage Downloads folder of the device. I am getting the below error.
java.io.FileNotFoundException:
/storage/emulated/0/Download/Pipe_Tally: open failed: EACCES
(Permission denied)
I have what I think are the correct permissions included in the AndroidManifest.xml file for reading and writing to storage.
Here is the AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.example.bigdaddy.pipelinepipetally">
<uses-feature android:name="android.hardware.camera2"
android:required="true"/>
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="18"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS"/>
<application
android:allowBackup="true"
android:debuggable="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:supportsRtl="true"
android:theme="#style/AppTheme"
tools:ignore="HardcodedDebugMode">
<activity
android:name=".MainActivity"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity
android:name=".TallyActivity2"
android:windowSoftInputMode="adjustResize">
</activity>
<activity
android:name=".JobAndDbActivity"
android:windowSoftInputMode="adjustResize">
</activity>
<activity
android:name=".ExistingTallyActivity"
android:windowSoftInputMode="adjustResize">
</activity>
<activity android:name=".ImageToFullscreen"
android:windowSoftInputMode="adjustResize">
</activity>
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.example.bigdaddy.pipelinepipetally.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_paths">
</meta-data>
</provider>
</application>
</manifest>
Here is the method I am trying to use to create the file for the /Downloads/ folder within the internal device storage:
public void backUpDatabase() {
/* Open your local db as the input stream */
DBHelper anotherDbHelper = null;
try {
try {
anotherDbHelper = new DBHelper(ExistingTallyActivity.this);
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
String path = null;
if (anotherDbHelper != null) {
path = String.valueOf(getApplicationContext().getDatabasePath(anotherDbHelper.getDatabaseName()));
Log.i(TAG, "backUpDatabase: 1" +path);
}
File dbFile = null;
if (path != null) {
dbFile = new File(path);
Log.i(TAG, "backUpDatabase: 2" + String.valueOf(dbFile));
}
FileInputStream fis = null;
try {
if (dbFile != null) {
fis = new FileInputStream(dbFile);
Log.i(TAG, "backUpDatabase: 3" + String.valueOf(dbFile));
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
String outFileName = (Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath()+ "/Pipe_Tally/");
Log.i(TAG, "backUpDatabase: 4"+ outFileName);
// Open the empty db as the output stream
OutputStream outputStream = null;
//FileOutputStream outputStream = null;
try {
outputStream = new FileOutputStream(outFileName);
Log.i(TAG, "backUpDatabase: 5"+ outFileName);
Log.i(TAG, "backUpDatabase: 6"+ String.valueOf(outputStream));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
// Transfer bytes from the input-file to the output-file
byte[] buffer = new byte[1024];
int length;
try {
if (fis != null) {
while ((length = fis.read(buffer)) > 0) {
try {
if (outputStream != null) {
outputStream.write(buffer, 0, length);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
//Close the streams
try {
if (outputStream != null) {
outputStream.flush();
outputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (fis != null) {
fis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
Here is the method I am using to check for the correct permissions at Runtime:
private boolean checkSdCardPermissions() {
/* Checking here if we already have permissions*/
if (ActivityCompat.checkSelfPermission(ExistingTallyActivity.this,
Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED
&& ActivityCompat.checkSelfPermission(ExistingTallyActivity.this,
Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { /* Added this for Write External Storage permissions */
/* Checking to see if we are equal to or at a higher version than Marshmallow */
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
/* System default popup box here to ask for permissions. */
ActivityCompat.requestPermissions(ExistingTallyActivity.this,
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
MY_PERMISSION_READ_EXTERNAL_STORAGE);
/* Added this today for trying to prompt for writing external storage*/
ActivityCompat.requestPermissions(ExistingTallyActivity.this,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
MY_PERMISSION_WRITE_EXTERNAL_STORAGE);
}
return true;
} else {
return false;
}
}
This is the onOptionsItemSelected() method that has the switch in it to handle the menu and what is selected. The case:R.id.menu_save_and_export:
is where I am checking if checkSdCardPermissions() method is true, then calling the backUpDatabase() method.
#Override
public boolean onOptionsItemSelected(MenuItem menuItem) {
switch (menuItem.getItemId()) {
/*
When Tally New Pipe is selected from the Menu, it sends the user to the TallyActivity
page, all encapsulated in a runnable and passed to thread below.
*/
case R.id.menu_new_pipe:
class ToTallyActivityManager implements Runnable {
#Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
Intent intentToTallyActivity =
new Intent(ExistingTallyActivity.this, TallyActivity2.class);
startActivity(intentToTallyActivity); /* Heading to TallyActivity.*/
} /* run method ends here. */
} /* Runnable ends here. */
/* This thread takes care of the call to go to TallyActivity page from here.*/
mThreadToTallyActivity = new Thread(new ToTallyActivityManager());
mThreadToTallyActivity.start();
break;
/* When selected, sends the user to the MainActivity page. */
case R.id.menu_to_main:
Intent intentToMainActivity =
new Intent(ExistingTallyActivity.this, MainActivity.class);
startActivity(intentToMainActivity); // Heading to MainActivity
break;
/* When chosen allows the option to save and export the current DB.*/
case R.id.menu_save_and_export:
if (checkSdCardPermissions()) {
try {
backUpDatabase();
} catch (/*IOException*/ SQLException e) {
e.printStackTrace();
}
}
break;
/* When chosen from the menu, this kills the app completely. Has popup embedded.*/
default:
AlertHelperDialog.displayExitAlertDialog(ExistingTallyActivity.this);
} /* switch/case ends here */
return super.onOptionsItemSelected(menuItem);
} /* onOptionsItemSelected method ends here. */
I really appreciate any help or advice with fixing this issue. Thank you.
You have a android:maxSdkVersion="18" set on the WRITE_EXTERNAL_STORAGE permission. If you are running on a phone with SDK > 18 the app will not have permission to WRITE_EXTERNAL_STORAGE. So I suggest you remove the android:maxSdkVersion attribute. Read the documentation.
android:maxSdkVersion : The highest API level at which this permission should be granted to your app. Setting this attribute is useful if the permission your app requires is no longer needed beginning at a certain API level.
Change it as follows,
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

Android MediaPlayer playback stutters over wired headphones, not over Bluetooth

I have a simple music player app (source) which has had playback issues in Lollipop when using headphones. Music will play normally for anywhere from 30 seconds to 5 minutes, then will pause for ~2-4 seconds, then resume.
The behavior seems to generally occur while the screen is off, but acquiring a CPU wakelock didn't help.
The frequency of the pauses seems to accelerate over time. At first it's once per hour, but then the time between pauses decreases by about half each time, until it's pausing almost every minute.
I've observed this behavior with iTunes encoded aac files, others have observed it with mp3s.
This has only been observed while playing over wired headphones. I have never experienced this behavior on a Bluetooth headset.
What could be causing this? It seems like a process priority issue, but I don't know how to address that kind of problem.
I haven't experienced this on Android 4.x.
Here's the Github ticket for this issue.
Here are some relevant bits of source code:
Manifest
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.smithdtyler.prettygoodmusicplayer"
android:versionCode="65"
android:versionName="3.2.14" >
<uses-sdk
android:minSdkVersion="16"
android:targetSdkVersion="19" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<application
android:allowBackup="true"
android:icon="#drawable/ic_pgmp_launcher"
android:label="#string/app_name"
android:theme="#style/AppBaseTheme" >
<!-- Set the artist list to launch mode single task to prevent multiple instances -->
<!-- This fixes an error where exiting the application just brings up another instance -->
<!-- See https://developer.android.com/guide/topics/manifest/activity-element.html#lmode -->
<activity
android:name="com.smithdtyler.prettygoodmusicplayer.ArtistList"
android:label="#string/app_name"
android:launchMode="singleTask" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.CATEGORY_APP_MUSIC " />
</intent-filter>
</activity>
<activity
android:name="com.smithdtyler.prettygoodmusicplayer.SettingsActivity"
android:label="#string/title_activity_settings" >
</activity>
<activity
android:name="com.smithdtyler.prettygoodmusicplayer.AlbumList"
android:label="#string/title_activity_album_list" >
</activity>
<activity
android:name="com.smithdtyler.prettygoodmusicplayer.SongList"
android:label="#string/title_activity_song_list" >
</activity>
<activity
android:name="com.smithdtyler.prettygoodmusicplayer.NowPlaying"
android:exported="true"
android:label="#string/title_activity_now_playing" >
</activity>
<!--
The service has android:exported="true" because that's needed for
control from the notification. Not sure why it causes a warning...
-->
<service
android:name="com.smithdtyler.prettygoodmusicplayer.MusicPlaybackService"
android:exported="true"
android:icon="#drawable/ic_pgmp_launcher" >
</service>
<receiver
android:name="com.smithdtyler.prettygoodmusicplayer.MusicBroadcastReceiver"
android:enabled="true" >
<intent-filter android:priority="2147483647" >
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
</application>
</manifest>
MusicPlaybackService.onCreate()
#Override
public synchronized void onCreate() {
Log.i(TAG, "Music Playback Service Created!");
isRunning = true;
sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
powerManager =(PowerManager) getSystemService(POWER_SERVICE);
wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
"PGMPWakeLock");
random = new Random();
mp = new MediaPlayer();
mp.setOnCompletionListener(new OnCompletionListener() {
#Override
public void onCompletion(MediaPlayer mp) {
Log.i(TAG, "Song complete");
next();
}
});
// https://developer.android.com/training/managing-audio/audio-focus.html
audioFocusListener = new PrettyGoodAudioFocusChangeListener();
// Get permission to play audio
am = (AudioManager) getBaseContext().getSystemService(
Context.AUDIO_SERVICE);
HandlerThread thread = new HandlerThread("ServiceStartArguments");
thread.start();
// Get the HandlerThread's Looper and use it for our Handler
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
// https://stackoverflow.com/questions/19474116/the-constructor-notification-is-deprecated
// https://stackoverflow.com/questions/6406730/updating-an-ongoing-notification-quietly/15538209#15538209
Intent resultIntent = new Intent(this, NowPlaying.class);
resultIntent.putExtra("From_Notification", true);
resultIntent.putExtra(AlbumList.ALBUM_NAME, album);
resultIntent.putExtra(ArtistList.ARTIST_NAME, artist);
resultIntent.putExtra(ArtistList.ARTIST_ABS_PATH_NAME, artistAbsPath);
// Use the FLAG_ACTIVITY_CLEAR_TOP to prevent launching a second
// NowPlaying if one already exists.
resultIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
resultIntent, 0);
Builder builder = new NotificationCompat.Builder(
this.getApplicationContext());
String contentText = getResources().getString(R.string.ticker_text);
if (songFile != null) {
contentText = Utils.getPrettySongName(songFile);
}
Notification notification = builder
.setContentText(contentText)
.setSmallIcon(R.drawable.ic_pgmp_launcher)
.setWhen(System.currentTimeMillis())
.setContentIntent(pendingIntent)
.setContentTitle(
getResources().getString(R.string.notification_title))
.build();
startForeground(uniqueid, notification);
timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
public void run() {
onTimerTick();
}
}, 0, 500L);
Log.i(TAG, "Registering event receiver");
mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
// Apparently audio registration is persistent across lots of things...
// restarts, installs, etc.
mAudioManager.registerMediaButtonEventReceiver(cn);
// I tried to register this in the manifest, but it doesn't seen to
// accept it, so I'll do it this way.
getApplicationContext().registerReceiver(receiver, filter);
headphoneReceiver = new HeadphoneBroadcastReceiver();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("android.intent.action.HEADSET_PLUG");
registerReceiver(headphoneReceiver, filter);
}
MusicPlaybackService.startPlayingFile()
private synchronized void startPlayingFile(int songProgress) {
// Have we loaded a file yet?
if (mp.getDuration() > 0) {
pause();
mp.stop();
mp.reset();
}
// open the file, pass it into the mp
try {
fis = new FileInputStream(songFile);
mp.setDataSource(fis.getFD());
mp.prepare();
if(songProgress > 0){
mp.seekTo(songProgress);
}
wakeLock.acquire();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
MusicPlaybackService Timer Task
private void onTimerTick() {
long currentTime = System.currentTimeMillis();
if (pauseTime < currentTime) {
pause();
}
updateResumePosition();
sendUpdateToClients();
}
private void updateResumePosition(){
long currentTime = System.currentTimeMillis();
if(currentTime - 10000 > lastResumeUpdateTime){
if(mp != null && songFile != null && mp.isPlaying()){
int pos = mp.getCurrentPosition();
SharedPreferences prefs = getSharedPreferences("PrettyGoodMusicPlayer", MODE_PRIVATE);
Log.i(TAG,
"Preferences update success: "
+ prefs.edit()
.putString(songFile.getParentFile().getAbsolutePath(),songFile.getName() + "~" + pos)
.commit());
}
lastResumeUpdateTime = currentTime;
}
}
private void sendUpdateToClients() {
List<Messenger> toRemove = new ArrayList<Messenger>();
synchronized (mClients) {
for (Messenger client : mClients) {
Message msg = Message.obtain(null, MSG_SERVICE_STATUS);
Bundle b = new Bundle();
if (songFile != null) {
b.putString(PRETTY_SONG_NAME,
Utils.getPrettySongName(songFile));
b.putString(PRETTY_ALBUM_NAME, songFile.getParentFile()
.getName());
b.putString(PRETTY_ARTIST_NAME, songFile.getParentFile()
.getParentFile().getName());
} else {
// songFile can be null while we're shutting down.
b.putString(PRETTY_SONG_NAME, " ");
b.putString(PRETTY_ALBUM_NAME, " ");
b.putString(PRETTY_ARTIST_NAME, " ");
}
b.putBoolean(IS_SHUFFLING, this._shuffle);
if (mp.isPlaying()) {
b.putInt(PLAYBACK_STATE, PlaybackState.PLAYING.ordinal());
} else {
b.putInt(PLAYBACK_STATE, PlaybackState.PAUSED.ordinal());
}
// We might not be able to send the position right away if mp is
// still being created
// so instead let's send the last position we knew about.
if (mp.isPlaying()) {
lastDuration = mp.getDuration();
lastPosition = mp.getCurrentPosition();
}
b.putInt(TRACK_DURATION, lastDuration);
b.putInt(TRACK_POSITION, lastPosition);
msg.setData(b);
try {
client.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
toRemove.add(client);
}
}
for (Messenger remove : toRemove) {
mClients.remove(remove);
}
}
}
I got a really helpful response from the developer of the Vanilla Music Player:
We use a separated thread to read-ahead the currently playing file:
-> The thread reads the file with about 256kb/s, so it will read the file faster than mediaserver does
-> This gives the file a very good chance to stay in the page/disk cache
-> ..and this minimizes the chance for 'drop outs' due to funky sd-cards or other IO-pauses.
The code is located here: https://github.com/vanilla-music/vanilla/blob/master/src/ch/blinkenlights/android/vanilla/ReadaheadThread.java
The code does not depend on any parts of vanilla music: if you would like to give it a try, just drop it into your project and do something like:
onCreate {
...
mReadaheadThread = new ReadaheadThread()
...
}
...
mMediaPlayer.setDataSource(path);
mReadaheadThread.setDataSource(path);
...
Since implementing this change I haven't encountered the problem.

Categories