How can I share a db file between 2 applications - java

since Enviroment.getExternalStorageDirectory() is deprecated I have a little Problem. I have 2 applications (a lite and a pro version of an app). These apps have a database which I could export and import. When switching from lite to pro app, the user could import the "old" database into the new app. The database was stored under a folder inside his "sdcard". But now on Android 12 devices we no longer have access to that External Storage.
So how could I solve the problem?
Cause I think, context.getExternalFilesDir((Environment.DIRECTORY_DOCUMENTS)
doesn't work cause the namespace of both apps are different. And I guess it will not work to open a file which is not located under the namespace of the app the file is requested.

Yes, sharing the app's shared memory with another one is not possible.
Other options are using a external server / Firbase to create profile for users and store there data in it.

The easiest way is not to have two apps. Make the single app work for both modes, and gate features at runtime based on the type of license. You can even make the upgrade an in app purchase.

I found a solution by myself, but if someone else is looking for a solution it might be helpful.
I ask the user to specify a file location via
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("application/db");
intent.putExtra(Intent.EXTRA_TITLE, "xyz.db");
mCreateNewDatabaseFile.launch(intent);
then in the mCreateNewDatabaseFile
private final ActivityResultLauncher<Intent> mCreateNewDatabaseFile = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
if (result.getResultCode() == RESULT_OK) {
if (result.getData() != null && result.getData().getData() != null) {
Uri path = result.getData().getData();
getContentResolver().takePersistableUriPermission(path, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
getContentResolver().takePersistableUriPermission(path, Intent.FLAG_GRANT_READ_URI_PERMISSION);
mPreferences.edit().putString(PREF_KEY_DATABASE_EXPORT_PATH, path.toString()).apply();
}
}
});
So even with another process I can read and write the file.
So in the lite Version I can create the db file and in the pro version I can let the user choose this file for import.
I hope it will help others with similar questions.

You can (should) use a ContentProvider to have other apps query your database using accessible methods of your ContentProvider :
you can configure a content provider to allow other applications to
securely access and modify your app data
(this will enforce Single-responsibility principle)

Related

How to delete a folder on Android 11?

I have a doubt, how I can delete a folder on Android 11 (or 10)?
Have much answers here on Stack how to do it, but nothing of worked.
This code worked for me on Android 5:
public static boolean deleteDir(File dir) {
if (dir.isDirectory()) {
String[] children = dir.list();
for (int i=0; i<children.length; i++) {
boolean success = deleteDir(new File(dir, children[i]));
if (!success) {
return false;
}
}
}
// The directory is now empty so delete it
return dir.delete();
}
On newest versions of Android it not work. I noticed that there are applications that can to do this, so I imagine it is possible. Any answer about it?
Android has done much to change its permission models around file access since Android 10 and 11. The preferred approach is to use Scoped Storage APIs.
Many of the old file read/write permissions are on life support and will no longer work. If they do continue to work, then must justify to Google why you need them, and then go through a security approval by Google. You will only be granted approval if you app is classified as a file browser application, or the like.
Basically, the new model is to use Android's file browsers to gain access to read/write/update a particular file that the user selects, and defer the rest of the file management to Google's first-party applications. The access you get is based on what the user selected in the first-party file browser. You are then handed a URI back to your application with the proper permissions to perform the intended action, such as read/write/etc...
You may find this video useful:
https://www.youtube.com/watch?v=RjyYCUW-9tY
Have you tried Context.deleteFile() ?
getApplicationContext().deleteFile(filename);

Public Storage Folder API 30 Android 11

