I have been using "Environment.getExternalStorage()" to store and manage files. And there is no warning message from logcat with that method and works greatly fine.
But, My project needs to use method "Context.getExternalFilesDir(String type)" and there is a warning message
ContextImpl:Failed to ensure directory: /storage/external_SD/Android/data/(package name)/files
Fortunately that File object works fine(reading or write or making folder works, too).
But I want to know how to resolve that warning message. Do I miss something?
You should know how the warning message comes up.
The getExternalFilesDir(String type) will call getExternalFilesDirs(String type) (notice the 's' at the final of the second method name).
The getExternalFilesDirs(String type) will find all dirs of the type, and calls ensureDirsExistOrFilter() at the end to ensure the directories exist.
If the dir can't be reached, it will print a warning!
Log.w(TAG, "Failed to ensure directory: " + dir);
dir = null;
So, if your device has two sdcard paths, it will produce two dirs. If one is not available, the warning will come up.
The conclusion is the warning does not need to be fixed.
If you have code that is iterating files, calling this API many times, this warning can cause log pollution. To solve this (since the warning is actually benign) you can create a wrapper class that stores the result of calling getExternalFilesDir / getExternalCacheDir and subsequently returns the stored value instead of calling the API. In this way, at least you will only ever see this message once.
I follow the getExternalFilesDir() source
/**
* Ensure that given directories exist, trying to create them if missing. If
* unable to create, they are filtered by replacing with {#code null}.
*/
private File[] ensureExternalDirsExistOrFilter(File[] dirs) {
File[] result = new File[dirs.length];
for (int i = 0; i < dirs.length; i++) {
File dir = dirs[i];
if (!dir.exists()) {
if (!dir.mkdirs()) {
// recheck existence in case of cross-process race
if (!dir.exists()) {
// Failing to mkdir() may be okay, since we might not have
// enough permissions; ask vold to create on our behalf.
final IMountService mount = IMountService.Stub.asInterface(
ServiceManager.getService("mount"));
try {
final int res = mount.mkdirs(getPackageName(), dir.getAbsolutePath());
if (res != 0) {
Log.w(TAG, "Failed to ensure " + dir + ": " + res);
dir = null;
}
} catch (Exception e) {
Log.w(TAG, "Failed to ensure " + dir + ": " + e);
dir = null;
}
}
}
}
result[i] = dir;
}
return result;
}
immediate use Environment.getExternalStorageDirectory() get ExternalDirs
public final class StorageUtil {
public static final String DIR_ANDROID = "Android";
private static final String DIR_DATA = "data";
private static final String DIR_FILES = "files";
private static final String DIR_CACHE = "cache";
#Nullable
public static synchronized File getExternalStorageAppFilesFile(Context context, String fileName) {
if (context == null) return null;
if (fileName == null) return null;
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
File dirs = buildExternalStorageAppFilesDirs(Environment.getExternalStorageDirectory().getAbsolutePath(), context.getPackageName());
return new File(dirs, fileName);
}
return null;
}
public synchronized static File buildExternalStorageAppFilesDirs(String externalStoragePath, String packageName) {
return buildPath(externalStoragePath, DIR_ANDROID, DIR_DATA, packageName, DIR_FILES);
}
public synchronized static File buildPath(String base, String... segments) {
File cur = new File(base);
for (String segment : segments) {
cur = new File(cur, segment);
}
return cur;
}
}
Related
Im investigating the use of citeproc in my current Android application
api 'de.undercouch:citeproc-java:2.0.0'
implementation 'org.citationstyles:styles:20.11'
implementation 'org.citationstyles:locales:20.11'
Its working fine using
// https://repo1.maven.org/maven2/com/eclipsesource/j2v8/j2v8/6.2.0/
implementation(name: 'j2v8-6.2.0', ext: 'aar')
However as Im running on Android OS the CSL static method
CSL.getSupportedStyles()
returns an empty list.
the underlying code in this method is as follows:-
private static Set<String> getAvailableFiles(String prefix,
String knownName, String extension) throws IOException {
Set<String> result = new LinkedHashSet<>();
// first load a file that is known to exist
String name = prefix + knownName + "." + extension;
URL knownUrl = CSL.class.getResource("/" + name);
if (knownUrl != null) {
String path = knownUrl.getPath();
// get the jar file containing the file
if (path.endsWith(".jar!/" + name)) {
String jarPath = path.substring(0, path.length() - name.length() - 2);
URI jarUri;
try {
jarUri = new URI(jarPath);
} catch (URISyntaxException e) {
// ignore
return result;
}
try (ZipFile zip = new ZipFile(new File(jarUri))) {
Enumeration<? extends ZipEntry> entries = zip.entries();
while (entries.hasMoreElements()) {
ZipEntry e = entries.nextElement();
if (e.getName().endsWith("." + extension) &&
(prefix.isEmpty() || e.getName().startsWith(prefix))) {
result.add(e.getName().substring(
prefix.length(), e.getName().length() - 4));
}
}
}
}
}
return result;
}
Is it possible to list get getAvailableFiles method to work on Android OS?
The following aproach does not work
val name: String = "$prefix$knownName.$extension"
val knownUrl: URL? = CSL::class.java.classLoader.getResource("$name")
All I require is the list of ".csl" files that in an Eclipse java project show as residing in styles-20.11.jar file
When I extract my Application APK file the ".csl" files are all listed seperately
Where am I going wrong?
How can I get a list of all ".csl" files available to CSL?
Your provided detail guides me to find the solution. That's enough to define apk in the checker condition.
class CLSHelper {
public static Set<String> getSupportedStyles() throws IOException {
return getAvailableFiles("", "ieee", "csl");
}
/**
* Customizing this function to able run in Android environment
*/
private static Set<String> getAvailableFiles(String prefix,
String knownName, String extension) throws IOException {
Set<String> result = new LinkedHashSet<>();
// first load a file that is known to exist
String name = prefix + knownName + "." + extension;
URL knownUrl = CSL.class.getResource("/" + name);
if (knownUrl != null) {
String path = knownUrl.getPath();
// get the jar or apk file containing the file
if (path.endsWith(".jar!/" + name) || path.endsWith(".apk!/" + name)) { // changing this line
String jarPath = path.substring(0, path.length() - name.length() - 2);
URI jarUri;
try {
jarUri = new URI(jarPath);
} catch (URISyntaxException e) {
// ignore
return result;
}
try (ZipFile zip = new ZipFile(new File(jarUri))) {
Enumeration<? extends ZipEntry> entries = zip.entries();
while (entries.hasMoreElements()) {
ZipEntry e = entries.nextElement();
if (e.getName().endsWith("." + extension) &&
(prefix.isEmpty() || e.getName().startsWith(prefix))) {
result.add(e.getName().substring(
prefix.length(), e.getName().length() - 4));
}
}
}
}
}
return result;
}
}
Output:
CSL.getSupportedStyles() // []
CLSHelper.getSupportedStyles() // [dependent/annals-of-occupational-and-environmental-medicine, dependent/photoacoustics, dependent/statistical-science, twentieth-century-music, dependent/aims-medical-science, dependent/cell-systems, dependent/nursingplus-open, dependent/computer-science-review,...]
P.S: To running on AndroidStudio I had to add these lines in build.gradle
android{
...
packagingOptions {
exclude 'META-INF/truffle/language'
}
}
I want to integrate Google drive with my app I have registered my app in Google Developers Console. I got a sample from https://github.com/googledrive/android-demos .By this i am able to create a file,folder in Google drive's root folder but the problem is I couldn't create a file or folder inside an existing folder. In such case i got a toast "Cannot find DriveId. Are you authorized to view this file?" ie I cannot get the driveID
public class CreateFolderInFolderActivity extends BaseDemoActivity {
#Override
public void onConnected(Bundle connectionHint) {
super.onConnected(connectionHint);
Drive.DriveApi.fetchDriveId(getGoogleApiClient(), EXISTING_FOLDER_ID)
.setResultCallback(idCallback);
}
final ResultCallback<DriveIdResult> idCallback = new ResultCallback<DriveIdResult>() {
#Override
public void onResult(DriveIdResult result) {
if (!result.getStatus().isSuccess()) {
showMessage(result.getStatus().toString());
showMessage("Cannot find DriveId. Are you authorized to view this file?");
return;
}
DriveFolder folder = Drive.DriveApi
.getFolder(getGoogleApiClient(), result.getDriveId());
MetadataChangeSet changeSet = new MetadataChangeSet.Builder()
.setTitle("MyNewFolder").build();
folder.createFolder(getGoogleApiClient(), changeSet)
.setResultCallback(createFolderCallback);
}
};
final ResultCallback<DriveFolderResult> createFolderCallback = new
ResultCallback<DriveFolderResult>() {
#Override
public void onResult(DriveFolderResult result) {
if (!result.getStatus().isSuccess()) {
showMessage("Problem while trying to create a folder");
return;
}
showMessage("Folder successfully created");
}
};
}
I cannot find any proper documentation for this.
Plz help me where i am going wrong or Whether I Have to include any other permissions
You can take a peek here: in the 'createTree()' method, there is a creation of folder within a folder.
There are 3 different drive ID entities in the new Google Drive Android API (GDAA)
the object of type DriveID - the one you get from methods and use in your code
a string you get from encodeToString() and pass to decodeFromString() - used to save within the app (caching for instance)
a string you get from getResourceId() and pass to fetchDriveId() - the one you see in the html address of a file.
Both 2 and 3 identifiers are strings, so they may be confused. Identifier 2 is faster when retrieving Drive ID (via decodeFromString()). Identifier 3 is slower to retrieve (via fetchDriveId()), but usefull if you need to take your ID elsewhere (Apps Script, for instance).
Please see also: SO 21800257
What is EXISTING_FOLDER_ID? If you are trying to just run the sample straight out without having made any changes, this won't work.
You need to change EXISTING_FOLDER_ID to the resource id of a folder that your app has access to. This could be a folder that your app created in the root.
first create folder using creatreeTree()
then run a search query to get id of create public static ArrayList<ContentValues> search(String prnId, String titl, String mime) {
ArrayList<ContentValues> gfs = new ArrayList<>();
if (mGOOSvc != null && mConnected) try {
// add query conditions, build query
String qryClause = "'me' in owners and ";
if (prnId != null) qryClause += "'" + prnId + "' in parents and ";
if (titl != null) qryClause += "title = '" + titl + "' and ";
if (mime != null) qryClause += "mimeType = '" + mime + "' and ";
qryClause = qryClause.substring(0, qryClause.length() - " and ".length());
Drive.Files.List qry = mGOOSvc.files().list().setQ(qryClause)
.setFields("items(id,mimeType,labels/trashed,title),nextPageToken");
String npTok = null;
if (qry != null) do {
FileList gLst = qry.execute();
if (gLst != null) {
for (File gFl : gLst.getItems()) {
if (gFl.getLabels().getTrashed()) continue;
gfs.add( UT.newCVs(gFl.getTitle(), gFl.getId(), gFl.getMimeType()));
} //else UT.lg("failed " + gFl.getTitle());
npTok = gLst.getNextPageToken();
qry.setPageToken(npTok);
}
} while (npTok != null && npTok.length() > 0); //UT.lg("found " + vlss.size());
} catch (Exception e) { UT.le(e); }
return gfs;
}
when you get folder id use this code to create folder in folder ` public static ParentReference insertFileIntoFolder(Drive service, String folderId,
String folderName) throws IOException {
// Log.e("founddd",id);
File fileMetadata = new File();
fileMetadata.setParents(Collections.singletonList(new ParentReference().setId(folderId == null ? "root" : folderId)));
fileMetadata.setTitle(folderName);
fileMetadata.setMimeType("application/vnd.google-apps.folder");
File file = mGOOSvc.files().insert(fileMetadata).execute();
System.out.println("Folder ID: " + file.getId());
strChildFolder = file.getId();
return null;
}`
I have created an encrypted .obb file using the jobb tool. I use the following code to mount the obb file:
public void mountExpansion() {
final StorageManager storageManager = (StorageManager) getContext()
.getSystemService(Context.STORAGE_SERVICE);
String packageName = "name.of.the.package";
String filePath = Environment.getExternalStorageDirectory()
+ "/Android/obb/" + packageName + "/" + "main."
+ version + "." + packageName + ".obb";
final File mainFile = new File(filePath);
if (mainFile.exists()) {
Log.d("STORAGE", "FILE: " + filePath + " Exists");
} else {
Log.d("STORAGE", "FILE: " + filePath + " DOESNT EXIST");
}
String key = "thisIsMyPassword";
if (!storageManager.isObbMounted(mainFile.getAbsolutePath())) {
if (mainFile.exists()) {
if(storageManager.mountObb(mainFile.getAbsolutePath(), key,
new OnObbStateChangeListener() {
#Override
public void onObbStateChange(String path, int state) {
super.onObbStateChange(path, state);
Log.d("PATH = ",path);
Log.d("STATE = ", state+"");
expansionFilePath = storageManager.getMountedObbPath(path);
if (state == OnObbStateChangeListener.MOUNTED) {
expansionFilePath = storageManager
.getMountedObbPath(path);
Log.d("STORAGE","-->MOUNTED");
}
else {
Log.d("##", "Path: " + path + "; state: " + state);
}
}
}))
{
Log.d("STORAGE_MNT","SUCCESSFULLY QUEUED");
}
else
{
Log.d("STORAGE_MNT","FAILED");
}
} else {
Log.d("STORAGE", "Patch file not found");
}
}
}
I am getting the following output:
FILE: filePath Exists
SUCCESSFULLY QUEUED
But nothing inside onObbStateChangeListener is getting called. I am calling this function from a custom view and testing this on Nexus 4/ KitKat.
What could be the reason for this behaviour?
I know this question is old but this may help someone else.
The StorageManager stores the listener in a weak reference which means that, given your example code (an anonymous instance created in the method call), it is gone almost as soon as you create it and usually well before the mount completes. You must maintain a reference to the listener object in your own code until it is no longer needed.
Something like this should work:
public class MyClass {
...
private OnObbStateChangeListener mListener =
new OnObbStateChangeListener() {
#Override
public void onObbStateChange(String path, int state) {
// your code here
}
};
public void mountExpansion() {
...
if (storageManager.mountObb(mainFile.getAbsolutePath(), key, mListener)
{
Log.d("STORAGE_MNT","SUCCESSFULLY QUEUED");
}
else
{
Log.d("STORAGE_MNT","FAILED");
}
...
}
...
}
This particular quirk of obb mounting has existed since at least honeycomb to my knowledge.
There seems to be a bug with OBB mounting that was introduced with KitKat. Currently no workarounds are known however it should be fixed with the next incremental update.
http://code.google.com/p/android/issues/detail?id=61881
I've wrote android app with native shared library ( libnativeext.so ).
Inside java class in app I load libnativeext.so with
System.loadLibrary("nativeext").
All works great.
Native code compiles, and libnativeext.so places in /libs/armeabi/ folder.
So final first.apk file contains /lib/armeabi/libnativeext.so, installs on device and all work ok.
Then I export project in javaext.jar.
At this point javaext.jar contains libnativeext.so in /libs/armeabi/.
In the new project (second-proj) I include javaext.jar and add path to javaext.jar in java build path.
Project builds with only warning about native library in javaext.jar.
I disable warning in eclipse preferences.
But on device I got: java.lang.UnsatisfiedLinkError: Couldn't load nativeext: findLibrary returned null
Strange, because second.apk have /libs/armeabi/libnativeext.so inside. I go to phone and figure out than folder on phone /data/data/myapp/lib/ is EMPTY! And ofcourse System.loadLibrary can't find libnativeext.so.
I find solution for myself, but it looks very ugly, and I want to find better way.
I create inside existing second-proj/libs/ folder armeabi and place libnativeext.so inside.
second-proj:
/libs/armeabi/libnativeext.so
/libs/javaext.jar
When I build project, I look inside second.apk:
/lib/armeabi/libnativeext.so <--- new one
/libs/armeabi/libnativeext.so
And this version work perfect on the phone.
So I assume, that during installation libraries from /libs/armeabi/ is ignored, and only libraries from /lib/armeabi/ is installed on the phone.
So question is: How to force apk bulder to copy *.so from *.jar to right *.apk folder?
In case there are no way to pack *.so library from included *.jar into final *.apk I solve this problem for myself.
I write LibraryLoader, which:
Tries to load library with System.loadLibrary().
If it fails, loader search library in application storage, and if find loads it with System.load().
If no library was found in the app storage, it find .apk file, serch there, and if loader find library - copies it to app storage, then loads it with System.load().
Post code here - may be it helps somebody.
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import android.content.Context;
import android.util.Log;
public class SharedLibraryLoader
{
private static Context context;
private static String libDir = "lib";
private static String shortLibName;
private static String fullLibName;
static public boolean loadLibrary(String libName, Context ctx)
{
context = ctx;
shortLibName = libName;
fullLibName = "lib" + libName + ".so";
try
{
Log.d("SharedLibraryLoader", "Trying to load library");
System.loadLibrary(shortLibName);
Log.d("SharedLibraryLoader", "Library was loaded from default location");
return true;
}
catch(UnsatisfiedLinkError e)
{
Log.d("SharedLibraryLoader","Lib wasn't found at default location. Trying to find in application private storage");
String path = null;
path = findInAppStorage(fullLibName);
if(path != null)
{
Log.d("SharedLibraryLoader","Lib was found in application private storage. Loading lib...");
System.load(path);
return true;
}
else
{
Log.d("SharedLibraryLoader","Lib was not found in application private storage. Trying to find in apk...");
path = findInApkAndCopyToAppStorage(fullLibName);
if(path != null)
{
Log.d("SharedLibraryLoader","Lib was found in apk and copied to application private storage. Loading lib...");
System.load(path);
return true;
}
else
{
Log.d("SharedLibraryLoader", "FAILED TO LOAD LIBRARY");
return false;
}
}
}
}
static private String findInAppStorage(String libName)
{
Log.d("SharedLibraryLoader","enter findInAppStorage()");
String basePath = context.getApplicationInfo().dataDir;
File dataDir = new File(basePath);
String[] listFiles;
String lib = null;
listFiles = dataDir.list();
for(int i=0; i < listFiles.length; i++)
{
lib = findInStorage(basePath + "/" +listFiles[i], libName);
if(lib != null)
{
return lib;
}
}
Log.d("SharedLibraryLoader", "Lib wasn't found.");
return null;
}
static private String findInStorage(String path, String nameOfLib)
{
File file = new File(path);
if(file.isDirectory())
{
Log.d("SharedLibraryLoader","Strorage__dir: " + path + "/");
String[] list = file.list();
String target = null;
for(int i = 0; i < list.length; i++)
{
target = findInStorage(path + "/" + list[i], nameOfLib);
if(target != null)
{
return target;
}
}
}
else
{
Log.d("SharedLibraryLoader","Strorage_file: " + path);
if(path.contains(nameOfLib))
{
Log.d("SharedLibraryLoader","Lib was found in: " + path);
return path;
}
}
return null;
}
static private String findInApkAndCopyToAppStorage(String libName)
{
Log.d("SharedLibraryLoader", "Enter findInApkAndCopyToStorage()");
// ---------------- ZIP - find path to .so inside .apk ------------------
String apkPath = context.getPackageResourcePath();
Log.d("SharedLibraryLoader", String.format("Path to Package resource is: %s", apkPath));
try
{
ZipFile zf = new ZipFile(apkPath);
Enumeration<ZipEntry> zipFiles = (Enumeration<ZipEntry>) zf.entries();
ZipEntry soZipEntry = null;
ZipEntry tempZipEntry;
String tmpString;
for ( ; zipFiles.hasMoreElements();)
{
tempZipEntry = zipFiles.nextElement();
tmpString = tempZipEntry.getName();
if(tmpString.contains(libName))
{
Log.d("SharedLibraryLoader", "Library " + fullLibName + " was found in: " + tmpString);
soZipEntry = tempZipEntry;
}
}
//----------now copy library---------------
Log.d("SharedLibraryLoader", "soZipEntry = " + soZipEntry.toString());
if(soZipEntry != null)
{
InputStream soInputStream = zf.getInputStream(soZipEntry);
File fileDir;
File soFile;
OutputStream outStream;
fileDir = context.getApplicationContext().getDir(libDir, Context.MODE_PRIVATE); // but "app_lib" was created!
String fullSoFilePath = fileDir.getAbsolutePath() + "/" + libName;
Log.d("SharedLibraryLoader", "New libpath is "+ fullSoFilePath);
soFile = new File(fullSoFilePath);
Log.d("SharedLibraryLoader", "Is file already exists? - " + soFile.exists());
outStream = new BufferedOutputStream(new FileOutputStream(soFile));
Log.d("SharedLibraryLoader", "Start copying library...");
byte[] byteArray = new byte[256];
int copiedBytes = 0;
while((copiedBytes = soInputStream.read(byteArray)) != -1)
{
outStream.write(byteArray, 0, copiedBytes);
}
Log.d("SharedLibraryLoader", "Finish copying library");
outStream.close();
soInputStream.close();
return fullSoFilePath;
}
else
{
Log.d("SharedLibraryLoader", "Library not Found in APK");
return null;
}
}
catch (IOException e)
{
e.printStackTrace();
return null;
}
}
}
My Android application needs to look into the default Camera directory and get the names of JPEG files created within a time range. I currently do this with the below code snippet.
The idea is to filter out files not only by the "jpg" extension but also to use the fact that the file names have date/time in them.
I run the below getImages() function within a Thread so the UI itself isn't affected.
I notice that it takes a while if there are lot of files in the directory even after all this. Is there a faster algorithm or Android specific functions that I can use to optimize it further?
private boolean getImages()
{
boolean status=true;
File dir = new File(picPath);
//Optimize file search by setting the common file prefix as a filter
//get the date format used by camera to store files.
String startStr=startTime.format3339(false);
String endStr=endTime.format3339(false);
//Loop through each character of start time
//and compare with end time till a mismatch is found
int len=startStr.length();
int idx=0;
for ( idx=0;id < len;idx++ )
{
if (startStr.charAt(idx) != endStr.charAt(idx))
{
Log.d(TAG,"breaking at idx "+ idx);
break;
}
}
filterString=endStr.substring(0,idx).replace("T", " ").replace(":",".");
Log.d(TAG,"Filter String" + filterString);
String[] children = dir.list(new JpegFilter());
List matchedFiles = new ArrayList();
Log.d(TAG,dir.getAbsolutePath()+" has "+ children.length+ " files");
int numfiles = children.length;
for(int i=0;i < numfiles;i++)
{
//Get file modify time
File file = new File(picPath+File.separator+children[i]);
// Get the last modification information.
long lastModified = file.lastModified();
if (lastModified > =startTime.toMillis(false)) //If file modified date greater than equals start time
{
Log.d(TAG,"Match! " + children[i]);
if (lastModified < =endTime.toMillis(false))//If file modified date less than equals end time
{
matchedFiles.add(picPath+File.separator+children[i]);
}
else //We are now over the time range, quit loop to save time
{
break; //TODO break out of for loop
}
}
}
if (!matchedFiles.isEmpty()) //If there are images to be processed
{
status=convertToPDF(matchedFiles,pdfFile);
Log.d(TAG,"convertToPDF() returned " + status);
}
return status;
}
This is the Filter class I use
class JpegFilter implements FilenameFilter {
public boolean accept(File dir, String name) {
return (name.startsWith(captureJPEG.filterString) && name.endsWith(".jpg"));
}
}
See http://androidworkz.com/2010/07/08/source-code-find-files-by-type-play-media-file/
public class FileUtils {
public File[] listFilesAsArray(File directory, FilenameFilter[] filter,
int recurse) {
Collection<File> files = listFiles(directory, filter, recurse);
File[] arr = new File[files.size()];
return files.toArray(arr);
}
public Collection<File> listFiles(File directory,
FilenameFilter[] filter, int recurse) {
Vector<File> files = new Vector<File>();
File[] entries = directory.listFiles();
if (entries != null) {
for (File entry : entries) {
for (FilenameFilter filefilter : filter) {
if (filter == null
|| filefilter
.accept(directory, entry.getName())) {
files.add(entry);
Log.v("FileUtils", "Added: "
+ entry.getName());
}
}
if ((recurse <= -1) || (recurse > 0 && entry.isDirectory())) {
recurse--;
files.addAll(listFiles(entry, filter, recurse));
recurse++;
}
}
}
return files;
}
}
Have you taken a look at the Android File Browser example written here? Maybe you can use this example and tweak it for your specific needs. Im not sure if this is faster, but its a good starting point to consider an alternative approach to your objective.