552 lines
18 KiB
Java
552 lines
18 KiB
Java
package de.vanitasvitae.sticktoalbum;
|
|
|
|
import android.app.AlertDialog;
|
|
import android.content.ContentUris;
|
|
import android.content.Context;
|
|
import android.content.DialogInterface;
|
|
import android.content.Intent;
|
|
import android.content.SharedPreferences;
|
|
import android.database.Cursor;
|
|
import android.media.MediaScannerConnection;
|
|
import android.net.Uri;
|
|
import android.os.AsyncTask;
|
|
import android.os.Bundle;
|
|
import android.os.Environment;
|
|
import android.preference.PreferenceManager;
|
|
import android.provider.MediaStore;
|
|
import android.support.v7.app.ActionBarActivity;
|
|
import android.text.InputType;
|
|
import android.util.Log;
|
|
import android.view.LayoutInflater;
|
|
import android.view.View;
|
|
import android.webkit.MimeTypeMap;
|
|
import android.widget.AdapterView;
|
|
import android.widget.ArrayAdapter;
|
|
import android.widget.EditText;
|
|
import android.widget.ListView;
|
|
import android.widget.Toast;
|
|
|
|
import java.io.File;
|
|
import java.io.FileInputStream;
|
|
import java.io.FileNotFoundException;
|
|
import java.io.FileOutputStream;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.io.OutputStream;
|
|
import java.util.ArrayList;
|
|
|
|
/**
|
|
* "Stick" your photos and videos into albums by moving them into new folders and updating
|
|
* Androids MediaStore.
|
|
* @author vanitas
|
|
*/
|
|
public class MainActivity extends ActionBarActivity
|
|
{
|
|
private ArrayList<String> sharedMedia;
|
|
private static String Tag = "stickToAlbum";
|
|
SharedPreferences sharedPref = null;
|
|
|
|
@Override
|
|
protected void onCreate(Bundle savedInstanceState)
|
|
{
|
|
super.onCreate(savedInstanceState);
|
|
setContentView(R.layout.activity_main);
|
|
|
|
sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
|
|
if(sharedPref.getString(SettingsActivity.KEY_PREF_APP_PATH, "").isEmpty())
|
|
{
|
|
resetAppDirectoryPath();
|
|
}
|
|
sharedMedia = new ArrayList<>();
|
|
|
|
Intent intent = getIntent();
|
|
String action = intent.getAction();
|
|
String type = intent.getType();
|
|
|
|
Log.d(Tag, "Intent: " + action + ", " + type);
|
|
|
|
if (Intent.ACTION_SEND.equals(action) && type != null)
|
|
{
|
|
if (type.startsWith("image/") || type.startsWith("video/"))
|
|
{
|
|
handleSendSingleFile(intent);
|
|
}
|
|
}
|
|
else if (Intent.ACTION_SEND_MULTIPLE.equals(action) && type != null)
|
|
{
|
|
handleSendMultipleFiles(intent);
|
|
}
|
|
else
|
|
{
|
|
handleStartFromHomeScreen();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle first run
|
|
*/
|
|
protected void onResume()
|
|
{
|
|
super.onResume();
|
|
|
|
if (sharedPref.getBoolean("firstrun", true))
|
|
{
|
|
AlertDialog.Builder firstRunDialog = new AlertDialog.Builder(this);
|
|
firstRunDialog.setTitle(R.string.dialog_title_first_run);
|
|
firstRunDialog.setMessage(R.string.dialog_content_first_run);
|
|
firstRunDialog.setPositiveButton(R.string.understood, new DialogInterface.OnClickListener()
|
|
{
|
|
@Override
|
|
public void onClick(DialogInterface dialog, int which)
|
|
{
|
|
dialog.dismiss();
|
|
}
|
|
});
|
|
firstRunDialog.setCancelable(false);
|
|
firstRunDialog.show();
|
|
sharedPref.edit().putBoolean("firstrun", false).apply();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle one single file being shared with the application.
|
|
* Add the files Uri to the list of files to move and open the saveDialog.
|
|
* @param intent Intent
|
|
*/
|
|
void handleSendSingleFile(Intent intent) {
|
|
Uri imageUri = intent.getParcelableExtra(Intent.EXTRA_STREAM);
|
|
if (imageUri != null) {
|
|
String url = getRealPathFromURI(this, imageUri);
|
|
if(url != null)
|
|
{
|
|
sharedMedia.add(url);
|
|
openSaveDialog();
|
|
}
|
|
else
|
|
{
|
|
handleUnsupportedMedia();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle multiple files being shared with the application.
|
|
* Add the files Uris to the list of files to move and open the saveDialog.
|
|
* @param intent Intent
|
|
*/
|
|
void handleSendMultipleFiles(Intent intent) {
|
|
ArrayList<Uri> imageUris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
|
|
if (imageUris != null)
|
|
{
|
|
for(Uri u : imageUris)
|
|
{
|
|
String url = getRealPathFromURI(this, u);
|
|
if(url != null)
|
|
{
|
|
sharedMedia.add(url);
|
|
}
|
|
}
|
|
}
|
|
if(imageUris == null || imageUris.size()==0) handleUnsupportedMedia();
|
|
else openSaveDialog();
|
|
}
|
|
|
|
/**
|
|
* Show Dialog to inform the user about the fact, that the feature he/she wants to use is not
|
|
* implemented
|
|
*/
|
|
@SuppressWarnings("unused")
|
|
void handleUnimplementedFeature()
|
|
{
|
|
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
|
builder.setTitle(R.string.dialog_title_unimplemented_feature);
|
|
builder.setMessage(R.string.dialog_content_unimplemented_feature);
|
|
builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener()
|
|
{
|
|
@Override
|
|
public void onClick(DialogInterface dialog, int which)
|
|
{
|
|
dialog.cancel();
|
|
finish();
|
|
}
|
|
});
|
|
builder.show();
|
|
}
|
|
|
|
void handleUnsupportedMedia()
|
|
{
|
|
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
|
builder.setTitle(R.string.dialog_title_unsupported_media);
|
|
builder.setMessage(R.string.dialog_content_unsupported_media);
|
|
builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener()
|
|
{
|
|
@Override
|
|
public void onClick(DialogInterface dialog, int which)
|
|
{
|
|
dialog.cancel();
|
|
finish();
|
|
}
|
|
});
|
|
builder.show();
|
|
}
|
|
|
|
void handleCouldNotWrite(String... urls)
|
|
{
|
|
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
|
builder.setTitle(R.string.dialog_title_write_failed);
|
|
String msg = getResources().getString(R.string.dialog_content_write_failed) + "\n\n";
|
|
for(String u : urls) msg = msg + u + "\n";
|
|
msg = msg.substring(0,msg.length()-1);
|
|
builder.setMessage(msg);
|
|
builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener()
|
|
{
|
|
@Override
|
|
public void onClick(DialogInterface dialog, int which)
|
|
{
|
|
dialog.cancel();
|
|
finish();
|
|
}
|
|
});
|
|
builder.show();
|
|
}
|
|
|
|
/**
|
|
* Open a Dialog that prompts the user to enter an album name.
|
|
* Reopens dialog, if album name is empty.
|
|
* Closes app after moving files or when canceled.
|
|
*/
|
|
private void openSaveDialog()
|
|
{
|
|
final Context context = this;
|
|
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
|
builder.setTitle(getResources().getText(R.string.dialog_title_album_name));
|
|
|
|
LayoutInflater inflater = this.getLayoutInflater();
|
|
View dialogView = inflater.inflate(R.layout.save_dialog, null);
|
|
builder.setView(dialogView);
|
|
|
|
final EditText input = (EditText) dialogView.findViewById(R.id.album_name);
|
|
input.setInputType(InputType.TYPE_CLASS_TEXT);
|
|
ListView existingAlbums = (ListView) dialogView.findViewById(R.id.album_list);
|
|
final ArrayList<String> directoryList = getFoldersOnDirectory(getAppDirectoryPath());
|
|
ArrayAdapter<String> adapter = new ArrayAdapter<>(context, android.R.layout.simple_list_item_1, directoryList);
|
|
existingAlbums.setAdapter(adapter);
|
|
existingAlbums.setOnItemClickListener(new AdapterView.OnItemClickListener()
|
|
{
|
|
@Override
|
|
public void onItemClick(AdapterView<?> parent, View view, int position, long id)
|
|
{
|
|
input.setText(directoryList.get(position));
|
|
}
|
|
});
|
|
builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener()
|
|
{
|
|
@Override
|
|
public void onClick(DialogInterface dialog, int which)
|
|
{
|
|
String aName = input.getText().toString();
|
|
if(!aName.isEmpty())
|
|
{
|
|
moveFiles(aName);
|
|
finish();
|
|
}
|
|
else
|
|
{
|
|
Toast.makeText(context, getResources().getText(R.string.toast_invalid_album_name), Toast.LENGTH_SHORT).show();
|
|
openSaveDialog();
|
|
}
|
|
}
|
|
});
|
|
builder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener()
|
|
{
|
|
@Override
|
|
public void onClick(DialogInterface dialog, int which)
|
|
{
|
|
dialog.cancel();
|
|
finish();
|
|
}
|
|
});
|
|
builder.show();
|
|
}
|
|
|
|
/**
|
|
* Show dialog that informs the user about how to use the app.
|
|
* Close app when closed.
|
|
*/
|
|
private void handleStartFromHomeScreen()
|
|
{
|
|
final Context context = this;
|
|
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
|
builder.setTitle(getResources().getText(R.string.dialog_title_on_start_from_home_screen));
|
|
builder.setMessage(getResources().getText(R.string.dialog_content_on_start_from_home_screen));
|
|
builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener()
|
|
{
|
|
@Override
|
|
public void onClick(DialogInterface dialog, int which)
|
|
{
|
|
dialog.cancel();
|
|
finish();
|
|
}
|
|
});
|
|
builder.setNegativeButton(R.string.action_settings, new DialogInterface.OnClickListener()
|
|
{
|
|
@Override
|
|
public void onClick(DialogInterface dialog, int which)
|
|
{
|
|
Intent intent = new Intent(context, SettingsActivity.class);
|
|
startActivity(intent);
|
|
//handleUnimplementedFeature();
|
|
}
|
|
});
|
|
builder.show();
|
|
}
|
|
|
|
/**
|
|
* Move selected files to a new album. The album is located at appDirectoryPath/albumName
|
|
* @param albumName name of the Album to move the files to
|
|
*/
|
|
private void moveFiles(String albumName)
|
|
{
|
|
for(String srcDest : sharedMedia)
|
|
{
|
|
if(!moveImageFromTo(srcDest, calcTargetPath(albumName, srcDest)))
|
|
{
|
|
System.out.println("Fail: " +srcDest);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Move file src to target.
|
|
* @param src url of the file that will be moved
|
|
* @param target url of the destination
|
|
* @return success
|
|
*/
|
|
private boolean moveImageFromTo(String src, String target)
|
|
{
|
|
if(!src.equals(target))
|
|
new LoadInitialImageData().doInBackground(src,target);
|
|
return true;
|
|
}
|
|
|
|
private void deleteFileFromMediaStore(String path)
|
|
{
|
|
if(getMimeType(path).startsWith("image/"))
|
|
{
|
|
String[] retCol = {MediaStore.Images.Media._ID};
|
|
Cursor cur = this.getContentResolver().query(
|
|
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
|
|
retCol,
|
|
MediaStore.MediaColumns.DATA+"='"+path+"'", null, null);
|
|
if (cur.getCount() == 0)
|
|
{
|
|
Log.e(Tag, "Could not find image "+path+". Therefore can't delete file from MediaStore");
|
|
return;
|
|
}
|
|
cur.moveToFirst();
|
|
int id = cur.getInt(cur.getColumnIndex(MediaStore.MediaColumns._ID));
|
|
cur.close();
|
|
Uri uri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id);
|
|
this.getContentResolver().delete(uri, null, null);
|
|
Log.v(Tag, "Deleted image "+path+" from MediaStore as "+uri);
|
|
}
|
|
else if(getMimeType(path).startsWith("video/"))
|
|
{
|
|
String[] retCol = {MediaStore.Video.Media._ID};
|
|
Cursor cur = this.getContentResolver().query(
|
|
MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
|
|
retCol,
|
|
MediaStore.MediaColumns.DATA+"='"+path+"'",null,null);
|
|
if(cur.getCount() == 0)
|
|
{
|
|
Log.e(Tag, "Could not find video "+path+". Therefore can't delete file from MediaStore");
|
|
return;
|
|
}
|
|
cur.moveToFirst();
|
|
int id = cur.getInt(cur.getColumnIndex(MediaStore.MediaColumns._ID));
|
|
cur.close();
|
|
Uri uri = ContentUris.withAppendedId(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, id);
|
|
this.getContentResolver().delete(uri, null, null);
|
|
Log.v(Tag, "Deleted video "+path+" from MediaStore as "+uri);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add the file on path to the MediaStore
|
|
* @param path url of the file
|
|
*/
|
|
private void addFileToMediaStore(String path)
|
|
{
|
|
MediaScannerConnection.scanFile(
|
|
getApplicationContext(),
|
|
new String[]{path},
|
|
null,
|
|
new MediaScannerConnection.OnScanCompletedListener()
|
|
{
|
|
@Override
|
|
public void onScanCompleted(String path, Uri uri)
|
|
{
|
|
Log.v(Tag, "File "+path+" was scanned successfully: "+uri);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* calculate the new filepath for a file according to the album name
|
|
* @param albumName name of the Album
|
|
* @param srcDest url of the file
|
|
* @return full url of the destination
|
|
*/
|
|
private String calcTargetPath(String albumName, String srcDest)
|
|
{
|
|
String file = getAppDirectoryPath()+albumName+srcDest.substring(srcDest.lastIndexOf("/"));
|
|
Log.v(Tag, "Path for "+srcDest+" in "+albumName+" will be "+file);
|
|
return file;
|
|
}
|
|
|
|
/**
|
|
* Set the apps directory path to <externalMemory>/Pictures/Albums
|
|
* @return new path
|
|
*/
|
|
public String resetAppDirectoryPath()
|
|
{
|
|
String path = Environment.getExternalStorageDirectory().getAbsolutePath()+"/Pictures/Albums/";
|
|
Log.d(Tag, "AppDirectoryPath = "+path);
|
|
SharedPreferences.Editor prefs = PreferenceManager.getDefaultSharedPreferences(this).edit();
|
|
prefs.putString(SettingsActivity.KEY_PREF_APP_PATH, path);
|
|
prefs.apply();
|
|
return path;
|
|
}
|
|
|
|
/**
|
|
* return the current app directory path
|
|
* @return path where to store new albums
|
|
*/
|
|
public String getAppDirectoryPath()
|
|
{
|
|
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
|
|
String appDirectoryPath = sharedPref.getString(SettingsActivity.KEY_PREF_APP_PATH, "");
|
|
if(!appDirectoryPath.isEmpty()) return appDirectoryPath;
|
|
else
|
|
{
|
|
return resetAppDirectoryPath();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return a list of Strings that represent the names of the folders on dirPath
|
|
* @param dirPath path of the directory
|
|
* @return list of subfolders of the directory
|
|
*/
|
|
public ArrayList<String> getFoldersOnDirectory(String dirPath)
|
|
{
|
|
File parent = new File(dirPath);
|
|
File[] cont = parent.listFiles();
|
|
ArrayList<String> subDirs = new ArrayList<>();
|
|
if(cont != null)
|
|
{
|
|
for (File f : cont)
|
|
{
|
|
if (f.isDirectory()) subDirs.add(f.getName());
|
|
}
|
|
}
|
|
return subDirs;
|
|
}
|
|
|
|
/**
|
|
* Get a real path from a uri.
|
|
* @param context context
|
|
* @param contentUri uri
|
|
* @return real path
|
|
*/
|
|
public String getRealPathFromURI(Context context, Uri contentUri)
|
|
{
|
|
String[] projection = {MediaStore.Images.Media.DATA};
|
|
Cursor cursor = context.getContentResolver().query(contentUri, projection, null, null, null);
|
|
if(cursor == null) return null;
|
|
int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
|
|
cursor.moveToFirst();
|
|
String realPath = cursor.getString(column_index);
|
|
cursor.close();
|
|
Log.v(Tag, "Real path for file "+contentUri+" is "+realPath);
|
|
return realPath;
|
|
}
|
|
|
|
/**
|
|
* Return the mimeType of the file on url
|
|
* @param url url
|
|
* @return mimeType (eg. "image/jpg")
|
|
*/
|
|
public static String getMimeType(String url)
|
|
{
|
|
String type = null;
|
|
String extension = MimeTypeMap.getFileExtensionFromUrl(url);
|
|
if (extension != null) {
|
|
MimeTypeMap mime = MimeTypeMap.getSingleton();
|
|
type = mime.getMimeTypeFromExtension(extension);
|
|
}
|
|
return type;
|
|
}
|
|
|
|
/**
|
|
* String[] = {src, target}
|
|
*/
|
|
private class LoadInitialImageData extends AsyncTask<String, Void, Void>
|
|
{
|
|
@Override
|
|
protected Void doInBackground(String... params)
|
|
{
|
|
InputStream in;
|
|
OutputStream out;
|
|
String src = params[0];
|
|
String target = params[1];
|
|
try
|
|
{
|
|
File dir = new File(src.substring(0, src.lastIndexOf("/")));
|
|
if (!dir.exists())
|
|
{
|
|
handleUnsupportedMedia();
|
|
return null;
|
|
}
|
|
in = new FileInputStream(src);
|
|
File outFile = new File(target.substring(0, target.lastIndexOf("/")));
|
|
if (!outFile.exists())
|
|
if(!outFile.mkdirs())
|
|
{
|
|
handleCouldNotWrite(outFile.getAbsolutePath());
|
|
return null;
|
|
}
|
|
out = new FileOutputStream(target);
|
|
|
|
byte[] buffer = new byte[1024];
|
|
int read;
|
|
while ((read = in.read(buffer)) != -1)
|
|
{
|
|
out.write(buffer, 0, read);
|
|
}
|
|
in.close();
|
|
out.flush();
|
|
out.close();
|
|
|
|
addFileToMediaStore(target);
|
|
deleteFileFromMediaStore(src);
|
|
|
|
} catch (FileNotFoundException e)
|
|
{
|
|
e.printStackTrace();
|
|
return null;
|
|
} catch (IOException e)
|
|
{
|
|
e.printStackTrace();
|
|
return null;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
protected void onPostExecute(Void v)
|
|
{
|
|
}
|
|
}
|
|
}
|