Android 11,
I'm trying to create a publicly accessible folder I can store my media files within, which will contain 1 folder and 1 text file per game type (there could be many), I do not want any other application to have access to the root folder for the exception of file explorers, as the user 'could' have content inside the folder that is R18 restricted or might not, I'm not in control of what content goes in there, it would be nice if I could provide that so that a child doesn't in mistakenly bump into the content while looking for pictures on the device for barnie.
I found some code on GitHub that did just the above on Android 10 and works flawlessly for Android 10, but cannot find anything but blue pills about it when it comes to Android 11, help in the form of example code would be great, yes I know targeting only Android 11 limits me but I'd rather live with the limit than play with many different versions of code.
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply {
flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
}
startActivityForResult(intent, OPEN_FOLDER_REQUEST_CODE)
EDIT 2
After some hacking around, because 'startActivityForResult(Intent!, Int): Unit' is deprecated. Deprecated in Java, I end up with this, results is a URI, can I now use this to pass files to other API calls now?
pref = getSharedPreferences("myPref", Context.MODE_PRIVATE)
var userFolderData: String? = pref.getString("userFolderData", "")
if (userFolderData=="") {
val getUserFolderData =
registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) {
mytools.debug(it.toString())
pref.edit().putString("userFolderData",it.toString()).apply()
userFolderData = it.toString()
}
getUserFolderData.launch("".toUri())
}
mytools.debug("userFolderData = ${userFolderData}")
EDIT 3
So I ran a test on the URI returned; always the same story not matter what I do, yes that's pretty ugly hack but it's the easiest way I can find to test it.
ivtitleimage.setImageURI("content://com.android.externalstorage.documents/document/primary%3ATest%2FMelsDeck%2Fbendover01.jpg".toUri())
31833-31833/com.example.cardgamexxx E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.cardgamexxx, PID: 31833
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.cardgamexxx/com.example.cardgamexxx.MainActivity}: java.lang.SecurityException: Permission Denial: opening provider com.android.externalstorage.ExternalStorageProvider from ProcessRecord{5a390cf 31833:com.example.cardgamexxx/u0a741} (pid=31833, uid=10741) requires that you obtain access using ACTION_OPEN_DOCUMENT or related APIs
EDIT 4
Right, so user selects folder/creates one with registerForActivityResult code above, I place files in it with Cx File Explorer and automagically drum roll! ...
I do not have read access to the file placed there, anyway thought I'd comment here as I don't want people stumbling on the post and thinking this works, because it doesn't.
On an Android 11 device your app can only create folders on root of external storage in the classic way if it has requested 'all files acces'.
Or it can use Storage Access Framework and for instance ACTION_OPEN_DOCUMENT_TREE to let the user create a folder in root of external storage and then select it.

Android Q - Delete Media (Audio) File

