Having trouble exporting SQLite Database in Android - java

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" />

Related

Can't upload canvas image to server using retrofit

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

Problem with NfcAdapter's enableForegroundDispatch call in android q (android 10)

I have some issues developing the nfc plugin in Unity.
When nfc tagging, onNewIntent is not called, the app moves to the background and the default nfc tagviewer opens.
It worked on Android 9 and below, but not android 10.
I found a suspicious logs using Logcat.
NfcService: setForegroundDispatch: Caller not in foreground.
2020-04-05 15:33:45.857 32411-32411/? E/class com.package.product.NFCPlugin: call Activity
2020-04-05 15:33:45.857 32411-32411/? E/class com.package.product.NFCPlugin: intent:Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp= com.package.product/.NFCPlugin bnds=[1131,670][1405,1123] (has extras) }
2020-04-05 15:33:45.857 32411-32411/? E/class com.package.product.NFCPlugin: Skill Launcher Cat: LAUNCHER
2020-04-05 15:33:45.858 32411-32411/? E/class com.package.product.NFCPlugin: onResume Activity
2020-04-05 15:33:45.858 32411-32411/? E/class com.package.product.NFCPlugin: Call Check ForegroundDispatch
2020-04-05 15:33:45.858 7904-7904/? I/[LGHome6]Launcher: rebindModel: rebind = false, flag = 0, currentPage = 4, isLandscape = true, mChangedProfileByMultiWindow = false, mOrientationOfCurrentLayout = 0, mWorkspaceLoading = false, mChangedProfile = true, mIsMirrorMode = false
2020-04-05 15:33:45.858 7506-7542/? E/NfcService: setForegroundDispatch: Caller not in foreground.
2020-04-05 15:33:45.859 32411-32735/? W/System.err: javax.net.ssl.SSLException: Write error: ssl=0x7c105b0c48: I/O error during system call, Broken pipe
2020-04-05 15:33:45.859 32411-32411/? E/class com.package.product.NFCPlugin: Equal Result:true
2020-04-05 15:33:45.860 2437-2462/? V/DesktopModeManager: Notification is not exist.
2020-04-05 15:33:45.860 5005-5005/? I/OpaLayout: Setting opa enabled to true
2020-04-05 15:33:45.863 2437-8224/? D/InputDispatcher: Window went away: Window{f88f9f9 u0 com.lge.launcher3/com.lge.launcher3.LauncherExtension}
I have found several places to print the log. As a result, I found the part that outputs the same log in Android Code Search.
https://cs.android.com/android/platform/superproject/+/master:packages/apps/Nfc/src/com/android/nfc/NfcService.java;l=1053;bpv=1;bpt=1?q=NfcService:%20setForegroundDispatch%20enable&ss=android%2Fplatform%2Fsuperproject
#Override
public void setForegroundDispatch(PendingIntent intent,
IntentFilter[] filters, TechListParcel techListsParcel)
{
NfcPermissions.enforceUserPermissions(mContext);
if (!mForegroundUtils.isInForeground(Binder.getCallingUid()))
{
Log.e(TAG, "setForegroundDispatch: Caller not in foreground.");
return;
}
// Short-cut the disable path
if (intent == null && filters == null && techListsParcel == null)
{
mNfcDispatcher.setForegroundDispatch(null, null, null);
return;
}
// Validate the IntentFilters
if (filters != null) {
if (filters.length == 0) {
filters = null;
} else {
for (IntentFilter filter : filters) {
if (filter == null) {
throw new IllegalArgumentException("null IntentFilter");
}
}
}
}
// Validate the tech lists
String[][] techLists = null;
if (techListsParcel != null) {
techLists = techListsParcel.getTechLists();
}
mNfcDispatcher.setForegroundDispatch(intent, filters, techLists);
}
According to the code, if a log occurs, processing is stopped without returning any results. So it seems that the foregroundDispatch does not proceed and goes over.
I've been trying for a long time to solve this issue, but couldn't find the same case, so I think it's a new issue in android 10.
Does anyone know how to fix this?
Lastly, I'm sorry that it was difficult to understand because I used a translation site to write this question.
Below is the code for the plugin.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
m_instance=this;
mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
if (mNfcAdapter == null)
{
finish();
return;
}
pendingIntent = PendingIntent.getActivity(NFCPlugin.this, 0, new Intent(NFCPlugin.this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
IntentFilter _ndfFilter = new IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED);
try
{
_ndfFilter.addDataType("*/*");
}
catch (MalformedMimeTypeException e) {
throw new RuntimeException("Filter fail:", e);
}
IntentFilter _ndfFilter_NDEF = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED);
try {
_ndfFilter_NDEF.addDataType("*/*");
}
catch (MalformedMimeTypeException e) {
throw new RuntimeException("Filter fail:", e);
}
IntentFilter _ndfFilter_Tech = new IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED);
try {
_ndfFilter_Tech.addDataType("*/*");
}
catch (MalformedMimeTypeException e) {
throw new RuntimeException("Filter fail:", e);
}
mIntentFilter = new IntentFilter[] { _ndfFilter ,_ndfFilter_NDEF,_ndfFilter_Tech};
techListsArray = new String[][] {
new String[] {NFCPlugin.class.getName()},
new String[] {TagTechnology.class.getName()},
new String[] {NfcA.class.getName()},
new String[] {NfcB.class.getName()},
new String[] {NfcV.class.getName()},
new String[] {IsoDep.class.getName()},
new String[] {Ndef.class.getName()},
new String[] {NdefFormatable.class.getName()}
};
}
#Override
public void onResume() {
super.onResume();
Log.e(NFCPlugin.class.toString(), "onResume Activity");
mNfcAdapter.enableForegroundDispatch(this, pendingIntent, mIntentFilter, techListsArray);
}
#Override
public void onPause() {
super.onPause();
Log.e(NFCPlugin.class.toString(), "onPause Activity");
mNfcAdapter.disableForegroundDispatch(this);
}
Next is the manifest content.
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.package.product"
xmlns:tools="http://schemas.android.com/tools"
android:installLocation="preferExternal">
<uses-permission android:name="android.permission.NFC" >
</uses-permission>
<uses-feature
android:name="android.hardware.nfc"
android:required="true" >
</uses-feature>
<supports-screens
android:smallScreens="true"
android:normalScreens="true"
android:largeScreens="true"
android:xlargeScreens="true"
android:anyDensity="true"/>
<application
android:theme="#style/UnityThemeSelector"
android:icon="#mipmap/app_icon"
android:label="#string/app_name"
android:networkSecurityConfig="#xml/network_security_config"
>
<activity android:name=".NFCPlugin"
android:launchMode="singleTask"
android:configChanges="keyboard|keyboardHidden|screenLayout|screenSize|orientation"
android:screenOrientation="landscape"
android:label="#string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.nfc.action.TAG_DISCOVERED" />
</intent-filter>
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter>
<action android:name="android.nfc.action.TECH_DISCOVERED"/>
</intent-filter>
<meta-data android:name="android.nfc.action.TECH_DISCOVERED"
android:resource="#xml/tech_list" />
<meta-data android:name="unityplayer.UnityActivity" android:value="true" />
</activity>
</application>
</manifest>
lastly, it is the contents of techList required for TECH_DISCOVERED.
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<tech-list>
<tech>android.nfc.tech.IsoDep</tech>
<tech>android.nfc.tech.NfcA</tech>
<tech>android.nfc.tech.NfcB</tech>
<tech>android.nfc.tech.NfcF</tech>
<tech>android.nfc.tech.NfcV</tech>
<tech>android.nfc.tech.Ndef</tech>
<tech>android.nfc.tech.NdefFormatable</tech>
<tech>android.nfc.tech.MifareClassic</tech>
<tech>android.nfc.tech.MifareUltralight</tech>
</tech-list>
</resources>

