Java tutorial
/* * Copyright (C) 2014 Arpit Khurana <arpitkh96@gmail.com>, Vishal Nehra <vishalmeham2@gmail.com>, * Emmanuel Messulam<emmanuelbendavid@gmail.com> * * This file is part of Amaze File Manager. * * Amaze File Manager is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.amaze.filemanager.asynchronous.services; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.os.AsyncTask; import android.os.Bundle; import android.os.IBinder; import android.preference.PreferenceManager; import android.support.v4.app.NotificationCompat; import android.util.Log; import android.widget.RemoteViews; import android.widget.Toast; import com.amaze.filemanager.R; import com.amaze.filemanager.activities.MainActivity; import com.amaze.filemanager.asynchronous.asynctasks.DeleteTask; import com.amaze.filemanager.database.CryptHandler; import com.amaze.filemanager.database.models.EncryptedEntry; import com.amaze.filemanager.exceptions.ShellNotRunningException; import com.amaze.filemanager.filesystem.FileUtil; import com.amaze.filemanager.filesystem.HybridFile; import com.amaze.filemanager.filesystem.HybridFileParcelable; import com.amaze.filemanager.filesystem.Operations; import com.amaze.filemanager.filesystem.RootHelper; import com.amaze.filemanager.ui.notifications.NotificationConstants; import com.amaze.filemanager.utils.DatapointParcelable; import com.amaze.filemanager.utils.ObtainableServiceBinder; import com.amaze.filemanager.utils.OpenMode; import com.amaze.filemanager.utils.ProgressHandler; import com.amaze.filemanager.utils.RootUtils; import com.amaze.filemanager.utils.ServiceWatcherUtil; import com.amaze.filemanager.utils.application.AppConfig; import com.amaze.filemanager.utils.files.CryptUtil; import com.amaze.filemanager.utils.files.FileUtils; import com.amaze.filemanager.utils.files.GenericCopyUtil; import java.io.IOException; import java.util.ArrayList; public class CopyService extends AbstractProgressiveService { public static final String TAG_IS_ROOT_EXPLORER = "is root"; public static final String TAG_COPY_TARGET = "COPY_DIRECTORY"; public static final String TAG_COPY_SOURCES = "FILE_PATHS"; public static final String TAG_COPY_OPEN_MODE = "MODE"; // target open mode public static final String TAG_COPY_MOVE = "move"; private static final String TAG_COPY_START_ID = "id"; public static final String TAG_BROADCAST_COPY_CANCEL = "copycancel"; private NotificationManager mNotifyManager; private NotificationCompat.Builder mBuilder; private Context c; private final IBinder mBinder = new ObtainableServiceBinder<>(this); private ServiceWatcherUtil watcherUtil; private ProgressHandler progressHandler = new ProgressHandler(); private volatile float progressPercent = 0f; private ProgressListener progressListener; // list of data packages, to initiate chart in process viewer fragment private ArrayList<DatapointParcelable> dataPackages = new ArrayList<>(); private int accentColor; private SharedPreferences sharedPreferences; private RemoteViews customSmallContentViews, customBigContentViews; private boolean isRootExplorer; private long totalSize = 0L; private int totalSourceFiles = 0; private int sourceProgress = 0; @Override public void onCreate() { super.onCreate(); c = getApplicationContext(); registerReceiver(receiver3, new IntentFilter(TAG_BROADCAST_COPY_CANCEL)); } @Override public int onStartCommand(Intent intent, int flags, final int startId) { Bundle b = new Bundle(); isRootExplorer = intent.getBooleanExtra(TAG_IS_ROOT_EXPLORER, false); ArrayList<HybridFileParcelable> files = intent.getParcelableArrayListExtra(TAG_COPY_SOURCES); String targetPath = intent.getStringExtra(TAG_COPY_TARGET); int mode = intent.getIntExtra(TAG_COPY_OPEN_MODE, OpenMode.UNKNOWN.ordinal()); final boolean move = intent.getBooleanExtra(TAG_COPY_MOVE, false); sharedPreferences = PreferenceManager.getDefaultSharedPreferences(c); accentColor = ((AppConfig) getApplication()).getUtilsProvider().getColorPreference() .getCurrentUserColorPreferences(this, sharedPreferences).accent; mNotifyManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); b.putInt(TAG_COPY_START_ID, startId); Intent notificationIntent = new Intent(this, MainActivity.class); notificationIntent.setAction(Intent.ACTION_MAIN); notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); notificationIntent.putExtra(MainActivity.KEY_INTENT_PROCESS_VIEWER, true); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0); customSmallContentViews = new RemoteViews(getPackageName(), R.layout.notification_service_small); customBigContentViews = new RemoteViews(getPackageName(), R.layout.notification_service_big); Intent stopIntent = new Intent(TAG_BROADCAST_COPY_CANCEL); PendingIntent stopPendingIntent = PendingIntent.getBroadcast(c, 1234, stopIntent, PendingIntent.FLAG_UPDATE_CURRENT); NotificationCompat.Action action = new NotificationCompat.Action(R.drawable.ic_content_copy_white_36dp, getString(R.string.stop_ftp), stopPendingIntent); mBuilder = new NotificationCompat.Builder(c, NotificationConstants.CHANNEL_NORMAL_ID) .setContentIntent(pendingIntent).setSmallIcon(R.drawable.ic_content_copy_white_36dp) .setCustomContentView(customSmallContentViews).setCustomBigContentView(customBigContentViews) .setCustomHeadsUpContentView(customSmallContentViews) .setStyle(new NotificationCompat.DecoratedCustomViewStyle()).addAction(action).setOngoing(true) .setColor(accentColor); // set default notification views text NotificationConstants.setMetadata(c, mBuilder, NotificationConstants.TYPE_NORMAL); startForeground(NotificationConstants.COPY_ID, mBuilder.build()); initNotificationViews(); b.putBoolean(TAG_COPY_MOVE, move); b.putString(TAG_COPY_TARGET, targetPath); b.putInt(TAG_COPY_OPEN_MODE, mode); b.putParcelableArrayList(TAG_COPY_SOURCES, files); super.onStartCommand(intent, flags, startId); super.progressHalted(); //going async new DoInBackground(isRootExplorer).execute(b); // If we get killed, after returning from here, restart return START_STICKY; } @Override protected NotificationManager getNotificationManager() { return mNotifyManager; } @Override protected NotificationCompat.Builder getNotificationBuilder() { return mBuilder; } @Override protected int getNotificationId() { return NotificationConstants.COPY_ID; } @Override protected float getPercentProgress() { return progressPercent; } @Override protected RemoteViews getNotificationCustomViewSmall() { return customSmallContentViews; } @Override protected RemoteViews getNotificationCustomViewBig() { return customBigContentViews; } @Override protected void setPercentProgress(float progress) { progressPercent = progress; } public ProgressListener getProgressListener() { return progressListener; } @Override public void setProgressListener(ProgressListener progressListener) { this.progressListener = progressListener; } @Override protected ArrayList<DatapointParcelable> getDataPackages() { return dataPackages; } @Override protected ProgressHandler getProgressHandler() { return progressHandler; } public void onDestroy() { this.unregisterReceiver(receiver3); } private class DoInBackground extends AsyncTask<Bundle, Void, Void> { ArrayList<HybridFileParcelable> sourceFiles; boolean move; Copy copy; private String targetPath; private OpenMode openMode; private boolean isRootExplorer; private DoInBackground(boolean isRootExplorer) { this.isRootExplorer = isRootExplorer; } protected Void doInBackground(Bundle... p1) { sourceFiles = p1[0].getParcelableArrayList(TAG_COPY_SOURCES); // setting up service watchers and initial data packages // finding total size on background thread (this is necessary condition for SMB!) totalSize = FileUtils.getTotalBytes(sourceFiles, c); totalSourceFiles = sourceFiles.size(); progressHandler.setSourceSize(totalSourceFiles); progressHandler.setTotalSize(totalSize); progressHandler.setProgressListener((speed) -> publishResults(speed, false, move)); watcherUtil = new ServiceWatcherUtil(progressHandler); addFirstDatapoint(sourceFiles.get(0).getName(), sourceFiles.size(), totalSize, move); targetPath = p1[0].getString(TAG_COPY_TARGET); move = p1[0].getBoolean(TAG_COPY_MOVE); openMode = OpenMode.getOpenMode(p1[0].getInt(TAG_COPY_OPEN_MODE)); copy = new Copy(); copy.execute(sourceFiles, targetPath, move, openMode); if (copy.failedFOps.size() == 0) { // adding/updating new encrypted db entry if any encrypted file was copied/moved for (HybridFileParcelable sourceFile : sourceFiles) { try { findAndReplaceEncryptedEntry(sourceFile); } catch (Exception e) { // unable to modify encrypted entry in database Toast.makeText(c, getString(R.string.encryption_fail_copy), Toast.LENGTH_SHORT).show(); } } } return null; } @Override public void onPostExecute(Void b) { super.onPostExecute(b); // publishResults(b, "", totalSourceFiles, totalSourceFiles, totalSize, totalSize, 0, true, move); // stopping watcher if not yet finished watcherUtil.stopWatch(); finalizeNotification(copy.failedFOps, move); Intent intent = new Intent(MainActivity.KEY_INTENT_LOAD_LIST); intent.putExtra(MainActivity.KEY_INTENT_LOAD_LIST_FILE, targetPath); sendBroadcast(intent); stopSelf(); } /** * Iterates through every file to find an encrypted file and update/add a new entry about it's * metadata in the database * @param sourceFile the file which is to be iterated */ private void findAndReplaceEncryptedEntry(HybridFileParcelable sourceFile) { // even directories can end with CRYPT_EXTENSION if (sourceFile.isDirectory() && !sourceFile.getName().endsWith(CryptUtil.CRYPT_EXTENSION)) { sourceFile.forEachChildrenFile(getApplicationContext(), isRootExplorer, file -> { // iterating each file inside source files which were copied to find instance of // any copied / moved encrypted file findAndReplaceEncryptedEntry(file); }); } else { if (sourceFile.getName().endsWith(CryptUtil.CRYPT_EXTENSION)) { try { CryptHandler cryptHandler = new CryptHandler(getApplicationContext()); EncryptedEntry oldEntry = cryptHandler.findEntry(sourceFile.getPath()); EncryptedEntry newEntry = new EncryptedEntry(); newEntry.setPassword(oldEntry.getPassword()); newEntry.setPath(targetPath + "/" + sourceFile.getName()); if (move) { // file was been moved, update the existing entry newEntry.setId(oldEntry.getId()); cryptHandler.updateEntry(oldEntry, newEntry); } else { // file was copied, create a new entry with same data cryptHandler.addEntry(newEntry); } } catch (Exception e) { e.printStackTrace(); // couldn't change the entry, leave it alone } } } } class Copy { ArrayList<HybridFile> failedFOps; ArrayList<HybridFileParcelable> toDelete; Copy() { failedFOps = new ArrayList<>(); toDelete = new ArrayList<>(); } /** * Method iterate through files to be copied * * @param mode target file open mode (current path's open mode) */ public void execute(final ArrayList<HybridFileParcelable> sourceFiles, final String targetPath, final boolean move, OpenMode mode) { // initial start of copy, initiate the watcher watcherUtil.watch(CopyService.this); if (FileUtil.checkFolder((targetPath), c) == 1) { for (int i = 0; i < sourceFiles.size(); i++) { sourceProgress = i; HybridFileParcelable f1 = (sourceFiles.get(i)); try { HybridFile hFile; if (targetPath.contains(getExternalCacheDir().getPath())) { // the target open mode is not the one we're currently in! // we're processing the file for cache hFile = new HybridFile(OpenMode.FILE, targetPath, sourceFiles.get(i).getName(), f1.isDirectory()); } else { // the target open mode is where we're currently at hFile = new HybridFile(mode, targetPath, sourceFiles.get(i).getName(), f1.isDirectory()); } if (!progressHandler.getCancelled()) { if (!f1.isSmb() && (f1.getMode() == OpenMode.ROOT || mode == OpenMode.ROOT) && isRootExplorer) { // either source or target are in root progressHandler.setSourceFilesProcessed(++sourceProgress); copyRoot(f1, hFile, move); continue; } progressHandler.setSourceFilesProcessed(++sourceProgress); copyFiles((f1), hFile, progressHandler); } else { break; } } catch (Exception e) { e.printStackTrace(); Log.e("CopyService", "Got exception checkout: " + f1.getPath()); failedFOps.add(sourceFiles.get(i)); for (int j = i + 1; j < sourceFiles.size(); j++) failedFOps.add(sourceFiles.get(j)); break; } } } else if (isRootExplorer) { for (int i = 0; i < sourceFiles.size(); i++) { if (!progressHandler.getCancelled()) { HybridFile hFile = new HybridFile(mode, targetPath, sourceFiles.get(i).getName(), sourceFiles.get(i).isDirectory()); progressHandler.setSourceFilesProcessed(++sourceProgress); progressHandler.setFileName(sourceFiles.get(i).getName()); copyRoot(sourceFiles.get(i), hFile, move); /*if(checkFiles(new HybridFile(sourceFiles.get(i).getMode(),path), new HybridFile(OpenMode.ROOT,targetPath+"/"+name))){ failedFOps.add(sourceFiles.get(i)); }*/ } } } else { for (HybridFileParcelable f : sourceFiles) failedFOps.add(f); return; } // making sure to delete files after copy operation is done // and not if the copy was cancelled if (move && !progressHandler.getCancelled()) { ArrayList<HybridFileParcelable> toDelete = new ArrayList<>(); for (HybridFileParcelable a : sourceFiles) { if (!failedFOps.contains(a)) toDelete.add(a); } new DeleteTask(getContentResolver(), c).execute((toDelete)); } } void copyRoot(HybridFileParcelable sourceFile, HybridFile targetFile, boolean move) { try { if (!move) RootUtils.copy(sourceFile.getPath(), targetFile.getPath()); else if (move) RootUtils.move(sourceFile.getPath(), targetFile.getPath()); ServiceWatcherUtil.position += sourceFile.getSize(); } catch (ShellNotRunningException e) { failedFOps.add(sourceFile); e.printStackTrace(); } FileUtils.scanFile(targetFile.getFile(), c); } private void copyFiles(final HybridFileParcelable sourceFile, final HybridFile targetFile, final ProgressHandler progressHandler) throws IOException { if (progressHandler.getCancelled()) return; if (sourceFile.isDirectory()) { if (!targetFile.exists()) targetFile.mkdir(c); // various checks // 1. source file and target file doesn't end up in loop // 2. source file has a valid name or not if (!Operations.isFileNameValid(sourceFile.getName()) || Operations.isCopyLoopPossible(sourceFile, targetFile)) { failedFOps.add(sourceFile); return; } targetFile.setLastModified(sourceFile.lastModified()); if (progressHandler.getCancelled()) return; sourceFile.forEachChildrenFile(c, false, file -> { HybridFile destFile = new HybridFile(targetFile.getMode(), targetFile.getPath(), file.getName(), file.isDirectory()); try { copyFiles(file, destFile, progressHandler); } catch (IOException e) { throw new IllegalStateException(e);//throw unchecked exception, no throws needed } }); } else { if (!Operations.isFileNameValid(sourceFile.getName())) { failedFOps.add(sourceFile); return; } GenericCopyUtil copyUtil = new GenericCopyUtil(c, progressHandler); progressHandler.setFileName(sourceFile.getName()); copyUtil.copy(sourceFile, targetFile); } } } } //check if copy is successful // avoid using the method as there is no way to know when we would be returning from command callbacks // rather confirm from the command result itself, inside it's callback boolean checkFiles(HybridFile hFile1, HybridFile hFile2) throws ShellNotRunningException { if (RootHelper.isDirectory(hFile1.getPath(), isRootExplorer, 5)) { if (RootHelper.fileExists(hFile2.getPath())) return false; ArrayList<HybridFileParcelable> baseFiles = RootHelper.getFilesList(hFile1.getPath(), true, true, null); if (baseFiles.size() > 0) { boolean b = true; for (HybridFileParcelable baseFile : baseFiles) { if (!checkFiles(new HybridFile(baseFile.getMode(), baseFile.getPath()), new HybridFile(hFile2.getMode(), hFile2.getPath() + "/" + (baseFile.getName())))) b = false; } return b; } return RootHelper.fileExists(hFile2.getPath()); } else { ArrayList<HybridFileParcelable> baseFiles = RootHelper.getFilesList(hFile1.getParent(), true, true, null); int i = -1; int index = -1; for (HybridFileParcelable b : baseFiles) { i++; if (b.getPath().equals(hFile1.getPath())) { index = i; break; } } ArrayList<HybridFileParcelable> baseFiles1 = RootHelper.getFilesList(hFile1.getParent(), true, true, null); int i1 = -1; int index1 = -1; for (HybridFileParcelable b : baseFiles1) { i1++; if (b.getPath().equals(hFile1.getPath())) { index1 = i1; break; } } return baseFiles.get(index).getSize() == baseFiles1.get(index1).getSize(); } } private BroadcastReceiver receiver3 = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { //cancel operation progressHandler.setCancelled(true); } }; @Override public IBinder onBind(Intent arg0) { // TODO Auto-generated method stub return mBinder; } }