I've been trying to get my app to be able to delete an audio file. However, after trying many possible solutions, I couldn't really find one that works.
Here is my solution so far:
public static void deleteFiles(List<Track> tracks, Context context,
final MutableLiveData<IntentSender> deletionIntentSenderLD){
final Uri AUDIO_URI = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
for(Track t : tracks){
try {
context.getContentResolver().delete(ContentUris
.withAppendedId(AUDIO_URI, t.getUriId()), null, null);
}catch (SecurityException securityException) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
if (securityException instanceof RecoverableSecurityException) {
deletionIntentSenderLD
.postValue(((RecoverableSecurityException) securityException)
.getUserAction().getActionIntent().getIntentSender());
} else
throw securityException;
} else
throw securityException;
}
}
}
When the try block fails a SecurityException is catch then the IntentSender is passed to the live data that is observed in a fragment:
audioViewModel.getDeletionIntentSenderLD().observe(getViewLifecycleOwner(),
intentSender -> {
try {
startIntentSenderForResult(intentSender, DELETE_PERMISSION_REQUEST,
null, 0 ,0, 0,
null);
} catch (IntentSender.SendIntentException e) {
e.printStackTrace();
}
});
I've tried implementing the onRequestPermissionResult() method but that doesn't do anything. I've also tried deleting the files using File file = new File(), however, due to the changes made to Android 10, I didn't expect it to work.
So after many Google searches, I've come to the conclusion that the best approach (to my knowledge) is to simply turn off scoped storage for Android Q (10).
Here, I'll provide two solutions. The first is the one where I turn it off and the second is the one where scope storage is still enable. However, a thing you should note is that the second solution is a little buggy, at times it actually does delete both the actual media file and updates the Media Store, but most times it simply deletes from the Media Store only. Obviously, this isn't a very good solution as on reboot your application would then load those files back in because the Media Store would scan for them.
Solution 1 - Turn off Scoped Storage
For this solution you can still target Android 11. All you have to do is go to the build.gradle file at the Module Level and set the compileSdkVersion and targetSdkVersion to 30.
After that, you go into the AndroidManifest.xml and have the uses-permission and application tag set up like this:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="29"
tools:ignore="ScopedStorage"/>
<application
android:requestLegacyExternalStorage="true"
...
After having done that, you could use the Content Resolver to delete the media file (and update the Media Store) and you do not have to worry about catching a security exception like its said in the Android docs. Your implementation for Android 11s delete operation should not be affected.
Solution-ish 2 - Turn on Scoped Storage
Firstly, in your manifest ensure that the WRITE_EXTERNAL_STORAGE permissions maxSdkVersion is set to 28. Also ensure that requestLegacyExternalStorage is set to false (don't think this is required). Then simply copy the code in my original post. You do not require a Live Data if you are doing the delete operation from your activity/fragment. But you should note that startIntentSenderForResult() requires an activity.
But as I mentioned before, I did experience some bugs with this. The most frustrating thing about this solution though is that it does not delete the actual file but instead deletes the entry from the Media Store. Maybe this has something to do with the fact that #blackapps mentioned, which is that you cannot bulk delete and I might have implemented it slightly wrong. Nevertheless, this is horrible for user experience if bulk deletion is impossible in Android 10.
The tutorials I followed for this are:
https://developer.android.com/training/data-storage/shared/media#remove-item
https://www.raywenderlich.com/9577211-scoped-storage-in-android-10-getting-started#toc-anchor-007
https://www.solutionanalysts.com/blog/scoped-storage-in-android-10/
Side Note - Delete on Android 11
To delete on Android 11 you just need to call createDeleteRequest() which should return a PendingIntent. From this PendingIntent you could get the IntentSender by using getIntentSender. Pass this intent sender to the activity/fragment then call startIntentSenderForResult() in your activity/fragment. This pops up a dialog to the user asking them if the application can delete a file. If the user gives permission the system goes ahead and deletes the file and updates the Media Store.
Side Side Note - Scoped Storage, Android 10 and Future
From everything I've seen, it seems to suggest that scoped storage is only enforced in Android 11 but I'm not entirely sure if the legacy option would still be available in Android 10 indefinitely. But I would have to do more research on this...

Reliable way of detecting whether an Android app is running in 'BlueStacks'

I would like to ascertain at run-time inside an Android app whether it is running within the BlueStacks Android emulator. This is so I can modify the way the app runs when running inside BlueStacks.
BlueStacks does not support multi-touch so I want to implement an alternative to the standard pinch-to-zoom functionality my current app has.
E.g.
If (appIsRunningInBlueStacks){
mySurfaceView.enableMultiTouchAlternatives();
} else{
mySurfaceView.enableMultiTouchFeatures();
}
What is a reliable way of ascertaining the value of appIsRunningInBlueStacks?
EDIT Summary of answers to comments on question:
Ben, Taras, thanks for the suggestions. The Build.MODEL etc. values for BlueStacks are:
Model: "GT-I9100"
Manufacturer: "samsung"
Device: "GT-I9100"
Product: "GT-I9100"
This is the same model number as the Samsung Galaxy SII so it would not be ideal to use this for fear of treating all users with SIIs the same as those on BlueStacks.
CommonsWare, the app continues to run in BlueStacks even with the < uses-feature> for multitouch in the manifest. In fact (also answering iagreen's question)...
packageManager.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT);
... returns true! This is to be expected I suppose as the emulator is convinced it is a Samsung Galaxy SII!
Therefore we are still without a way of reliably detecting whether an app is running on BlueStacks without also throwing all Samsung Galaxy SII users in the same bucket. Any other ideas?
All the above methods are not working on BlueStacks 5. The correct way to do is checking if the path of /mnt/windows/BstSharedFolder exists. It is working fine on both BlueStacks 4 and 5.
fun checkFilesExist(files: Array<String>): Boolean {
files.forEach {
val file = File(it)
if (file.exists()) {
return true
}
}
return false
}
fun isBlueStacks(): Boolean {
val BLUE_STACKS_FILES = arrayOf(
"/mnt/windows/BstSharedFolder"
)
return checkFilesExist(BLUE_STACKS_FILES)
}
You can check that the Bluestacks shared folder exist
/sdcard/windows/BstSharedFolder
Boolean onBlueStacks()
{
File sharedFolder = new File(Environment
.getExternalStorageDirectory().toString()
+ File.separatorChar
+ "windows"
+ File.separatorChar
+ "BstSharedFolder");
if (sharedFolder.exists())
{
return true;
}
return false;
}
After trying all the suggested solutions available online we found the Google's SafetyNet Attestation API is the only solution for detecting VMs like BlueStack(any version) and NoxPlayer.
Apps that care about content piracy (and other security issues) can filter their availability on the Google Play like Netflix filters devices on the PlayStore.
The new “device catalog” section of the console includes an option
called “SafetyNet exclusion,” which can be used to prevent “devices
that fail integrity tests or those that are uncertified by Google,”
from downloading a specific app: among these would be rooted devices
and those running custom ROMs.
But there is a catch user will still find the APK from cross-sharing or other distribution systems, so the client must implement SafetyNet Attestation API on the app level.
How does it work?
SafetyNet examines software and hardware information on the device
where your app is installed to create a profile of that device. The
service then attempts to find this same profile within a list of
device models that have passed Android compatibility testing. The API
also uses this software and hardware information to help you assess
the basic integrity of the device, as well as the APK information of
the calling app. This attestation helps you to determine whether or
not the particular device has been tampered with or otherwise
modified.
It's an (easy to implement) paid API from the Google which allows 10,000 free hits per day as of now :\
If anyone is interested in detecting VMs by them self, these are the good papers available suggesting heuristic approaches :
Evading Android Runtime Analysis via Sandbox Detection
Rage Against the Virtual Machine:
Hindering Dynamic Analysis of Android Malware
My version of BlueStacks is reporting my Build.Model as GT-N7100.
Using: android.opengl.GLES20.glGetString(android.opengl.GLES20.GL_RENDERER) I get Bluestacks.
It maybe too late but for the sake of others who have the same problem :
public boolean isRunningOnEmulator() {
return Build.FINGERPRINT.startsWith("generic")
|| Build.FINGERPRINT.startsWith("unknown")
|| Build.MODEL.contains("google_sdk")
|| Build.MODEL.contains("Emulator")
|| Build.MODEL.contains("Android SDK built for x86")
|| Build.MANUFACTURER.contains("Genymotion")
|| (Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic"))
|| "google_sdk".equals(Build.PRODUCT)
|| Build.PRODUCT.contains("vbox86p")
|| Build.DEVICE.contains("vbox86p")
|| Build.HARDWARE.contains("vbox86");
}
Based on Mr. Regis' answer, you can detect it when the shared folder is present. However in Bluestacks 4, using file.exists() will only return false. This is because the shared folder has no permissions (000 or ----------). But listing files in the directory will detect the folder.
String path = Environment.getExternalStorageDirectory().toString();
Log.d("FILES", "Path: " + path);
File directory = new File(path);
File[] files = directory.listFiles();
for (File file : files) {
if (file.getName().contains("windows")) {
Log.d("FILES", "windows file exists, it's a bluestacks emu");
}
}
This Will be unique.
There is no bluetooth device in Bluestack.
So try to get The Bluetooth Address string which is always 'null' on Bluestack or Any emulator.Make sure you are adding Bluetooth permission on your project manifest.
BluetoothAdapter m_BluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
String m_bluetoothAdd = m_BluetoothAdapter.getAddress();

Android: custom Facebook integration

I need some advice for this matter...
I used the facebook android sdk to create an integration with facebook from my application...I followed this tutorial:
http://www.integratingstuff.com/2010/10/14/integrating-facebook-into-an-android-application/
I would need to implement authentication in one activity and the function postToWall in another.... after authentication i want to send post simply by pressing a button but in other activity, different from that where i do authentication.
is it possible? or with the SDK I'm forced to do everything together in the same activity?
thanks in advance
Yes it is possible. You will get a access token which you can send to the next activity. Use getAccessToken() and setAccessToken().
Here is an example that even saves the needed data: Contact-Picture-Sync
you need to install an extension, similar to the core Android SDK, but no, here is what you need to do:
1.) go to github.com/facebook/facebook-android-sdk
2.) download the facebook directory ONLY! The other directories are only examples.
3.) Put the files from the src (you can copy the drawables too, if you want to) in the package, you are currently working with
4.) You are good to go, you can use the facebook "SDK"
see also this example https://github.com/facebook/facebook-android-sdk/tree/master/examples/Hackbook download it , it is working example provided by facebook
just to provide an alternative answer, there's other ways of implementing sharing on Android.
It allows for more sharing options (like Twitter, QR-Barcodes, blogging and whatnot) without having to deal with the facebook android sdk.
What you would use is a "share" intent, like so:
String title = "My thing"; // used if you share through email or channels that require a headline for the content, always include this or some apps might not parse the content right
String wallPost = "Hey - check out this stuff: http://link.com "; // the content of your wallpost
String shareVia = "Share this stuff via"; // the headline for your chooser, where the phones avaliable sharing mechanisms are offered.
Intent shareIntent = new Intent(android.content.Intent.ACTION_SEND);
shareIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
shareIntent.setType("text/plain");
shareIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, title);
shareIntent.putExtra(android.content.Intent.EXTRA_TEXT, wallPost);
startActivity(Intent.createChooser(shareIntent, shareVia));
This is by far the preferred solution on Android if you're looking for simple sharing, as it makes your app future-compatible with new services. And more lean and flexible for the user too, as there's little to no friction from hitting the share button to posting content.
It can also be seen in this blog post: http://android-developers.blogspot.com/2012/02/share-with-intents.html
I hope you can use this for your project.

Categories

Resources