/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with context work for additional information
* regarding copyright ownership. The ASF licenses context file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use context 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.doplgangr.secrecy.Views;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Parcelable;
import android.support.v4.app.Fragment;
import android.support.v7.app.ActionBarActivity;
import android.text.InputType;
import android.webkit.MimeTypeMap;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.Toast;
import com.doplgangr.secrecy.Config;
import com.doplgangr.secrecy.CustomApp;
import com.doplgangr.secrecy.FileSystem.CryptStateListener;
import com.doplgangr.secrecy.FileSystem.File;
import com.doplgangr.secrecy.FileSystem.OurFileProvider;
import com.doplgangr.secrecy.FileSystem.Vault;
import com.doplgangr.secrecy.FileSystem.storage;
import com.doplgangr.secrecy.Jobs.AddFileJob;
import com.doplgangr.secrecy.Listeners;
import com.doplgangr.secrecy.R;
import com.doplgangr.secrecy.Util;
import org.androidannotations.annotations.AfterInject;
import org.androidannotations.annotations.Background;
import org.androidannotations.annotations.EFragment;
import org.androidannotations.annotations.UiThread;
import org.androidannotations.api.BackgroundExecutor;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@EFragment(R.layout.activity_file_viewer)
public class FileViewer extends Fragment {
ActionBarActivity context;
@AfterInject
void onCreate() {
context = (ActionBarActivity) getActivity();
if (context.getSupportActionBar() != null)
context.getSupportActionBar().setSubtitle(storage.getRoot().getAbsolutePath());
final EditText input = new EditText(context);
input.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
new AlertDialog.Builder(context)
.setTitle(getString(R.string.File__open))
.setMessage(getString(R.string.File__open_message))
.setView(input)
.setPositiveButton(getString(R.string.OK), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
String password = input.getText().toString();
Uri file = context.getIntent().getData();
decrypt(new File(new java.io.File(file.getPath()), password), null, null);
}
}).setNegativeButton(getString(R.string.CANCEL), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
finish();
}
}).show();
}
@Background
void addFile(Vault secret, final Uri data) {
CustomApp.jobManager.addJobInBackground(new AddFileJob(context, secret, data));
}
@Background
void decrypt(File file, final ProgressBar pBar, final Listeners.EmptyListener onFinish) {
java.io.File tempFile = getFile(file, pBar, onFinish);
//File specified is not invalid
if (tempFile != null) {
if (tempFile.getParentFile().equals(storage.getTempFolder())) {
java.io.File newFile = new java.io.File(storage.getTempFolder(), tempFile.getName());
tempFile = newFile;
}
Uri uri = OurFileProvider.getUriForFile(context, OurFileProvider.FILE_PROVIDER_AUTHORITY, tempFile);
MimeTypeMap myMime = MimeTypeMap.getSingleton();
Intent newIntent = new Intent(android.content.Intent.ACTION_VIEW);
String mimeType = myMime.getMimeTypeFromExtension(file.getType());
newIntent.setDataAndType(uri, mimeType);
newIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
//altIntent: resort to using file provider when content provider does not work.
Intent altIntent = new Intent(android.content.Intent.ACTION_VIEW);
Uri rawuri = Uri.fromFile(tempFile);
altIntent.setDataAndType(rawuri, mimeType);
afterDecrypt(newIntent, altIntent);
}
}
@Background
void sendMultiple(ArrayList<FilesListFragment.DecryptArgHolder> args) {
ArrayList<Uri> uris = new ArrayList<Uri>();
Set<String> mimes = new HashSet<String>();
MimeTypeMap myMime = MimeTypeMap.getSingleton();
for (FilesListFragment.DecryptArgHolder arg : args) {
java.io.File tempFile = getFile(arg.file, arg.pBar, arg.onFinish);
//File specified is not invalid
if (tempFile != null) {
if (tempFile.getParentFile().equals(storage.getTempFolder()))
tempFile = new java.io.File(storage.getTempFolder(), tempFile.getName());
uris.add(OurFileProvider.getUriForFile(context, OurFileProvider.FILE_PROVIDER_AUTHORITY, tempFile));
mimes.add(myMime.getMimeTypeFromExtension(arg.file.getType()));
}
}
if (uris.size() == 0 || mimes.size() == 0)
return;
Intent newIntent;
if (uris.size() == 1) {
newIntent = new Intent(Intent.ACTION_SEND);
newIntent.putExtra(Intent.EXTRA_STREAM, uris.get(0));
} else {
newIntent = new Intent(Intent.ACTION_SEND_MULTIPLE);
newIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris);
}
if (mimes.size() > 1)
newIntent.setType("text/plain"); //Mixed filetypes
else
newIntent.setType(new ArrayList<String>(mimes).get(0));
newIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
Intent chooserIntent = generateCustomChooserIntent(newIntent, uris);
try {
startActivity(Intent.createChooser(chooserIntent, CustomApp.context.getString(R.string.Dialog__send_file)));
onPauseDecision.startActivity();
} catch (android.content.ActivityNotFoundException e) {
Util.toast(context, CustomApp.context.getString(R.string.Error__no_activity_view), Toast.LENGTH_LONG);
onPauseDecision.finishActivity();
}
}
java.io.File getFile(final File file, final ProgressBar pBar, final Listeners.EmptyListener onfinish) {
CryptStateListener listener = new CryptStateListener() {
@Override
public void updateProgress(int progress) {
updatePBar(pBar, progress);
}
@Override
public void setMax(int max) {
maxPBar(pBar, max);
}
@Override
public void onFailed(int statCode) {
String message;
switch (statCode) {
case Config.wrong_password:
message = getString(R.string.Error__wrong_password);
break;
case Config.file_not_found:
message = getString(R.string.Error__file_not_found);
break;
default:
message = getString(R.string.Error__unknown);
}
alert(message);
}
@Override
public void Finished() {
onfinish.run();
}
};
return file.readFile(listener);
}
@UiThread
void afterDecrypt(Intent newIntent, Intent altIntent) {
try {
startActivity(newIntent);
onPauseDecision.startActivity();
} catch (android.content.ActivityNotFoundException e) {
try {
startActivity(altIntent);
onPauseDecision.startActivity();
} catch (android.content.ActivityNotFoundException e2) {
Util.toast(context, getString(R.string.Error__no_activity_view), Toast.LENGTH_LONG);
onPauseDecision.finishActivity();
}
} catch (IllegalStateException e) {
//duh why you leave so early
onPauseDecision.finishActivity();
}
}
@UiThread
void alert(String message) {
DialogInterface.OnClickListener click = new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
finish();
}
};
Util.alert(context, getString(R.string.Error__decrypt_file), message, click, null);
}
@UiThread
void updatePBar(ProgressBar pBar, int progress) {
if (pBar != null)
pBar.setProgress(progress);
}
@UiThread
void maxPBar(ProgressBar pBar, int max) {
if (pBar != null)
pBar.setMax(max);
}
@Override
public void onPause() {
super.onPause();
if (onPauseDecision.shouldFinish())
finish();
}
void finish() {
BackgroundExecutor.cancelAll(Config.cancellable_task, false);
getActivity().finish();
}
@Override
public void onResume() {
super.onResume();
onPauseDecision.finishActivity();
}
@Override
public void onDestroy() {
BackgroundExecutor.cancelAll(Config.cancellable_task, true);
super.onDestroy();
}
private Intent generateCustomChooserIntent(Intent prototype, ArrayList<Uri> uris) {
List<Intent> targetedShareIntents = new ArrayList<Intent>();
List<HashMap<String, String>> intentMetaInfo = new ArrayList<HashMap<String, String>>();
Intent chooserIntent;
Intent dummy = new Intent(prototype.getAction());
dummy.setType(prototype.getType());
List<ResolveInfo> resInfo = context.getPackageManager().queryIntentActivities(dummy, 0);
if (!resInfo.isEmpty()) {
for (ResolveInfo resolveInfo : resInfo) {
if (resolveInfo.activityInfo == null || resolveInfo.activityInfo.packageName.equalsIgnoreCase("com.doplgangr.secrecy"))
continue;
HashMap<String, String> info = new HashMap<String, String>();
info.put("packageName", resolveInfo.activityInfo.packageName);
info.put("className", resolveInfo.activityInfo.name);
info.put("simpleName", String.valueOf(resolveInfo.activityInfo.loadLabel(context.getPackageManager())));
intentMetaInfo.add(info);
for (Uri uri : uris)
context.grantUriPermission(resolveInfo.activityInfo.packageName, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
if (!intentMetaInfo.isEmpty()) {
// sorting for nice readability
Collections.sort(intentMetaInfo, new Comparator<HashMap<String, String>>() {
@Override
public int compare(HashMap<String, String> map, HashMap<String, String> map2) {
return map.get("simpleName").compareTo(map2.get("simpleName"));
}
});
// create the custom intent list
for (HashMap<String, String> metaInfo : intentMetaInfo) {
Intent targetedShareIntent = (Intent) prototype.clone();
targetedShareIntent.setPackage(metaInfo.get("packageName"));
targetedShareIntent.setClassName(metaInfo.get("packageName"), metaInfo.get("className"));
targetedShareIntents.add(targetedShareIntent);
}
chooserIntent = Intent.createChooser(targetedShareIntents.remove(targetedShareIntents.size() - 1), CustomApp.context.getString(R.string.Dialog__send_file));
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, targetedShareIntents.toArray(new Parcelable[targetedShareIntents.size()]));
return chooserIntent;
}
}
return new Intent(Intent.ACTION_SEND); //Unable to do anything. Duh.
}
static class onPauseDecision {
static Boolean pause = true;
// An activity is started, should not pause and kill this fragment.
static void startActivity() {
pause = false;
}
// Fragment returns to top, allow it to be paused and killed.
static void finishActivity() {
pause = true;
}
static Boolean shouldFinish() {
return pause;
}
}
}