"App" has unfortunately stopped working caused by "could not find method android.view.Window$Callback" errors

I am building a mobile app for my project on which I have to retrieve data sent to Thingspeak on it.
When I tap on an image button, it should open a new activity which will show its last value retrieved from Thingspeak.
The issue I am getting is that the app stops working when I click on an image button which is supposed to bring me to another activity. It does open a new activity showing that navigation to another activity is working but then it shows "Smart Agriculture has unfortunately stopped working". I think the problem is in the xml files of the activities or the TextView I have used in some TempHumidity.java file but I don't really know how to resolve them being an absolute beginner in Android Studio. I have 5 files namely activity_main.xml, MainActivity.java, TempHumidity.java, AndroidManifest.xml, imgbtnmenu.xml
TempHumidity.java class(it contains the HttpURLConnection codes)
public class TempHumidity extends AppCompatActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.imgbtnmenu);
new GetMethodDemo().execute("https://thingspeak.com/channels/357670/field/1/last");
}
public class GetMethodDemo extends AsyncTask<String , Void ,String> {
String server_response;
private TextView aTextView;
#Override
protected String doInBackground(String... strings) {
URL url;
HttpURLConnection urlConnection = null;
try {
url = new URL(strings[0]);
urlConnection = (HttpURLConnection) url.openConnection();
int responseCode = urlConnection.getResponseCode();
if(responseCode == HttpURLConnection.HTTP_OK){
server_response = readStream(urlConnection.getInputStream());
Log.v("CatalogClient", server_response);
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
Log.e("Response", "" + server_response);
aTextView.setText(s);
}
}
// Converting InputStream to String
private String readStream(InputStream in) {
BufferedReader reader = null;
StringBuffer response = new StringBuffer();
try {
reader = new BufferedReader(new InputStreamReader(in));
String line = "";
while ((line = reader.readLine()) != null) {
response.append(line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return response.toString();
}
}
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.teerna.smartagriculture">
<uses-permission android:name="android.permission.INTERNET" />
<application
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">
<activity
android:name=".MainActivity"
android:label="#string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".TempHumidity"> // you need to add these 3 lines inside application tag.
</activity>
</application>
</manifest>
imgbtnmenu.xml for the new activity menu(the menu that opens when Humidity and Temperature image button is tapped)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
</LinearLayout>
The messages on Logcat are as follows:
Most of the logcat is only warning, you can see the W/ character. The The app crashes because you're receiving a java.lang.VerifyError which is Could not find class 'android.graphics.drawable.RippleDrawable. This error will only happen on pre-lolipop device (Android 5.0)
Try add these line to build.gradle:
android {
defaultConfig {
vectorDrawables.useSupportLibrary = true
}
}
If it still not working, change app:srcCompat inside every ImageView to android:src

how to copy file from Uri to a specific folder in external data storage

I am trying to create a file organizer app but when created an activity to receive shared audio,video, or image it works fine but the problem that I want to copy that file from Uri to a specific folder in external data storage. I have tried a lot of code and read all the related questions, but no one solve my problem.
this is what I tried:
the permissions are
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
the activity declaration at manifest:
<activity
android:name="nahamsoft.com.Home"
android:label="#string/title_activity_home"
android:theme="#style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.SEND"/>
<action android:name="android.intent.action.SEND_MULTIPLE"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="application/vnd.google.panorama360+jpg"/>
<data android:mimeType="image/*"/>
<data android:mimeType="video/*"/>
<data android:mimeType="audio/*"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="text/plain"/>
</intent-filter>
</activity>
the Java code to receive file is:
if(Intent.ACTION_SEND.equals(action)&& type!=null){
fab.show();
Toast.makeText(this,"send action",Toast.LENGTH_LONG).show();
if("text/plain".equals(type)){
handleSentText(intent);
}else if(type.startsWith("image/")){
try {
handleSentImage(intent);
} catch (IOException e) {
e.printStackTrace();
}
}else if(type.startsWith("video/")){
handleSentVideo(intent);
}else if(type.startsWith("audio/")){
handleSentAudio(intent);
}
}
else{
Toast.makeText(this,"No data were come...",Toast.LENGTH_LONG).show();
}
the methods to copy the file are:
private void handleSentAudio(Intent intent) {
Uri audio=(Uri)intent.getParcelableExtra(Intent.EXTRA_STREAM);
Toast.makeText(this,"audio were handled",Toast.LENGTH_LONG).show();
}
private void handleSentVideo(Intent intent) {
Toast.makeText(this,"Video were handled",Toast.LENGTH_LONG).show();
}
private void handleSentImage(Intent intent) throws IOException {
Uri imageUri=(Uri)intent.getParcelableExtra(Intent.EXTRA_STREAM);
/*try {
MoveFile(imageUri.getPath(),"/sdcard/Alarms/");
} catch (IOException e) {
e.printStackTrace();
}*/
if(imageUri!=null){
Toast.makeText(this,"from:"+imageUri.getPath()+"\n to :"+
Environment.getExternalStorageDirectory().getAbsolutePath()+"/Download/",Toast.LENGTH_LONG).show();
File src=new File(imageUri.toString());
File des=new File(Environment.getExternalStorageDirectory().getAbsolutePath()+"/Download/");
copyFile(src,des);
}else{
Toast.makeText(this,"Image was not handled",Toast.LENGTH_LONG).show();
}
}
private void handleSentText(Intent intent) {
String sharedText=intent.getStringExtra(Intent.EXTRA_TEXT);
Toast.makeText(this,"Text is come...",Toast.LENGTH_LONG).show();
}
copy file method
public static void copyFile(File src, File dst) throws IOException
{
FileChannel inChannel = new FileInputStream(src).getChannel();
FileChannel outChannel = new FileOutputStream(dst).getChannel();
try
{
inChannel.transferTo(0, inChannel.size(), outChannel);
}
finally
{
if (inChannel != null)
inChannel.close();
if (outChannel != null)
outChannel.close();
}
}
what I want is to move the handled file to specific folder which called myFolder.
Replace:
File src=new File(imageUri.toString());
with:
InputStream src=getContentResolver().openInputStream(imageUri);
Then:
Modify copyFile(src,des) to copy an InputStream to your destination
Eventually move this I/O to a background thread, so you do not freeze your UI for the duration of the copy operation

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