Looking at this issue xamarin/Essentials#1322, how do I download a file on both Android ( versions 6-10, Api 23-29 ) and iOS ( version 13.1+ ) that is publicly available (share-able to other apps, such as Microsoft Word). I don't need to give write access to the other apps, just read-only is ok if it must be restricted.
I get the following exception:
[Bug] Android.OS.FileUriExposedException: file:///data/user/0/{AppBundleName}/cache/file.doc exposed beyond app through Intent.getData()
With the following code.
public static string GetCacheDataPath( string fileName ) => Path.Combine(Xamarin.Essentials.FileSystem.CacheDirectory, fileName);
public static FileInfo SaveFile( string filename, Uri link )
{
using var client = new WebClient();
string path = GetCacheDataPath(filename);
DebugTools.PrintMessage(path);
client.DownloadFile(link, path);
return new FileInfo(path);
}
public async Task Test(Uri link)
{
LocalFile path = await SaveFile("file.doc", link).ConfigureAwait(true);
var url = new Uri($"ms-word://{path.FullName}", UriKind.Absolute);
await Xamarin.Essentials.Launcher.OpenAsync(url).ConfigureAwait(true);
}
With this answer, I created a FileService interface and it works with local private files but I am unable to share the files. Starting with Android Q (10 / Api 29), the following is deprecated.
string path = Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryDownloads).AbsolutePath; // deprecated
I get the following exception:
System.UnauthorizedAccessException: Access to the path '/storage/emulated/0/Download/file.doc' is denied. ---> System.IO.IOException: Permission denied
I haven't found any way yet to get a public path for Android 10 with Xamarin.Forms. I've looked at the Android Docs for Content providers but it's in Java, and I can't get it working in C# yet.
Any help would be greatly appreciated.
I did find a Solution
Found a fix
For Android
public Task<System.IO.FileInfo> DownloadFile( Uri link, string fileName )
{
if ( link is null )
throw new ArgumentNullException(nameof(link));
using System.Net.WebClient client = new System.Net.WebClient();
// MainActivity is the class that loads the application.
// MainActivity.Instance is a property that you set "Instance = this;" inside of OnCreate.
Java.IO.File root = MainActivity.Instance.GetExternalFilesDir(MediaStore.Downloads.ContentType);
string path = Path.Combine(root.AbsolutePath, fileName);
client.DownloadFile(link, path);
return Task.FromResult(new System.IO.FileInfo(path));
}
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
internal static MainActivity Instance { get; private set; }
protected override void OnCreate(Bundle savedInstanceState)
{
...
Instance = this;
...
}
...
}
For iOS
public Task<System.IO.FileInfo> DownloadFile( Uri link, string fileName )
{
if ( link is null )
throw new ArgumentNullException(nameof(link));
using System.Net.WebClient client = new System.Net.WebClient();
string path = Path.Combine(Xamarin.Essentials.FileSystem.CacheDirectory, fileName)
client.DownloadFile(link, path);
return Task.FromResult(new System.IO.FileInfo(path));
}
public async Task Share()
{
// back in shared project, choose a file name and pass the link.
System.IO.FileInfo info = await DependencyService.Get<IDownload>().DownloadFile(new Uri("<enter site>", "file.doc").ConfigureAwait(true);
ShareFile shareFile = new ShareFile(info.FullName, "doc"); // enter the file type / extension.
var request = new ShareFileRequest("Choose the App to open the file", shareFile);
await Xamarin.Essentials.Share.RequestAsync(request).ConfigureAwait(true);
}
Note that for iOS, due to Apple's infinite wisdom... I cannot share the file directly with another app as I can on Android. Sandboxing is good for security but in this case, how they implemented it, it limits options. Both Applications must be pre-registered / pre-allocated in an "App Group" to share files directly. See this Article and the Apple Docs for more information.
Is there a Java equivalent for System.IO.Path.Combine() in C#/.NET? Or any code to accomplish this?
This static method combines one or more strings into a path.
Rather than keeping everything string-based, you should use a class which is designed to represent a file system path.
If you're using Java 7 or Java 8, you should strongly consider using java.nio.file.Path; Path.resolve can be used to combine one path with another, or with a string. The Paths helper class is useful too. For example:
Path path = Paths.get("foo", "bar", "baz.txt");
If you need to cater for pre-Java-7 environments, you can use java.io.File, like this:
File baseDirectory = new File("foo");
File subDirectory = new File(baseDirectory, "bar");
File fileInDirectory = new File(subDirectory, "baz.txt");
If you want it back as a string later, you can call getPath(). Indeed, if you really wanted to mimic Path.Combine, you could just write something like:
public static String combine(String path1, String path2)
{
File file1 = new File(path1);
File file2 = new File(file1, path2);
return file2.getPath();
}
In Java 7, you should use resolve:
Path newPath = path.resolve(childPath);
While the NIO2 Path class may seem a bit redundant to File with an unnecessarily different API, it is in fact subtly more elegant and robust.
Note that Paths.get() (as suggested by someone else) doesn't have an overload taking a Path, and doing Paths.get(path.toString(), childPath) is NOT the same thing as resolve(). From the Paths.get() docs:
Note that while this method is very convenient, using it will imply an assumed reference to the default FileSystem and limit the utility of the calling code. Hence it should not be used in library code intended for flexible reuse. A more flexible alternative is to use an existing Path instance as an anchor, such as:
Path dir = ...
Path path = dir.resolve("file");
The sister function to resolve is the excellent relativize:
Path childPath = path.relativize(newPath);
The main answer is to use File objects. However Commons IO does have a class FilenameUtils that can do this kind of thing, such as the concat() method.
platform independent approach (uses File.separator, ie will works depends on operation system where code is running:
java.nio.file.Paths.get(".", "path", "to", "file.txt")
// relative unix path: ./path/to/file.txt
// relative windows path: .\path\to\filee.txt
java.nio.file.Paths.get("/", "path", "to", "file.txt")
// absolute unix path: /path/to/filee.txt
// windows network drive path: \\path\to\file.txt
java.nio.file.Paths.get("C:", "path", "to", "file.txt")
// absolute windows path: C:\path\to\file.txt
I know its a long time since Jon's original answer, but I had a similar requirement to the OP.
By way of extending Jon's solution I came up with the following, which will take one or more path segments takes as many path segments that you can throw at it.
Usage
Path.combine("/Users/beardtwizzle/");
Path.combine("/", "Users", "beardtwizzle");
Path.combine(new String[] { "/", "Users", "beardtwizzle", "arrayUsage" });
Code here for others with a similar problem
public class Path {
public static String combine(String... paths)
{
File file = new File(paths[0]);
for (int i = 1; i < paths.length ; i++) {
file = new File(file, paths[i]);
}
return file.getPath();
}
}
To enhance JodaStephen's answer, Apache Commons IO has FilenameUtils which does this. Example (on Linux):
assert org.apache.commons.io.FilenameUtils.concat("/home/bob", "work\\stuff.log") == "/home/bob/work/stuff.log"
It's platform independent and will produce whatever separators your system needs.
Late to the party perhaps, but I wanted to share my take on this. I prefer not to pull in entire libraries for something like this. Instead, I'm using a Builder pattern and allow conveniently chained append(more) calls. It even allows mixing File and String, and can easily be extended to support Path as well. Furthermore, it automatically handles the different path separators correctly on both Linux, Macintosh, etc.
public class Files {
public static class PathBuilder {
private File file;
private PathBuilder ( File root ) {
file = root;
}
private PathBuilder ( String root ) {
file = new File(root);
}
public PathBuilder append ( File more ) {
file = new File(file, more.getPath()) );
return this;
}
public PathBuilder append ( String more ) {
file = new File(file, more);
return this;
}
public File buildFile () {
return file;
}
}
public static PathBuilder buildPath ( File root ) {
return new PathBuilder(root);
}
public static PathBuilder buildPath ( String root ) {
return new PathBuilder(root);
}
}
Example of usage:
File root = File.listRoots()[0];
String hello = "hello";
String world = "world";
String filename = "warez.lha";
File file = Files.buildPath(root).append(hello).append(world)
.append(filename).buildFile();
String absolute = file.getAbsolutePath();
The resulting absolute will contain something like:
/hello/world/warez.lha
or maybe even:
A:\hello\world\warez.lha
If you do not need more than strings, you can use com.google.common.io.Files
Files.simplifyPath("some/prefix/with//extra///slashes" + "file//name")
to get
"some/prefix/with/extra/slashes/file/name"
Here's a solution which handles multiple path parts and edge conditions:
public static String combinePaths(String ... paths)
{
if ( paths.length == 0)
{
return "";
}
File combined = new File(paths[0]);
int i = 1;
while ( i < paths.length)
{
combined = new File(combined, paths[i]);
++i;
}
return combined.getPath();
}
This also works in Java 8 :
Path file = Paths.get("Some path");
file = Paths.get(file + "Some other path");
This solution offers an interface for joining path fragments from a String[] array. It uses java.io.File.File(String parent, String child):
public static joinPaths(String[] fragments) {
String emptyPath = "";
return buildPath(emptyPath, fragments);
}
private static buildPath(String path, String[] fragments) {
if (path == null || path.isEmpty()) {
path = "";
}
if (fragments == null || fragments.length == 0) {
return "";
}
int pathCurrentSize = path.split("/").length;
int fragmentsLen = fragments.length;
if (pathCurrentSize <= fragmentsLen) {
String newPath = new File(path, fragments[pathCurrentSize - 1]).toString();
path = buildPath(newPath, fragments);
}
return path;
}
Then you can just do:
String[] fragments = {"dir", "anotherDir/", "/filename.txt"};
String path = joinPaths(fragments);
Returns:
"/dir/anotherDir/filename.txt"
Assuming all given paths are absolute paths. you can follow below snippets to merge these paths.
String baseURL = "\\\\host\\testdir\\";
String absoluteFilePath = "\\\\host\\testdir\\Test.txt";;
String mergedPath = Paths.get(baseURL, absoluteFilePath.replaceAll(Matcher.quoteReplacement(baseURL), "")).toString();
output path is \\host\testdir\Test.txt.
I have a problem with this line of code. Want to create sqlite database on the device.
string dbPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal) + "\\test.db";
if (!System.IO.File.Exists(dbPath))
using (System.IO.Stream sr = ***Assets***.Open("test.db"))
{
using (System.IO.Stream srTo = System.IO.File.Create(dbPath))
{
sr.CopyTo(srTo);
}
}
This message gives the:
The name 'Assets' does not exist in the current context
have a similar project, but more comprehensive than big. There is no error. They Assets Where I'm following definition defines a cs file in c drive gives reference to Android.Content.ContextWrapper.
is not on The project the path.
[How the file was added application?
If you aren't doing this in an activity you need a reference to the Activity/Context. You will need to pass this in to your helper class in the constructor.
public yourClass(Activity context.......){
context.Assets.Open("your.db");
}
You can do like this:
using (System.IO.Stream sr = Android.App.Application.Context.Assets.Open("test.db"))
{
using (System.IO.Stream srTo = System.IO.File.Create(dbPath))
{
sr.CopyTo(srTo);
}
}
I tried really hard, but always get a Class Not Found exception, from reading this answer
https://stackoverflow.com/a/3024261
I took my jar run the
dx --dex --output=C:\classes.dex C:\MyAndroidLib.jar
and got a dex file
then I run the
apt add C:\MyLib.jar C:\classes.dex
to create a jar with the dex file.
then I wrote the following code.
DexClassLoader classLoader = new DexClassLoader(
destPath, dirPath +"/" , null, getClass().getClassLoader());
Class<?> classToLoad = classLoader.loadClass("ClassImpl");
on debug I can see that the dex is inside the classLoader(under the mDexs member)
and the ClassImpl is the only class I got inside.
but I keep getting the class not found exception.
Anyone got a working sample of dynamic class loading from external jar ?
Someone knows whats my problem?
I did not use aapt... Only dex. And I got my class loaded, and my methods called.
Use this code for see inside the dex file:
DexFile dexfile = DexFile.loadDex(url_jar_path,
File.createTempFile("opt", "dex", context.getCacheDir()).getPath(), 0);
// Print all classes in the DexFile
Enumeration<String> classNames = dexfile.entries();
String classname = "";
while (true) {
if (! classNames.hasMoreElements()) {
break;
}
classname = classNames.nextElement();
}
Use this code for see inside the class:
Class class = dexclassloader.loadClass(ruta_clase_en_jar);
String name;
Method[] method_array = clase.getMethods();
for (Method i: method_array) {
name = i.getName ();
}
I currently have a compiled jar file that I would like to use on an android device. The code outputs to the command line using System.out.println().
How would I create a wrapper to grab the stdout and put it in a text view on an android device? Would I need to make any changes to the jar (I do have all the source code) to allow the wrapper?
Thanks in advance.
I think you'll need to make some changes. You can set standart output by calling
System.setOut(PrintStream out)
// Reassigns the "standard" output stream.
Where out is your own class that will print data to text view. See swing solution. Just set appending to text view and you can use this code.
Or just create one method
void log(String message);
where you appending text to your view. Then change all println() calls to this.
First you should consider that Android has a specific Java VM called Dalvik and not any jar can be ran under it.
If there's one point in your jar where output occurs, the best option would be to create a usual application with a TextView, include your jar to it's build path and replace a call to println() with output to it:
public void print(String msg) {
mTextView.setText(msg);
}
If there're many sources of output you could run you jar using java.lang.Process and use it's getInputStream() method to read printed messages:
public static final String XBOOT_CLASS_PATH = "-Xbootclasspath:/system/framework/core.jar"
public static final String CLASS_PATH = "-classpath /path/to/your/file.jar com.your.package.name"
...
Process p = new ProcessBuilder("dalvikvm", XBOOT_CLASS_PATH, CLASS_PATH).start();
BufferedReader reader = new BufferedReader (new InputStreamReader(p.getInputStream()));
String msg = reader.readLine();
if (msg != null) {
mTextView.setText(msg);
}
// Cleanup omitted for simplicity
If it's an executable jar file here is a working example
Add this simple executable HelloWorld jar file to your Android Project's build path
If the jar file doesn't have a package, then you will have to use Reflection to invoke methods in it.Other wise you can just import the class files and invoke the main method directly.(This example jar has a package "psae")
eg:
TextView tv = (TextView)findViewById(R.id.textv);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
PrintStream ps = new PrintStream(baos);
System.setOut(ps);
String[] params = {"Aneesh","Joseph"};
psae.HelloWorld.main(params);
String output = baos.toString();
tv.setText(output)
If the jar file just has a default package, then you won't be able to import class files from that jar, and hence you will have to use Reflection to invoke the method.
TextView tv = (TextView)findViewById(R.id.textv);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
PrintStream ps = new PrintStream(baos);
System.setOut(ps);
try {
//pick the entry class from the jar Manifest
//Main-Class: psae.HelloWorld
Class myClass = Class.forName("psae.HelloWorld");
//since this has a package, there is no need of reflection.This is just an example
//If the jar file had just a default package, the it would have been something like the below line (and this is where it would be useful)
//Class myClass = Class.forName("Main");
Method myMethod = myClass.getMethod("main", String[].class);
//parameters to the main method
String[] params = {"Aneesh","Joseph"};
myMethod.invoke(null, (Object) params);
String output = baos.toString();
tv.setText(output);
}
catch(Exception d)
{
tv.setText(d.toString());
}