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 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); String appDirectoryPath = sharedPref.getString(SettingsActivity.KEY_PREF_APP_PATH, ""); if(appDirectoryPath.isEmpty()) { appDirectoryPath = 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(); } } 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).commit(); } } /** * 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 */ void handleSendSingleFile(Intent intent) { Uri imageUri = intent.getParcelableExtra(Intent.EXTRA_STREAM); if (imageUri != null) { sharedMedia.add(imageUri); } openSaveDialog(); } /** * 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 */ void handleSendMultipleFiles(Intent intent) { ArrayList imageUris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM); if (imageUris != null) { sharedMedia = imageUris; } openSaveDialog(); } /** * Show Dialog to inform the user about the fact, that the feature he/she wants to use is not * implemented */ 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(); } /** * 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 directoryList = getFoldersOnDirectory(getAppDirectoryPath()); ArrayAdapter 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 */ private void moveFiles(String albumName) { for(Uri u: sharedMedia) { String srcDest = getRealPathFromURI(this, u); if(!moveImageFromTo(srcDest, calcTargetPath(albumName, srcDest))) { System.out.println("Fail: " +srcDest); } } } /** * Move file src to target. * @param src * @param target * @return */ 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); } } 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); } }); } 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; } 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; } 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(); } } public ArrayList getFoldersOnDirectory(String dirPath) { File parent = new File(dirPath); File[] cont = parent.listFiles(); ArrayList subDirs = new ArrayList<>(); if(cont != null) { for (File f : cont) { if (f.isDirectory()) subDirs.add(f.getName()); } } return subDirs; } public String getRealPathFromURI(Context context, Uri contentUri) { String[] projection = {MediaStore.Images.Media.DATA}; Cursor cursor = context.getContentResolver().query(contentUri, projection, null, null, 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; } 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 { @Override protected Void doInBackground(String... params) { InputStream in = null; OutputStream out = null; String src = params[0]; String target = params[1]; try { File dir = new File(src.substring(0, src.lastIndexOf("/"))); if (!dir.exists()) dir.mkdirs(); in = new FileInputStream(src); File outFile = new File(target.substring(0, target.lastIndexOf("/"))); if (!outFile.exists()) outFile.mkdirs(); 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) { } } }