Moved to Gradle based build system.
Update Google Analytics to v4 as part of the change.master
parent
3390a2d6b5
commit
735f06f353
@ -1 +1,58 @@
|
||||
*DS_Store
|
||||
# Windows image file caches
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
|
||||
# Folder config file
|
||||
Desktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Windows Installer files
|
||||
*.cab
|
||||
*.msi
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# =========================
|
||||
# Operating System Files
|
||||
# =========================
|
||||
|
||||
# OSX
|
||||
# =========================
|
||||
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Icon must ends with two \r.
|
||||
Icon
|
||||
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear on external disk
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
|
||||
# Gradle
|
||||
build
|
||||
.gradle
|
||||
.gradletasknamecache
|
||||
|
||||
|
||||
#Eclipse
|
||||
.classpath
|
||||
.project
|
||||
.settings/
|
||||
|
||||
#Android studio
|
||||
.idea
|
||||
*.iml
|
||||
local.properties
|
||||
|
||||
|
||||
#Project specific
|
||||
android/routerKeygen/src/main/libs
|
||||
android/routerKeygen/src/main/obj
|
||||
|
@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
|
||||
<classpathentry kind="src" path="src"/>
|
||||
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
|
||||
<classpathentry kind="src" path="gen"/>
|
||||
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.DEPENDENCIES"/>
|
||||
<classpathentry kind="output" path="bin/classes"/>
|
||||
</classpath>
|
@ -1,53 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<?fileVersion 4.0.0?><cproject storage_type_id="org.eclipse.cdt.core.XmlProjectDescriptionStorage">
|
||||
<storageModule moduleId="org.eclipse.cdt.core.settings">
|
||||
<cconfiguration id="com.android.toolchain.gcc.885265119">
|
||||
<storageModule buildSystemId="org.eclipse.cdt.managedbuilder.core.configurationDataProvider" id="com.android.toolchain.gcc.885265119" moduleId="org.eclipse.cdt.core.settings" name="Default">
|
||||
<externalSettings/>
|
||||
<extensions>
|
||||
<extension id="org.eclipse.cdt.core.ELF" point="org.eclipse.cdt.core.BinaryParser"/>
|
||||
<extension id="org.eclipse.cdt.core.VCErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
|
||||
<extension id="org.eclipse.cdt.core.GmakeErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
|
||||
<extension id="org.eclipse.cdt.core.CWDLocator" point="org.eclipse.cdt.core.ErrorParser"/>
|
||||
<extension id="org.eclipse.cdt.core.MakeErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
|
||||
<extension id="org.eclipse.cdt.core.GCCErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
|
||||
<extension id="org.eclipse.cdt.core.GASErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
|
||||
<extension id="org.eclipse.cdt.core.GLDErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
|
||||
</extensions>
|
||||
</storageModule>
|
||||
<storageModule moduleId="cdtBuildSystem" version="4.0.0">
|
||||
<configuration artifactName="${ProjName}" buildProperties="" description="" id="com.android.toolchain.gcc.885265119" name="Default" parent="org.eclipse.cdt.build.core.emptycfg">
|
||||
<folderInfo id="com.android.toolchain.gcc.885265119.347836534" name="/" resourcePath="">
|
||||
<toolChain id="com.android.toolchain.gcc.228391802" name="com.android.toolchain.gcc" superClass="com.android.toolchain.gcc">
|
||||
<targetPlatform binaryParser="org.eclipse.cdt.core.ELF" id="com.android.targetPlatform.1736848340" isAbstract="false" superClass="com.android.targetPlatform"/>
|
||||
<builder id="com.android.builder.1227571086" keepEnvironmentInBuildfile="false" managedBuildOn="false" name="Android Builder" superClass="com.android.builder">
|
||||
<outputEntries>
|
||||
<entry flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="outputPath" name="obj"/>
|
||||
<entry flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="outputPath" name="libs"/>
|
||||
</outputEntries>
|
||||
</builder>
|
||||
<tool id="com.android.gcc.compiler.1345667542" name="Android GCC Compiler" superClass="com.android.gcc.compiler">
|
||||
<inputType id="com.android.gcc.inputType.2076735373" superClass="com.android.gcc.inputType"/>
|
||||
</tool>
|
||||
</toolChain>
|
||||
</folderInfo>
|
||||
<sourceEntries>
|
||||
<entry flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="sourcePath" name="jni"/>
|
||||
</sourceEntries>
|
||||
</configuration>
|
||||
</storageModule>
|
||||
<storageModule moduleId="org.eclipse.cdt.core.externalSettings"/>
|
||||
</cconfiguration>
|
||||
</storageModule>
|
||||
<storageModule moduleId="cdtBuildSystem" version="4.0.0">
|
||||
<project id="RouterKeygen.null.400149446" name="RouterKeygen"/>
|
||||
</storageModule>
|
||||
<storageModule moduleId="org.eclipse.cdt.core.pathentry"/>
|
||||
<storageModule moduleId="scannerConfiguration">
|
||||
<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
|
||||
<scannerConfigBuildInfo instanceId="com.android.toolchain.gcc.885265119;com.android.toolchain.gcc.885265119.347836534;com.android.gcc.compiler.1345667542;com.android.gcc.inputType.2076735373">
|
||||
<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId="com.android.AndroidPerProjectProfile"/>
|
||||
</scannerConfigBuildInfo>
|
||||
</storageModule>
|
||||
<storageModule moduleId="refreshScope"/>
|
||||
</cproject>
|
@ -1,4 +0,0 @@
|
||||
/bin
|
||||
/gen
|
||||
/obj
|
||||
.settings
|
@ -1,97 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>RouterKeygen</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.cdt.managedbuilder.core.genmakebuilder</name>
|
||||
<triggers>clean,full,incremental,</triggers>
|
||||
<arguments>
|
||||
<dictionary>
|
||||
<key>?children?</key>
|
||||
<value>?name?=outputEntries\|?children?=?name?=entry\\\\\\\|\\\|?name?=entry\\\\\\\|\\\|\||</value>
|
||||
</dictionary>
|
||||
<dictionary>
|
||||
<key>?name?</key>
|
||||
<value></value>
|
||||
</dictionary>
|
||||
<dictionary>
|
||||
<key>org.eclipse.cdt.make.core.append_environment</key>
|
||||
<value>true</value>
|
||||
</dictionary>
|
||||
<dictionary>
|
||||
<key>org.eclipse.cdt.make.core.buildArguments</key>
|
||||
<value></value>
|
||||
</dictionary>
|
||||
<dictionary>
|
||||
<key>org.eclipse.cdt.make.core.buildCommand</key>
|
||||
<value>ndk-build</value>
|
||||
</dictionary>
|
||||
<dictionary>
|
||||
<key>org.eclipse.cdt.make.core.cleanBuildTarget</key>
|
||||
<value>clean</value>
|
||||
</dictionary>
|
||||
<dictionary>
|
||||
<key>org.eclipse.cdt.make.core.contents</key>
|
||||
<value>org.eclipse.cdt.make.core.activeConfigSettings</value>
|
||||
</dictionary>
|
||||
<dictionary>
|
||||
<key>org.eclipse.cdt.make.core.enableAutoBuild</key>
|
||||
<value>false</value>
|
||||
</dictionary>
|
||||
<dictionary>
|
||||
<key>org.eclipse.cdt.make.core.enableCleanBuild</key>
|
||||
<value>true</value>
|
||||
</dictionary>
|
||||
<dictionary>
|
||||
<key>org.eclipse.cdt.make.core.enableFullBuild</key>
|
||||
<value>true</value>
|
||||
</dictionary>
|
||||
<dictionary>
|
||||
<key>org.eclipse.cdt.make.core.stopOnError</key>
|
||||
<value>true</value>
|
||||
</dictionary>
|
||||
<dictionary>
|
||||
<key>org.eclipse.cdt.make.core.useDefaultBuildCmd</key>
|
||||
<value>true</value>
|
||||
</dictionary>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>com.android.ide.eclipse.adt.ApkBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder</name>
|
||||
<triggers>full,incremental,</triggers>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>com.android.ide.eclipse.adt.AndroidNature</nature>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.eclipse.cdt.core.cnature</nature>
|
||||
<nature>org.eclipse.cdt.core.ccnature</nature>
|
||||
<nature>org.eclipse.cdt.managedbuilder.core.managedBuildNature</nature>
|
||||
<nature>org.eclipse.cdt.managedbuilder.core.ScannerConfigNature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
@ -0,0 +1,22 @@
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
compileSdkVersion 22
|
||||
buildToolsVersion "22.0.1"
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 7
|
||||
targetSdkVersion 22
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile 'com.android.support:support-v4:22.1.0'
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
* Copyright (C) 2011 Paul Burke
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
-->
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.ipaulpro.afilechooser">
|
||||
|
||||
<uses-sdk android:minSdkVersion="7" />
|
||||
|
||||
</manifest>
|
@ -0,0 +1,233 @@
|
||||
|
||||
package com.ianhanniballake.localstorage;
|
||||
|
||||
import android.content.res.AssetFileDescriptor;
|
||||
import android.database.Cursor;
|
||||
import android.database.MatrixCursor;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Point;
|
||||
import android.os.CancellationSignal;
|
||||
import android.os.Environment;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.provider.DocumentsContract.Document;
|
||||
import android.provider.DocumentsContract.Root;
|
||||
import android.provider.DocumentsProvider;
|
||||
import android.util.Log;
|
||||
import android.webkit.MimeTypeMap;
|
||||
|
||||
import com.ipaulpro.afilechooser.R;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
public class LocalStorageProvider extends DocumentsProvider {
|
||||
|
||||
public static final String AUTHORITY = "com.ianhanniballake.localstorage.documents";
|
||||
|
||||
/**
|
||||
* Default root projection: everything but Root.COLUMN_MIME_TYPES
|
||||
*/
|
||||
private final static String[] DEFAULT_ROOT_PROJECTION = new String[] {
|
||||
Root.COLUMN_ROOT_ID,
|
||||
Root.COLUMN_FLAGS, Root.COLUMN_TITLE, Root.COLUMN_DOCUMENT_ID, Root.COLUMN_ICON,
|
||||
Root.COLUMN_AVAILABLE_BYTES
|
||||
};
|
||||
/**
|
||||
* Default document projection: everything but Document.COLUMN_ICON and
|
||||
* Document.COLUMN_SUMMARY
|
||||
*/
|
||||
private final static String[] DEFAULT_DOCUMENT_PROJECTION = new String[] {
|
||||
Document.COLUMN_DOCUMENT_ID,
|
||||
Document.COLUMN_DISPLAY_NAME, Document.COLUMN_FLAGS, Document.COLUMN_MIME_TYPE,
|
||||
Document.COLUMN_SIZE,
|
||||
Document.COLUMN_LAST_MODIFIED
|
||||
};
|
||||
|
||||
@Override
|
||||
public Cursor queryRoots(final String[] projection) throws FileNotFoundException {
|
||||
// Create a cursor with either the requested fields, or the default
|
||||
// projection if "projection" is null.
|
||||
final MatrixCursor result = new MatrixCursor(projection != null ? projection
|
||||
: DEFAULT_ROOT_PROJECTION);
|
||||
// Add Home directory
|
||||
File homeDir = Environment.getExternalStorageDirectory();
|
||||
final MatrixCursor.RowBuilder row = result.newRow();
|
||||
// These columns are required
|
||||
row.add(Root.COLUMN_ROOT_ID, homeDir.getAbsolutePath());
|
||||
row.add(Root.COLUMN_DOCUMENT_ID, homeDir.getAbsolutePath());
|
||||
row.add(Root.COLUMN_TITLE, getContext().getString(R.string.internal_storage));
|
||||
row.add(Root.COLUMN_FLAGS, Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_CREATE);
|
||||
row.add(Root.COLUMN_ICON, R.drawable.ic_provider);
|
||||
// These columns are optional
|
||||
row.add(Root.COLUMN_AVAILABLE_BYTES, homeDir.getFreeSpace());
|
||||
// Root.COLUMN_MIME_TYPE is another optional column and useful if you
|
||||
// have multiple roots with different
|
||||
// types of mime types (roots that don't match the requested mime type
|
||||
// are automatically hidden)
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String createDocument(final String parentDocumentId, final String mimeType,
|
||||
final String displayName) throws FileNotFoundException {
|
||||
File newFile = new File(parentDocumentId, displayName);
|
||||
try {
|
||||
newFile.createNewFile();
|
||||
return newFile.getAbsolutePath();
|
||||
} catch (IOException e) {
|
||||
Log.e(LocalStorageProvider.class.getSimpleName(), "Error creating new file " + newFile);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AssetFileDescriptor openDocumentThumbnail(final String documentId, final Point sizeHint,
|
||||
final CancellationSignal signal) throws FileNotFoundException {
|
||||
// Assume documentId points to an image file. Build a thumbnail no
|
||||
// larger than twice the sizeHint
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
options.inJustDecodeBounds = true;
|
||||
BitmapFactory.decodeFile(documentId, options);
|
||||
final int targetHeight = 2 * sizeHint.y;
|
||||
final int targetWidth = 2 * sizeHint.x;
|
||||
final int height = options.outHeight;
|
||||
final int width = options.outWidth;
|
||||
options.inSampleSize = 1;
|
||||
if (height > targetHeight || width > targetWidth) {
|
||||
final int halfHeight = height / 2;
|
||||
final int halfWidth = width / 2;
|
||||
// Calculate the largest inSampleSize value that is a power of 2 and
|
||||
// keeps both
|
||||
// height and width larger than the requested height and width.
|
||||
while ((halfHeight / options.inSampleSize) > targetHeight
|
||||
|| (halfWidth / options.inSampleSize) > targetWidth) {
|
||||
options.inSampleSize *= 2;
|
||||
}
|
||||
}
|
||||
options.inJustDecodeBounds = false;
|
||||
Bitmap bitmap = BitmapFactory.decodeFile(documentId, options);
|
||||
// Write out the thumbnail to a temporary file
|
||||
File tempFile = null;
|
||||
FileOutputStream out = null;
|
||||
try {
|
||||
tempFile = File.createTempFile("thumbnail", null, getContext().getCacheDir());
|
||||
out = new FileOutputStream(tempFile);
|
||||
bitmap.compress(Bitmap.CompressFormat.PNG, 90, out);
|
||||
} catch (IOException e) {
|
||||
Log.e(LocalStorageProvider.class.getSimpleName(), "Error writing thumbnail", e);
|
||||
return null;
|
||||
} finally {
|
||||
if (out != null)
|
||||
try {
|
||||
out.close();
|
||||
} catch (IOException e) {
|
||||
Log.e(LocalStorageProvider.class.getSimpleName(), "Error closing thumbnail", e);
|
||||
}
|
||||
}
|
||||
// It appears the Storage Framework UI caches these results quite
|
||||
// aggressively so there is little reason to
|
||||
// write your own caching layer beyond what you need to return a single
|
||||
// AssetFileDescriptor
|
||||
return new AssetFileDescriptor(ParcelFileDescriptor.open(tempFile,
|
||||
ParcelFileDescriptor.MODE_READ_ONLY), 0,
|
||||
AssetFileDescriptor.UNKNOWN_LENGTH);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor queryChildDocuments(final String parentDocumentId, final String[] projection,
|
||||
final String sortOrder) throws FileNotFoundException {
|
||||
// Create a cursor with either the requested fields, or the default
|
||||
// projection if "projection" is null.
|
||||
final MatrixCursor result = new MatrixCursor(projection != null ? projection
|
||||
: DEFAULT_DOCUMENT_PROJECTION);
|
||||
final File parent = new File(parentDocumentId);
|
||||
for (File file : parent.listFiles()) {
|
||||
// Don't show hidden files/folders
|
||||
if (!file.getName().startsWith(".")) {
|
||||
// Adds the file's display name, MIME type, size, and so on.
|
||||
includeFile(result, file);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor queryDocument(final String documentId, final String[] projection)
|
||||
throws FileNotFoundException {
|
||||
// Create a cursor with either the requested fields, or the default
|
||||
// projection if "projection" is null.
|
||||
final MatrixCursor result = new MatrixCursor(projection != null ? projection
|
||||
: DEFAULT_DOCUMENT_PROJECTION);
|
||||
includeFile(result, new File(documentId));
|
||||
return result;
|
||||
}
|
||||
|
||||
private void includeFile(final MatrixCursor result, final File file)
|
||||
throws FileNotFoundException {
|
||||
final MatrixCursor.RowBuilder row = result.newRow();
|
||||
// These columns are required
|
||||
row.add(Document.COLUMN_DOCUMENT_ID, file.getAbsolutePath());
|
||||
row.add(Document.COLUMN_DISPLAY_NAME, file.getName());
|
||||
String mimeType = getDocumentType(file.getAbsolutePath());
|
||||
row.add(Document.COLUMN_MIME_TYPE, mimeType);
|
||||
int flags = file.canWrite() ? Document.FLAG_SUPPORTS_DELETE | Document.FLAG_SUPPORTS_WRITE
|
||||
: 0;
|
||||
// We only show thumbnails for image files - expect a call to
|
||||
// openDocumentThumbnail for each file that has
|
||||
// this flag set
|
||||
if (mimeType.startsWith("image/"))
|
||||
flags |= Document.FLAG_SUPPORTS_THUMBNAIL;
|
||||
row.add(Document.COLUMN_FLAGS, flags);
|
||||
// COLUMN_SIZE is required, but can be null
|
||||
row.add(Document.COLUMN_SIZE, file.length());
|
||||
// These columns are optional
|
||||
row.add(Document.COLUMN_LAST_MODIFIED, file.lastModified());
|
||||
// Document.COLUMN_ICON can be a resource id identifying a custom icon.
|
||||
// The system provides default icons
|
||||
// based on mime type
|
||||
// Document.COLUMN_SUMMARY is optional additional information about the
|
||||
// file
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDocumentType(final String documentId) throws FileNotFoundException {
|
||||
File file = new File(documentId);
|
||||
if (file.isDirectory())
|
||||
return Document.MIME_TYPE_DIR;
|
||||
// From FileProvider.getType(Uri)
|
||||
final int lastDot = file.getName().lastIndexOf('.');
|
||||
if (lastDot >= 0) {
|
||||
final String extension = file.getName().substring(lastDot + 1);
|
||||
final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
|
||||
if (mime != null) {
|
||||
return mime;
|
||||
}
|
||||
}
|
||||
return "application/octet-stream";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteDocument(final String documentId) throws FileNotFoundException {
|
||||
new File(documentId).delete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParcelFileDescriptor openDocument(final String documentId, final String mode,
|
||||
final CancellationSignal signal) throws FileNotFoundException {
|
||||
File file = new File(documentId);
|
||||
final boolean isWrite = (mode.indexOf('w') != -1);
|
||||
if (isWrite) {
|
||||
return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_WRITE);
|
||||
} else {
|
||||
return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
return true;
|
||||
}
|
||||
}
|
@ -0,0 +1,218 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Paul Burke
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.ipaulpro.afilechooser;
|
||||
|
||||
import android.app.ActionBar;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.FragmentManager.BackStackEntry;
|
||||
import android.support.v4.app.FragmentManager.OnBackStackChangedListener;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* Main Activity that handles the FileListFragments
|
||||
*
|
||||
* @version 2013-06-25
|
||||
* @author paulburke (ipaulpro)
|
||||
*/
|
||||
public class FileChooserActivity extends FragmentActivity implements
|
||||
OnBackStackChangedListener, FileListFragment.Callbacks {
|
||||
|
||||
public static final String PATH = "path";
|
||||
public static final String EXTERNAL_BASE_PATH = Environment
|
||||
.getExternalStorageDirectory().getAbsolutePath();
|
||||
|
||||
private static final boolean HAS_ACTIONBAR = Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;
|
||||
|
||||
private FragmentManager mFragmentManager;
|
||||
private BroadcastReceiver mStorageListener = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Toast.makeText(context, R.string.storage_removed, Toast.LENGTH_LONG).show();
|
||||
finishWithResult(null);
|
||||
}
|
||||
};
|
||||
|
||||
private String mPath;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
mFragmentManager = getSupportFragmentManager();
|
||||
mFragmentManager.addOnBackStackChangedListener(this);
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
mPath = EXTERNAL_BASE_PATH;
|
||||
addFragment();
|
||||
} else {
|
||||
mPath = savedInstanceState.getString(PATH);
|
||||
}
|
||||
|
||||
setTitle(mPath);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
|
||||
unregisterStorageListener();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
registerStorageListener();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
|
||||
outState.putString(PATH, mPath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackStackChanged() {
|
||||
|
||||
int count = mFragmentManager.getBackStackEntryCount();
|
||||
if (count > 0) {
|
||||
BackStackEntry fragment = mFragmentManager.getBackStackEntryAt(count - 1);
|
||||
mPath = fragment.getName();
|
||||
} else {
|
||||
mPath = EXTERNAL_BASE_PATH;
|
||||
}
|
||||
|
||||
setTitle(mPath);
|
||||
if (HAS_ACTIONBAR)
|
||||
invalidateOptionsMenu();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
if (HAS_ACTIONBAR) {
|
||||
boolean hasBackStack = mFragmentManager.getBackStackEntryCount() > 0;
|
||||
|
||||
ActionBar actionBar = getActionBar();
|
||||
actionBar.setDisplayHomeAsUpEnabled(hasBackStack);
|
||||
actionBar.setHomeButtonEnabled(hasBackStack);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
mFragmentManager.popBackStack();
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the initial Fragment with given path.
|
||||
*/
|
||||
private void addFragment() {
|
||||
FileListFragment fragment = FileListFragment.newInstance(mPath);
|
||||
mFragmentManager.beginTransaction()
|
||||
.add(android.R.id.content, fragment).commit();
|
||||
}
|
||||
|
||||
/**
|
||||
* "Replace" the existing Fragment with a new one using given path. We're
|
||||
* really adding a Fragment to the back stack.
|
||||
*
|
||||
* @param file The file (directory) to display.
|
||||
*/
|
||||
private void replaceFragment(File file) {
|
||||
mPath = file.getAbsolutePath();
|
||||
|
||||
FileListFragment fragment = FileListFragment.newInstance(mPath);
|
||||
mFragmentManager.beginTransaction()
|
||||
.replace(android.R.id.content, fragment)
|
||||
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
|
||||
.addToBackStack(mPath).commit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Finish this Activity with a result code and URI of the selected file.
|
||||
*
|
||||
* @param file The file selected.
|
||||
*/
|
||||
private void finishWithResult(File file) {
|
||||
if (file != null) {
|
||||
Uri uri = Uri.fromFile(file);
|
||||
setResult(RESULT_OK, new Intent().setData(uri));
|
||||
finish();
|
||||
} else {
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the user selects a File
|
||||
*
|
||||
* @param file The file that was selected
|
||||
*/
|
||||
@Override
|
||||
public void onFileSelected(File file) {
|
||||
if (file != null) {
|
||||
if (file.isDirectory()) {
|
||||
replaceFragment(file);
|
||||
} else {
|
||||
finishWithResult(file);
|
||||
}
|
||||
} else {
|
||||
Toast.makeText(FileChooserActivity.this, R.string.error_selecting_file,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the external storage BroadcastReceiver.
|
||||
*/
|
||||
private void registerStorageListener() {
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(Intent.ACTION_MEDIA_REMOVED);
|
||||
registerReceiver(mStorageListener, filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister the external storage BroadcastReceiver.
|
||||
*/
|
||||
private void unregisterStorageListener() {
|
||||
unregisterReceiver(mStorageListener);
|
||||
}
|
||||
}
|
@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Paul Burke
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.ipaulpro.afilechooser;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* List adapter for Files.
|
||||
*
|
||||
* @version 2013-12-11
|
||||
* @author paulburke (ipaulpro)
|
||||
*/
|
||||
public class FileListAdapter extends BaseAdapter {
|
||||
|
||||
private final static int ICON_FOLDER = R.drawable.ic_folder;
|
||||
private final static int ICON_FILE = R.drawable.ic_file;
|
||||
|
||||
private final LayoutInflater mInflater;
|
||||
|
||||
private List<File> mData = new ArrayList<File>();
|
||||
|
||||
public FileListAdapter(Context context) {
|
||||
mInflater = LayoutInflater.from(context);
|
||||
}
|
||||
|
||||
public void add(File file) {
|
||||
mData.add(file);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void remove(File file) {
|
||||
mData.remove(file);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void insert(File file, int index) {
|
||||
mData.add(index, file);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
mData.clear();
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getItem(int position) {
|
||||
return mData.get(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return mData.size();
|
||||
}
|
||||
|
||||
public List<File> getListItems() {
|
||||
return mData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the list items without notifying on the clear. This prevents loss of
|
||||
* scroll position.
|
||||
*
|
||||
* @param data
|
||||
*/
|
||||
public void setListItems(List<File> data) {
|
||||
mData = data;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
View row = convertView;
|
||||
|
||||
if (row == null)
|
||||
row = mInflater.inflate(R.layout.file, parent, false);
|
||||
|
||||
TextView view = (TextView) row;
|
||||
|
||||
// Get the file at the current position
|
||||
final File file = getItem(position);
|
||||
|
||||
// Set the TextView as the file name
|
||||
view.setText(file.getName());
|
||||
|
||||
// If the item is not a directory, use the file icon
|
||||
int icon = file.isDirectory() ? ICON_FOLDER : ICON_FILE;
|
||||
view.setCompoundDrawablesWithIntrinsicBounds(icon, 0, 0, 0);
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,136 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Paul Burke
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.ipaulpro.afilechooser;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.support.v4.app.ListFragment;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.view.View;
|
||||
import android.widget.ListView;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Fragment that displays a list of Files in a given path.
|
||||
*
|
||||
* @version 2013-12-11
|
||||
* @author paulburke (ipaulpro)
|
||||
*/
|
||||
public class FileListFragment extends ListFragment implements
|
||||
LoaderManager.LoaderCallbacks<List<File>> {
|
||||
|
||||
/**
|
||||
* Interface to listen for events.
|
||||
*/
|
||||
public interface Callbacks {
|
||||
/**
|
||||
* Called when a file is selected from the list.
|
||||
*
|
||||
* @param file The file selected
|
||||
*/
|
||||
public void onFileSelected(File file);
|
||||
}
|
||||
|
||||
private static final int LOADER_ID = 0;
|
||||
|
||||
private FileListAdapter mAdapter;
|
||||
private String mPath;
|
||||
|
||||
private Callbacks mListener;
|
||||
|
||||
/**
|
||||
* Create a new instance with the given file path.
|
||||
*
|
||||
* @param path The absolute path of the file (directory) to display.
|
||||
* @return A new Fragment with the given file path.
|
||||
*/
|
||||
public static FileListFragment newInstance(String path) {
|
||||
FileListFragment fragment = new FileListFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putString(FileChooserActivity.PATH, path);
|
||||
fragment.setArguments(args);
|
||||
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
|
||||
try {
|
||||
mListener = (Callbacks) activity;
|
||||
} catch (ClassCastException e) {
|
||||
throw new ClassCastException(activity.toString()
|
||||
+ " must implement FileListFragment.Callbacks");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
mAdapter = new FileListAdapter(getActivity());
|
||||
mPath = getArguments() != null ? getArguments().getString(
|
||||
FileChooserActivity.PATH) : Environment
|
||||
.getExternalStorageDirectory().getAbsolutePath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
setEmptyText(getString(R.string.empty_directory));
|
||||
setListAdapter(mAdapter);
|
||||
setListShown(false);
|
||||
|
||||
getLoaderManager().initLoader(LOADER_ID, null, this);
|
||||
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onListItemClick(ListView l, View v, int position, long id) {
|
||||
FileListAdapter adapter = (FileListAdapter) l.getAdapter();
|
||||
if (adapter != null) {
|
||||
File file = (File) adapter.getItem(position);
|
||||
mPath = file.getAbsolutePath();
|
||||
mListener.onFileSelected(file);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Loader<List<File>> onCreateLoader(int id, Bundle args) {
|
||||
return new FileLoader(getActivity(), mPath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<List<File>> loader, List<File> data) {
|
||||
mAdapter.setListItems(data);
|
||||
|
||||
if (isResumed())
|
||||
setListShown(true);
|
||||
else
|
||||
setListShownNoAnimation(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<List<File>> loader) {
|
||||
mAdapter.clear();
|
||||
}
|
||||
}
|
@ -0,0 +1,149 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Paul Burke
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.ipaulpro.afilechooser;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.FileObserver;
|
||||
import android.support.v4.content.AsyncTaskLoader;
|
||||
|
||||
import com.ipaulpro.afilechooser.utils.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Loader that returns a list of Files in a given file path.
|
||||
*
|
||||
* @version 2013-12-11
|
||||
* @author paulburke (ipaulpro)
|
||||
*/
|
||||
public class FileLoader extends AsyncTaskLoader<List<File>> {
|
||||
|
||||
private static final int FILE_OBSERVER_MASK = FileObserver.CREATE
|
||||
| FileObserver.DELETE | FileObserver.DELETE_SELF
|
||||
| FileObserver.MOVED_FROM | FileObserver.MOVED_TO
|
||||
| FileObserver.MODIFY | FileObserver.MOVE_SELF;
|
||||
|
||||
private FileObserver mFileObserver;
|
||||
|
||||
private List<File> mData;
|
||||
private String mPath;
|
||||
|
||||
public FileLoader(Context context, String path) {
|
||||
super(context);
|
||||
this.mPath = path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<File> loadInBackground() {
|
||||
|
||||
ArrayList<File> list = new ArrayList<File>();
|
||||
|
||||
// Current directory File instance
|
||||
final File pathDir = new File(mPath);
|
||||
|
||||
// List file in this directory with the directory filter
|
||||
final File[] dirs = pathDir.listFiles(FileUtils.sDirFilter);
|
||||
if (dirs != null) {
|
||||
// Sort the folders alphabetically
|
||||
Arrays.sort(dirs, FileUtils.sComparator);
|
||||
// Add each folder to the File list for the list adapter
|
||||
for (File dir : dirs)
|
||||
list.add(dir);
|
||||
}
|
||||
|
||||
// List file in this directory with the file filter
|
||||
final File[] files = pathDir.listFiles(FileUtils.sFileFilter);
|
||||
if (files != null) {
|
||||
// Sort the files alphabetically
|
||||
Arrays.sort(files, FileUtils.sComparator);
|
||||
// Add each file to the File list for the list adapter
|
||||
for (File file : files)
|
||||
list.add(file);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deliverResult(List<File> data) {
|
||||
if (isReset()) {
|
||||
onReleaseResources(data);
|
||||
return;
|
||||
}
|
||||
|
||||
List<File> oldData = mData;
|
||||
mData = data;
|
||||
|
||||
if (isStarted())
|
||||
super.deliverResult(data);
|
||||
|
||||
if (oldData != null && oldData != data)
|
||||
onReleaseResources(oldData);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStartLoading() {
|
||||
if (mData != null)
|
||||
deliverResult(mData);
|
||||
|
||||
if (mFileObserver == null) {
|
||||
mFileObserver = new FileObserver(mPath, FILE_OBSERVER_MASK) {
|
||||
@Override
|
||||
public void onEvent(int event, String path) {
|
||||
onContentChanged();
|
||||
}
|
||||
};
|
||||
}
|
||||
mFileObserver.startWatching();
|
||||
|
||||
if (takeContentChanged() || mData == null)
|
||||
forceLoad();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStopLoading() {
|
||||
cancelLoad();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onReset() {
|
||||
onStopLoading();
|
||||
|
||||
if (mData != null) {
|
||||
onReleaseResources(mData);
|
||||
mData = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCanceled(List<File> data) {
|
||||
super.onCanceled(data);
|
||||
|
||||
onReleaseResources(data);
|
||||
}
|
||||
|
||||
protected void onReleaseResources(List<File> data) {
|
||||
|
||||
if (mFileObserver != null) {
|
||||
mFileObserver.stopWatching();
|
||||
mFileObserver = null;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,528 @@
|
||||
/*
|
||||
* Copyright (C) 2007-2008 OpenIntents.org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.ipaulpro.afilechooser.utils;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentUris;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.database.DatabaseUtils;
|
||||
import android.graphics.Bitmap;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.provider.DocumentsContract;
|
||||
import android.provider.MediaStore;
|
||||
import android.util.Log;
|
||||
import android.webkit.MimeTypeMap;
|
||||
|
||||
import com.ianhanniballake.localstorage.LocalStorageProvider;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.Comparator;
|
||||
|
||||
/**
|
||||
* @version 2009-07-03
|
||||
* @author Peli
|
||||
* @version 2013-12-11
|
||||
* @author paulburke (ipaulpro)
|
||||
*/
|
||||
public class FileUtils {
|
||||
private FileUtils() {} //private constructor to enforce Singleton pattern
|
||||
|
||||
/** TAG for log messages. */
|
||||
static final String TAG = "FileUtils";
|
||||
private static final boolean DEBUG = false; // Set to true to enable logging
|
||||
|
||||
public static final String MIME_TYPE_AUDIO = "audio/*";
|
||||
public static final String MIME_TYPE_TEXT = "text/*";
|
||||
public static final String MIME_TYPE_IMAGE = "image/*";
|
||||
public static final String MIME_TYPE_VIDEO = "video/*";
|
||||
public static final String MIME_TYPE_APP = "application/*";
|
||||
|
||||
public static final String HIDDEN_PREFIX = ".";
|
||||
|
||||
/**
|
||||
* Gets the extension of a file name, like ".png" or ".jpg".
|
||||
*
|
||||
* @param uri
|
||||
* @return Extension including the dot("."); "" if there is no extension;
|
||||
* null if uri was null.
|
||||
*/
|
||||
public static String getExtension(String uri) {
|
||||
if (uri == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int dot = uri.lastIndexOf(".");
|
||||
if (dot >= 0) {
|
||||
return uri.substring(dot);
|
||||
} else {
|
||||
// No extension.
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Whether the URI is a local one.
|
||||
*/
|
||||
public static boolean isLocal(String url) {
|
||||
if (url != null && !url.startsWith("http://") && !url.startsWith("https://")) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return True if Uri is a MediaStore Uri.
|
||||
* @author paulburke
|
||||
*/
|
||||
public static boolean isMediaUri(Uri uri) {
|
||||
return "media".equalsIgnoreCase(uri.getAuthority());
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert File into Uri.
|
||||
*
|
||||
* @param file
|
||||
* @return uri
|
||||
*/
|
||||
public static Uri getUri(File file) {
|
||||
if (file != null) {
|
||||
return Uri.fromFile(file);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path only (without file name).
|
||||
*
|
||||
* @param file
|
||||
* @return
|
||||
*/
|
||||
public static File getPathWithoutFilename(File file) {
|
||||
if (file != null) {
|
||||
if (file.isDirectory()) {
|
||||
// no file to be split off. Return everything
|
||||
return file;
|
||||
} else {
|
||||
String filename = file.getName();
|
||||
String filepath = file.getAbsolutePath();
|
||||
|
||||
// Construct path without file name.
|
||||
String pathwithoutname = filepath.substring(0,
|
||||
filepath.length() - filename.length());
|
||||
if (pathwithoutname.endsWith("/")) {
|
||||
pathwithoutname = pathwithoutname.substring(0, pathwithoutname.length() - 1);
|
||||
}
|
||||
return new File(pathwithoutname);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The MIME type for the given file.
|
||||
*/
|
||||
public static String getMimeType(File file) {
|
||||
|
||||
String extension = getExtension(file.getName());
|
||||
|
||||
if (extension.length() > 0)
|
||||
return MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension.substring(1));
|
||||
|
||||
return "application/octet-stream";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The MIME type for the give Uri.
|
||||
*/
|
||||
public static String getMimeType(Context context, Uri uri) {
|
||||
File file = new File(getPath(context, uri));
|
||||
return getMimeType(file);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param uri The Uri to check.
|
||||
* @return Whether the Uri authority is {@link LocalStorageProvider}.
|
||||
* @author paulburke
|
||||
*/
|
||||
public static boolean isLocalStorageDocument(Uri uri) {
|
||||
return LocalStorageProvider.AUTHORITY.equals(uri.getAuthority());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param uri The Uri to check.
|
||||
* @return Whether the Uri authority is ExternalStorageProvider.
|
||||
* @author paulburke
|
||||
*/
|
||||
public static boolean isExternalStorageDocument(Uri uri) {
|
||||
return "com.android.externalstorage.documents".equals(uri.getAuthority());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param uri The Uri to check.
|
||||
* @return Whether the Uri authority is DownloadsProvider.
|
||||
* @author paulburke
|
||||
*/
|
||||
public static boolean isDownloadsDocument(Uri uri) {
|
||||
return "com.android.providers.downloads.documents".equals(uri.getAuthority());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param uri The Uri to check.
|
||||
* @return Whether the Uri authority is MediaProvider.
|
||||
* @author paulburke
|
||||
*/
|
||||
public static boolean isMediaDocument(Uri uri) {
|
||||
return "com.android.providers.media.documents".equals(uri.getAuthority());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param uri The Uri to check.
|
||||
* @return Whether the Uri authority is Google Photos.
|
||||
*/
|
||||
public static boolean isGooglePhotosUri(Uri uri) {
|
||||
return "com.google.android.apps.photos.content".equals(uri.getAuthority());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of the data column for this Uri. This is useful for
|
||||
* MediaStore Uris, and other file-based ContentProviders.
|
||||
*
|
||||
* @param context The context.
|
||||
* @param uri The Uri to query.
|
||||
* @param selection (Optional) Filter used in the query.
|
||||
* @param selectionArgs (Optional) Selection arguments used in the query.
|
||||
* @return The value of the _data column, which is typically a file path.
|
||||
* @author paulburke
|
||||
*/
|
||||
public static String getDataColumn(Context context, Uri uri, String selection,
|
||||
String[] selectionArgs) {
|
||||
|
||||
Cursor cursor = null;
|
||||
final String column = "_data";
|
||||
final String[] projection = {
|
||||
column
|
||||
};
|
||||
|
||||
try {
|
||||
cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
|
||||
null);
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
if (DEBUG)
|
||||
DatabaseUtils.dumpCursor(cursor);
|
||||
|
||||
final int column_index = cursor.getColumnIndexOrThrow(column);
|
||||
return cursor.getString(column_index);
|
||||
}
|
||||
} finally {
|
||||
if (cursor != null)
|
||||
cursor.close();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a file path from a Uri. This will get the the path for Storage Access
|
||||
* Framework Documents, as well as the _data field for the MediaStore and
|
||||
* other file-based ContentProviders.<br>
|
||||
* <br>
|
||||
* Callers should check whether the path is local before assuming it
|
||||
* represents a local file.
|
||||
*
|
||||
* @param context The context.
|
||||
* @param uri The Uri to query.
|
||||
* @see #isLocal(String)
|
||||
* @see #getFile(Context, Uri)
|
||||
* @author paulburke
|
||||
*/
|
||||
public static String getPath(final Context context, final Uri uri) {
|
||||
|
||||
if (DEBUG)
|
||||
Log.d(TAG + " File -",
|
||||
"Authority: " + uri.getAuthority() +
|
||||
", Fragment: " + uri.getFragment() +
|
||||
", Port: " + uri.getPort() +
|
||||
", Query: " + uri.getQuery() +
|
||||
", Scheme: " + uri.getScheme() +
|
||||
", Host: " + uri.getHost() +
|
||||
", Segments: " + uri.getPathSegments().toString()
|
||||
);
|
||||
|
||||
final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
|
||||
|
||||
// DocumentProvider
|
||||
if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
|
||||
// LocalStorageProvider
|
||||
if (isLocalStorageDocument(uri)) {
|
||||
// The path is the id
|
||||
return DocumentsContract.getDocumentId(uri);
|
||||
}
|
||||
// ExternalStorageProvider
|
||||
else if (isExternalStorageDocument(uri)) {
|
||||
final String docId = DocumentsContract.getDocumentId(uri);
|
||||
final String[] split = docId.split(":");
|
||||
final String type = split[0];
|
||||
|
||||
if ("primary".equalsIgnoreCase(type)) {
|
||||
return Environment.getExternalStorageDirectory() + "/" + split[1];
|
||||
}
|
||||
|
||||
// TODO handle non-primary volumes
|
||||
}
|
||||
// DownloadsProvider
|
||||
else if (isDownloadsDocument(uri)) {
|
||||
|
||||
final String id = DocumentsContract.getDocumentId(uri);
|
||||
final Uri contentUri = ContentUris.withAppendedId(
|
||||
Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
|
||||
|
||||
return getDataColumn(context, contentUri, null, null);
|
||||
}
|
||||
// MediaProvider
|
||||
else if (isMediaDocument(uri)) {
|
||||
final String docId = DocumentsContract.getDocumentId(uri);
|
||||
final String[] split = docId.split(":");
|
||||
final String type = split[0];
|
||||
|
||||
Uri contentUri = null;
|
||||
if ("image".equals(type)) {
|
||||
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
|
||||
} else if ("video".equals(type)) {
|
||||
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
|
||||
} else if ("audio".equals(type)) {
|
||||
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
|
||||
}
|
||||
|
||||
final String selection = "_id=?";
|
||||
final String[] selectionArgs = new String[] {
|
||||
split[1]
|
||||
};
|
||||
|
||||
return getDataColumn(context, contentUri, selection, selectionArgs);
|
||||
}
|
||||
}
|
||||
// MediaStore (and general)
|
||||
else if ("content".equalsIgnoreCase(uri.getScheme())) {
|
||||
|
||||
// Return the remote address
|
||||
if (isGooglePhotosUri(uri))
|
||||
return uri.getLastPathSegment();
|
||||
|
||||
return getDataColumn(context, uri, null, null);
|
||||
}
|
||||
// File
|
||||
else if ("file".equalsIgnoreCase(uri.getScheme())) {
|
||||
return uri.getPath();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Uri into File, if possible.
|
||||
*
|
||||
* @return file A local file that the Uri was pointing to, or null if the
|
||||
* Uri is unsupported or pointed to a remote resource.
|
||||
* @see #getPath(Context, Uri)
|
||||
* @author paulburke
|
||||
*/
|
||||
public static File getFile(Context context, Uri uri) {
|
||||
if (uri != null) {
|
||||
String path = getPath(context, uri);
|
||||
if (path != null && isLocal(path)) {
|
||||
return new File(path);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the file size in a human-readable string.
|
||||
*
|
||||
* @param size
|
||||
* @return
|
||||
* @author paulburke
|
||||
*/
|
||||
public static String getReadableFileSize(int size) {
|
||||
final int BYTES_IN_KILOBYTES = 1024;
|
||||
final DecimalFormat dec = new DecimalFormat("###.#");
|
||||
final String KILOBYTES = " KB";
|
||||
final String MEGABYTES = " MB";
|
||||
final String GIGABYTES = " GB";
|
||||
float fileSize = 0;
|
||||
String suffix = KILOBYTES;
|
||||
|
||||
if (size > BYTES_IN_KILOBYTES) {
|
||||
fileSize = size / BYTES_IN_KILOBYTES;
|
||||
if (fileSize > BYTES_IN_KILOBYTES) {
|
||||
fileSize = fileSize / BYTES_IN_KILOBYTES;
|
||||
if (fileSize > BYTES_IN_KILOBYTES) {
|
||||
fileSize = fileSize / BYTES_IN_KILOBYTES;
|
||||
suffix = GIGABYTES;
|
||||
} else {
|
||||
suffix = MEGABYTES;
|
||||
}
|
||||
}
|
||||
}
|
||||
return String.valueOf(dec.format(fileSize) + suffix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to retrieve the thumbnail of given File from the MediaStore. This
|
||||
* should not be called on the UI thread.
|
||||
*
|
||||
* @param context
|
||||
* @param file
|
||||
* @return
|
||||
* @author paulburke
|
||||
*/
|
||||
public static Bitmap getThumbnail(Context context, File file) {
|
||||
return getThumbnail(context, getUri(file), getMimeType(file));
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to retrieve the thumbnail of given Uri from the MediaStore. This
|
||||