I have an app that lets users open files via the storage access framework, edit them, and then overwrite the previously open file. In short this is the essential code:
private ActivityResultLauncher<String[]> mGetContent;
private Uri OpenFileUri;
public void onViewCreated(#NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mGetContent = registerForActivityResult(new ActivityResultContracts.OpenDocument(),
new ActivityResultCallback<Uri>() {
#Override
public void onActivityResult(Uri uri) {
try (InputStream in = getActivity().getContentResolver().openInputStream(uri))
{
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
String text = reader.lines().collect(Collectors.joining("\n"));
binding.editBox.setText(text);
OpenFileUri = uri;
} catch (IOException e) {
e.printStackTrace();
}
}
});
binding.buttonOpen.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
mGetContent.launch(new String[] {"text/*"});
}
});
binding.buttonSave.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (OpenFileUri == null)
return;
try (OutputStream mOutputStream = getActivity().getContentResolver().openOutputStream(OpenFileUri, "wt")) {
try (PrintWriter p = new PrintWriter(mOutputStream)) {
String t = binding.editBox.getText().toString();
p.println(t);
} catch (Exception e1) {
e1.printStackTrace();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
Full code is available here: https://github.com/pekspro/CorruptFileDemo (created for a different purpose)
The app does work fine for most of my users, but one is using Xiaomi 11T Pro with Android 11. A device I do not have access to :-(. Other devices with Anroid 11 works fine. This user could open a file, but an exception is thrown when the file is saved (not sure what, do not have any logs). My real app also trying to call takePersistableUriPermission, but this also throws an java.lang.SecurityException on this device.
When I run the app and open a file, the URI is:
content://com.android.externalstorage.documents/document/…
But for this user, it instead begins with:
content://com.mi.android.globalFileexplorer.myprovider/external_files/Documents/…
This makes me suspect that Mi File Manager is used as a document selector on Xaomi devices.
If I open a file directly in Mi File Manager I see that is only has read only permissions.
Is this behavior expected for (some) Xiaomi devices? For testing purposes, can some force my app to use Mi File Manager as document selector?
Does storage access framework work on Xaomi devices?
Yes all you posted works.
Only a provider gives no write access to a file choosen by user.
(But maybe there is a setting that would give it ?? ...)
Related
I am using Android to turn on my S Voice application in Android. As previous work, I will use the follows code to turn on it
String SVOICE_PACKAGE_NAME = "com.vlingo.midas";
String SVOICE_LISTEN_ACTION = "com.sec.action.SVOICE";
Intent intent = new Intent();
intent.setPackage(SVOICE_PACKAGE_NAME);
intent.setAction(SVOICE_LISTEN_ACTION);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
try {
getApplication().startActivity(intent);
} catch (final ActivityNotFoundException e) {
e.printStackTrace();
} catch (final Exception e) {
e.printStackTrace();
}
The above code worked well in Galaxy S4 with Android 5.0. However, the issue comes from first and second lines in Galaxy S7 with Android 6.0. In Galaxy S7 with Android 6.0, the first and second lines have to modify as
SVOICE_PACKAGE_NAME = "com.samsung.voiceserviceplatform";
SVOICE_LISTEN_ACTION = "com.sec.action.SVOICE";
And also the application name S Voice with changing from "S Voice" to "S Voice App". That changing gives me a difficult work. Hence, I want to determine the S Voice App in my phone before deciding calls these function. Currently, I do not know the changing is from Android version or the device. Could you have any idea to adapt the issue in various phones: S4 and S7?
Whenever opening applications, there could be package or application name differences. Here is a standard utility method to check:
/**
* Check if the user has a package installed
*
* #param ctx the application context
* #param packageName the application package name
* #return true if the package is installed
*/
public static boolean isPackageInstalled(#NonNull final Context ctx, #NonNull final String packageName) {
if (DEBUG) {
MyLog.i(CLS_NAME, "isPackageInstalled");
}
try {
ctx.getApplicationContext().getPackageManager().getApplicationInfo(packageName, 0);
return true;
} catch (final PackageManager.NameNotFoundException e) {
if (DEBUG) {
MyLog.w(CLS_NAME, "isPackageInstalled: NameNotFoundException");
}
} catch (final NullPointerException e) {
if (DEBUG) {
MyLog.w(CLS_NAME, "isPackageInstalled: NullPointerException");
}
} catch (final Exception e) {
if (DEBUG) {
MyLog.w(CLS_NAME, "isPackageInstalled: Exception");
}
}
return false;
}
You'll need to remove my custom logging.
I have a simple .txt file with just a couple lines in right now, each line has a word then a comma then another word, representing a very simplistic username , password bank. For some reason though I cant get the File to open to read from it.
Here is my code that I'm using....
try {
final String PATH = "src\\main\\assets\\passwords.txt";
Log.w("myApp", "passed");
List<String> user_password = FileUtils.readLines(new File(PATH));
Log.w("myApp", "passed2");
#SuppressWarnings("unchecked") List<Credentials> credentials = (List<Credentials>) CollectionUtils.collect(user_password, new Transformer() {
#Override
public Object transform(Object input) {
String cred = (String) input;
String parsed[] = cred.split(",");
Log.w("myApp", parsed[0]);
return new Credentials(parsed[0], parsed[1]);
//return credential;
}
});
user = (Credentials) CollectionUtils.find(credentials, new Predicate() {
#Override
public boolean evaluate(Object object) {
Credentials c = (Credentials) object;
return c.getUserName().equals(userName);
}
});
} catch (IOException e) {
System.out.print(e);
Log.w("MyApp", "failed");
}
I've tried putting the passwords.txt file in different places but that doesn't seem to work either.
You're referencing wrong to file in assets folder. It has to be smth like:
file:///android_asset/myfoldername/myfilename
in your particular case it's file:///android_asset/passwords.txt, though you have to keep in mind that it's always read only file
final String PATH = "src\\main\\assets\\passwords.txt";
That's not going to work. Android is not Windows, and an Android device is not your Windows development PC.
First, \ is the Windows path separator. On OS X, Linux, and Android, the path separator is /.
Second, src\main\assets\passwords.txt is a file in your project. It is not a file on the filesystem of the Android device.
To access assets, use AssetManager to open an InputStream(). You can get an AssetManager by calling getAssets() on any handy Context, such as your activity. Then, for your asset, call open("passwords.txt") on the AssetManager to get the InputStream, that you can then use to read in the data.
Thanks to #CommonsWare I was able to achieve what I was trying to do by using InputStream and then also IOUtils to read everything into the List.
try {
InputStream iS = this.getAssets().open("passwords.txt");
List<String> user_password = IOUtils.readLines(iS);
#SuppressWarnings("unchecked") List<Credentials> credentials = (List<Credentials>) CollectionUtils.collect(user_password, new Transformer() {
#Override
public Object transform(Object input) {
String cred = (String) input;
String parsed[] = cred.split(",");
return new Credentials(parsed[0], parsed[1]);
}
});
user = (Credentials) CollectionUtils.find(credentials, new Predicate() {
#Override
public boolean evaluate(Object object) {
Credentials c = (Credentials) object;
return c.getUserName().equals(userName);
}
});
}catch (IOException e){
System.out.print(e);
}
How to load a image from the play store using the package ID and not from local installed apps. For example when the app is not installed but you need to fetch the icon of the app.
There is an un-official API for the Playstore
Something like this might work:
GetImageRequest imgReq = GetImageRequest.newBuilder().setAppId("-7934792861962808905")
.setImageUsage(AppImageUsage.ICON)
.setImageId("1")
.build();
session.append(imgReq, new Callback<GetImageResponse>() {
#Override
public void onResult(ResponseContext context, GetImageResponse response) {
try {
FileOutputStream fos = new FileOutputStream("icon.png");
fos.write(response.getImageData().toByteArray());
fos.close();
} catch(Exception ex) {
ex.printStackTrace();
}
}
});
session.flush();
The important part is the .setImageUsage(AppImageUsage.ICON) you could also get screenshots with this method, setting .setImageUsage(AppImageUsage.SCREENSHOT)
UPDATE There are a number of other posts asking how to get a Screenshot in android but none seemed to have a full answer of how to do so. Originally I posted this as a question due to a particular issue I was running into while attempting to open a stream to the Frame Buffer. Now I've swapped over to dumping the Frame Buffer to a file so I've updated my post to show how I got there. For reference (and acknowledgement), I found the command to send the FrameBuffer to a file from this post (unfortunately he didn't provide how he got to that point). I'm just missing how to turn the raw data I pulled from the Frame Buffer into an actual image file.
My intention was to take a full dump of the actual screen on an Android Device. The only way I could find to do so without using the adb bridge was to directly access the Frame Buffer of the system. Obviously this approach will require root privileges on the device and for the app running it! Fortunately for my purposes I have control over how the Device is set up and having the device rooted with root privileges provided to my application is feasible. My testing is currently being done on an old Droid running 2.2.3.
I found my first hints of how to approach it from https://stackoverflow.com/a/6970338/1446554. After a bit more research I found another article that describes how to properly run shell commands as root. They were using it to execute a reboot, I use it to send the current frame buffer to an actual file. My current testing has only gotten as far as doing this via ADB and in a basic Activity (each being provided root). I will be doing further testing from a Service running in the background, updates to come! Here is my entire test activity that can export the current screen to a file:
public class ScreenshotterActivity extends Activity {
public static final String TAG = "ScreenShotter";
private Button _SSButton;
private PullScreenAsyncTask _Puller;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
_SSButton = (Button)findViewById(R.id.main_screenshotButton);
_SSButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
if (_Puller != null)
return;
//TODO: Verify that external storage is available! Could always use internal instead...
_Puller = new PullScreenAsyncTask();
_Puller.execute((Void[])null);
}
});
}
private void runSuShellCommand(String cmd) {
Runtime runtime = Runtime.getRuntime();
Process proc = null;
OutputStreamWriter osw = null;
StringBuilder sbstdOut = new StringBuilder();
StringBuilder sbstdErr = new StringBuilder();
try { // Run Script
proc = runtime.exec("su");
osw = new OutputStreamWriter(proc.getOutputStream());
osw.write(cmd);
osw.flush();
osw.close();
} catch (IOException ex) {
ex.printStackTrace();
} finally {
if (osw != null) {
try {
osw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
try {
if (proc != null)
proc.waitFor();
} catch (InterruptedException e) {
e.printStackTrace();
}
sbstdOut.append(readBufferedReader(new InputStreamReader(proc.getInputStream())));
sbstdErr.append(readBufferedReader(new InputStreamReader(proc.getErrorStream())));
}
private String readBufferedReader(InputStreamReader input) {
BufferedReader reader = new BufferedReader(input);
StringBuilder found = new StringBuilder();
String currLine = null;
String sep = System.getProperty("line.separator");
try {
// Read it all in, line by line.
while ((currLine = reader.readLine()) != null) {
found.append(currLine);
found.append(sep);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
class PullScreenAsyncTask extends AsyncTask<Void, Void, Void> {
#Override
protected Void doInBackground(Void... params) {
File ssDir = new File(Environment.getExternalStorageDirectory(), "/screenshots");
if (ssDir.exists() == false) {
Log.i(TAG, "Screenshot directory doesn't already exist, creating...");
if (ssDir.mkdirs() == false) {
//TODO: We're kinda screwed... what can be done?
Log.w(TAG, "Failed to create directory structure necessary to work with screenshots!");
return null;
}
}
File ss = new File(ssDir, "ss.raw");
if (ss.exists() == true) {
ss.delete();
Log.i(TAG, "Deleted old Screenshot file.");
}
String cmd = "/system/bin/cat /dev/graphics/fb0 > "+ ss.getAbsolutePath();
runSuShellCommand(cmd);
return null;
}
#Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
_Puller = null;
}
}
}
This also requires adding the android.permission.WRITE_EXTERNAL_STORAGE permission to the Manifest. As suggested in this post. Otherwise it runs, doesn't complain, doesn't create the directories nor the file.
Originally I couldn't get usable data from the Frame Buffer due to not understanding how to properly run shell commands. Now that I've swapped to using the streams for executing commands I can use '>' to send the Frame Buffer's current data to an actual file...
Programmatically you can run "adb shell /system/bin/screencap -p /sdcard/img.png" as below :
Process sh = Runtime.getRuntime().exec("su", null,null);
OutputStream os = sh.getOutputStream();
os.write(("/system/bin/screencap -p " + "/sdcard/img.png").getBytes("ASCII"));
os.flush();
os.close();
sh.waitFor();
An easy solution for ICS devices is to use the following from the command line
adb shell /system/bin/screencap -p /sdcard/screenshot.png
adb pull /sdcard/screenshot.png screenshot.png
This'll save the screenshot.png file in the current directory.
Tested on a Samsung Galaxy SII running 4.0.3.
That would be different for different phones. It depends on the underlying graphics format of your device. You can poll what the graphics format is using system calls. If you are only going to run this on devices that you know the graphics format of you can write a converter that turns it into a known format.
You can have a look at the following project: http://code.google.com/p/android-fb2png/
If you look at the source code for fb2png.c you can see that they poll FBIOGET_VSCREENINFO which contains info about how the device stores the screen image in memory. Once you know that, you should be able to convert it into a format you can use.
I hope this helps.
I've been working with Eclipse ADT for about 2 months. In that time, I have a small utility that allows me to select an IP Address and Port, and then send a file to that combo. The utility works as intended, but when I type in the wrong file name, the application hangs.
#Override
public void run() {
if (data != null) {
this.send(data);
} else if (this.file != null) {
if (file.exists()) {
this.send(file);
} else {
transferError = new FileNotFoundException("The specified file could not be found");
}
}
}
I've even tried to do the following in hopes that one or the other would throw, but I am unsuccessful in both.
public void run() {
if (data != null) {
this.send(data);
} else if (this.file != null) {
if (file.exists()) {
this.send(file);
} else {
transferError = new FileNotFoundException("The specified file could not be found");
}
}try {
throw new Exception("blah blah blah");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
I've jockeyed around the exception, I've added the one above, I've tried placing it in different places, and all unsuccessful. Again, I'm exceptionally new to this, and got here from basically mincing various tcp client codes. Aside of creating a way to throw the exception correctly, please help me understand why the first one isn't working and why the one you suggest is.
in your else block you aren't throwin the transferError you create.
throw transferError;
However you probably won't be able to do that because FileNotFoundException is a checked exception and the run() method doesn't declare any thrown exceptions. You probably need to find a different way to present the error to the user, like with a Toast or something.
Your second block doesn't work because you are catching the exception you throw.