/*
* Copyright 2014 Yaroslav Mytkalyk
* 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.docd.purefm.ui.dialogs;
import com.cyanogenmod.filemanager.util.AIDHelper;
import com.docd.purefm.Environment;
import com.docd.purefm.Extras;
import com.docd.purefm.R;
import com.docd.purefm.file.CommandLineFile;
import com.docd.purefm.file.FileFactory;
import com.docd.purefm.file.GenericFile;
import com.docd.purefm.file.Permissions;
import com.docd.purefm.settings.Settings;
import com.docd.purefm.utils.PFMFileUtils;
import com.docd.purefm.utils.PFMTextUtils;
import com.docd.purefm.utils.ThemeUtils;
import com.stericson.RootTools.RootTools;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.Resources;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.CompoundButton;
import android.widget.TextView;
import android.widget.Toast;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import java.io.IOException;
import java.lang.ref.WeakReference;
public final class FilePropertiesDialog extends DialogFragment {
public static FilePropertiesDialog newInstance(final @NonNull GenericFile f) {
final Bundle extras = new Bundle();
extras.putSerializable(Extras.EXTRA_FILE, f);
final FilePropertiesDialog fpd = new FilePropertiesDialog();
fpd.setArguments(extras);
return fpd;
}
private GenericFile file;
private PropertiesAdapter mAdapter;
@Override
public void onCreate(Bundle state) {
super.onCreate(state);
final Bundle args = this.getArguments();
if (args == null) {
throw new RuntimeException(
"Arguments were not supplied. Make sure you created this DialogFragment using newInstance method");
}
this.file = (GenericFile) args.getSerializable(Extras.EXTRA_FILE);
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Activity activity = getActivity();
if (activity == null || activity.isFinishing()) {
return null;
}
final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
mAdapter = new PropertiesAdapter(activity, file);
builder.setIcon(ThemeUtils.getDrawableNonNull(activity, R.attr.ic_menu_info));
builder.setTitle(file.getName());
builder.setNeutralButton(R.string.close, null);
builder.setPositiveButton(R.string.apply, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
final FilePermissionsPagerItem fragment = (FilePermissionsPagerItem)
mAdapter.getItem(1);
fragment.applyPermissions(getActivity());
}
});
//noinspection InflateParams
final View content = activity.getLayoutInflater()
.inflate(R.layout.dialog_properties_container, null);
if (content == null) {
throw new RuntimeException("Inflated view is null");
}
this.initView(content);
builder.setView(content);
final AlertDialog dialog = builder.create();
dialog.setOnShowListener(new DialogInterface.OnShowListener() {
@Override
public void onShow(final DialogInterface dialog) {
final Button button = ((AlertDialog) dialog).getButton(
DialogInterface.BUTTON_POSITIVE);
if (button == null) {
throw new RuntimeException("Can't get positive button");
}
button.setVisibility(View.GONE);
}
});
return dialog;
}
private void initView(@NonNull final View view) {
final ViewPager pager = (ViewPager) view.findViewById(R.id.tabsContainer);
pager.setAdapter(mAdapter);
final CompoundButton tab1 = (CompoundButton) view.findViewById(R.id.tab1);
final CompoundButton tab2 = (CompoundButton) view.findViewById(R.id.tab2);
pager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
@Override
public void onPageSelected(int position) {
tab1.setChecked(position == 0);
tab2.setChecked(position == 1);
final AlertDialog dialog = (AlertDialog) getDialog();
if (dialog == null) {
throw new RuntimeException("The dialog is null");
}
final Button positive = dialog.getButton(DialogInterface.BUTTON_POSITIVE);
if (positive == null) {
throw new RuntimeException("Can't get positive button");
}
positive.setVisibility(position == 0 ||
!((FilePermissionsPagerItem) mAdapter.getItem(1)).areBoxesEnabled()
? View.GONE : View.VISIBLE);
}
});
tab1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
tab1.setChecked(true);
tab2.setChecked(false);
pager.setCurrentItem(0);
}
});
tab2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
tab2.setChecked(true);
tab1.setChecked(false);
pager.setCurrentItem(1);
}
});
}
@Override
public void onStart() {
super.onStart();
mAdapter.onStart();
}
@Override
public void onStop() {
super.onStop();
mAdapter.onStop();
}
private interface PagerItem {
void onStart();
void onStop();
@NonNull
View onCreateView(@NonNull LayoutInflater inflater, @NonNull final ViewGroup container);
}
static final class FilePermissionsPagerItem implements PagerItem, CompoundButton.OnCheckedChangeListener {
private LoadFsTask mTask;
View mView;
FilePermissionsPagerItem(final GenericFile file) {
mFile = file;
}
@NonNull
@Override
public View onCreateView(@NonNull final LayoutInflater inflater,
@NonNull final ViewGroup container) {
//noinspection InflateParams
mView = inflater.inflate(R.layout.dialog_permissions, null);
initView(mView);
return mView;
}
@Override
public void onStart() {
if (mView != null) {
if (mTask == null) {
mTask = new LoadFsTask(this);
}
if (mTask.getStatus() != AsyncTask.Status.RUNNING) {
mTask.execute(mFile);
}
}
}
@Override
public void onStop() {
if (mTask != null && mTask.getStatus() == AsyncTask.Status.RUNNING) {
mTask.cancel(false);
}
}
/**
* Permissions that file had when FilePermissionsController was created
*/
private Permissions mInputPermissions;
/**
* Currently modified permissions
*/
private Permissions mModifiedPermissions;
/**
* Target file
*/
private final GenericFile mFile;
/**
* User: read
*/
private CompoundButton ur;
/**
* User: write
*/
private CompoundButton uw;
/**
* User: execute
*/
private CompoundButton ux;
/**
* Group: read
*/
private CompoundButton gr;
/**
* Group: write
*/
private CompoundButton gw;
/**
* Group: execute
*/
private CompoundButton gx;
/**
* Others: read
*/
private CompoundButton or;
/**
* Others: write
*/
private CompoundButton ow;
/**
* Others: execute
*/
private CompoundButton ox;
private void initView(View table) {
final TextView owner = (TextView) table.findViewById(R.id.owner);
final TextView group = (TextView) table.findViewById(R.id.group);
final Permissions p = mFile.getPermissions();
this.ur = (CompoundButton) table.findViewById(R.id.uread);
this.uw = (CompoundButton) table.findViewById(R.id.uwrite);
this.ux = (CompoundButton) table.findViewById(R.id.uexecute);
this.gr = (CompoundButton) table.findViewById(R.id.gread);
this.gw = (CompoundButton) table.findViewById(R.id.gwrite);
this.gx = (CompoundButton) table.findViewById(R.id.gexecute);
this.or = (CompoundButton) table.findViewById(R.id.oread);
this.ow = (CompoundButton) table.findViewById(R.id.owrite);
this.ox = (CompoundButton) table.findViewById(R.id.oexecute);
this.ur.setChecked(p.ur);
this.uw.setChecked(p.uw);
this.ux.setChecked(p.ux);
this.gr.setChecked(p.gr);
this.gw.setChecked(p.gw);
this.gx.setChecked(p.gx);
this.or.setChecked(p.or);
this.ow.setChecked(p.ow);
this.ox.setChecked(p.ox);
this.ur.setOnCheckedChangeListener(this);
this.uw.setOnCheckedChangeListener(this);
this.ux.setOnCheckedChangeListener(this);
this.gr.setOnCheckedChangeListener(this);
this.gw.setOnCheckedChangeListener(this);
this.gx.setOnCheckedChangeListener(this);
this.or.setOnCheckedChangeListener(this);
this.ow.setOnCheckedChangeListener(this);
this.ox.setOnCheckedChangeListener(this);
if (mFile instanceof CommandLineFile) {
final CommandLineFile f = (CommandLineFile) mFile;
final SparseArray<String> aids = AIDHelper.getAIDs(table.getContext(), false);
if (aids != null) {
owner.setText(aids.get(f.getOwner()));
group.setText(aids.get(f.getGroup()));
}
} else {
this.disableBoxes();
}
this.mInputPermissions = p;
this.mModifiedPermissions = p;
}
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
this.mModifiedPermissions = new Permissions(
this.ur.isChecked(), this.uw.isChecked(), this.ux.isChecked(),
this.gr.isChecked(), this.gw.isChecked(), this.gx.isChecked(),
this.or.isChecked(), this.ow.isChecked(), this.ox.isChecked());
}
private void disableBoxes() {
this.ur.setEnabled(false);
this.uw.setEnabled(false);
this.ux.setEnabled(false);
this.gr.setEnabled(false);
this.gw.setEnabled(false);
this.gx.setEnabled(false);
this.or.setEnabled(false);
this.ow.setEnabled(false);
this.ox.setEnabled(false);
}
boolean areBoxesEnabled() {
return this.ur.isEnabled();
}
public void applyPermissions(final Context context) {
if (!mInputPermissions.equals(mModifiedPermissions)) {
final ApplyTask task = new ApplyTask(context, mModifiedPermissions);
task.execute(this.mFile);
}
}
private static final class LoadFsTask extends AsyncTask<GenericFile, Void, String> {
private final WeakReference<FilePermissionsPagerItem> mItemRef;
LoadFsTask(@NonNull final FilePermissionsPagerItem item) {
this.mItemRef = new WeakReference<>(item);
}
@Override
protected String doInBackground(final GenericFile... params) {
return PFMFileUtils.resolveFileSystem(params[0]);
}
@Override
protected void onPostExecute(final String fsType) {
final FilePermissionsPagerItem item = mItemRef.get();
if (item != null) {
if (fsType == null || fsType.equals("vfat") || fsType.equals("fuse")
|| fsType.equals("ntfs") || fsType.equals("msdos") ||
fsType.equals("sdcardfs")) {
item.disableBoxes();
}
item.mView.findViewById(android.R.id.progress).setVisibility(View.GONE);
item.mView.findViewById(R.id.content).setVisibility(View.VISIBLE);
}
}
}
private static final class ApplyTask extends AsyncTask<GenericFile, Void, Boolean> {
private final Context mContext;
private final Permissions mTarget;
private ApplyTask(Context context, Permissions target) {
this.mContext = context;
this.mTarget = target;
}
@Override
protected Boolean doInBackground(final GenericFile... params) {
final String path = params[0].getAbsolutePath();
final Settings settings = Settings.getInstance(mContext);
final boolean remount = settings.useCommandLine() && settings.isSuEnabled() &&
Environment.needsRemount(path);
if (remount) {
RootTools.remount(path, "RW");
}
try {
return params[0].applyPermissions(this.mTarget);
} finally {
if (remount) {
RootTools.remount(path, "RO");
}
}
}
@Override
protected void onPostExecute(Boolean result) {
Toast.makeText(this.mContext, this.mContext.getText(result ?
R.string.permissions_changed : R.string.applying_failed), Toast.LENGTH_SHORT).show();
}
}
}
static final class FilePropertiesPagerItem implements PagerItem {
private final GenericFile mFile;
private final Settings mSettings;
private final Resources mResources;
private PropertiesTask mTask;
private View mView;
FilePropertiesPagerItem(@NonNull final GenericFile file,
@NonNull final Settings settings,
@NonNull final Resources resources) {
mFile = file;
mSettings = settings;
mResources = resources;
}
@NonNull
@Override
public View onCreateView(@NonNull final LayoutInflater inflater,
@NonNull final ViewGroup container) {
//noinspection InflateParams
mView = inflater.inflate(R.layout.dialog_properties, null);
return mView;
}
@Override
public void onStart() {
if (mView != null) {
if (mTask == null) {
mTask = new PropertiesTask(mView, mSettings, mResources);
}
if (mTask.getStatus() != AsyncTask.Status.RUNNING) {
mTask.execute(mFile);
}
}
}
@Override
public void onStop() {
if (mTask != null && mTask.getStatus() == AsyncTask.Status.RUNNING) {
mTask.cancel(false);
}
}
private static final class FileProperties {
final CharSequence mFileName;
final CharSequence mParentPath;
final CharSequence mTypeText;
final CharSequence mMimeTypeText;
final CharSequence mFileSizeText;
final CharSequence mLastModifiedText;
private FileProperties(@NonNull final CharSequence fileName,
@NonNull CharSequence parentPath,
@NonNull CharSequence typeText,
@Nullable CharSequence mimeTypeText,
@NonNull CharSequence fileSizeText,
@NonNull CharSequence lastModifiedText) {
this.mFileName = fileName;
this.mParentPath = parentPath;
this.mTypeText = typeText;
this.mMimeTypeText = mimeTypeText;
this.mFileSizeText = fileSizeText;
this.mLastModifiedText = lastModifiedText;
}
}
private static final class PropertiesTask extends AsyncTask
<GenericFile, Void, FileProperties> {
private final WeakReference<View> mViewRef;
private final Settings mSettings;
private final Resources mResources;
PropertiesTask(@NonNull final View view,
@NonNull final Settings settings,
@NonNull final Resources res) {
this.mViewRef = new WeakReference<>(view);
this.mSettings = settings;
this.mResources = res;
}
@NonNull
@Override
protected FileProperties doInBackground(final GenericFile... params) {
final GenericFile file = params[0];
GenericFile par = file.getParentFile();
if (par == null) {
par = FileFactory.newFile(mSettings, Environment.sRootDirectory);
}
String parentPath;
try {
parentPath = par.getCanonicalPath();
} catch (IOException e) {
parentPath = par.getAbsolutePath();
}
final CharSequence typeText = file.isSymlink() ?
mResources.getText(R.string.type_symlink) :
file.isDirectory() ? mResources.getText(R.string.type_directory) :
mResources.getText(R.string.type_file);
return new FileProperties(file.getName(),
parentPath,
typeText,
file.getMimeType(),
PFMFileUtils.byteCountToDisplaySize(file.lengthTotal()),
PFMTextUtils.humanReadableDate(file.lastModified(), file instanceof CommandLineFile));
}
@Override
protected void onPostExecute(final @NonNull FileProperties fileProperties) {
final View view = mViewRef.get();
if (view == null) {
return;
}
final TextView name = (TextView) view.findViewById(R.id.name);
name.setText(fileProperties.mFileName);
final TextView parent = (TextView) view.findViewById(R.id.location);
parent.setText(fileProperties.mParentPath);
final TextView type = (TextView) view.findViewById(R.id.type);
type.setText(fileProperties.mTypeText);
if (fileProperties.mMimeTypeText != null) {
final TextView mime = (TextView) view.findViewById(R.id.mime);
mime.setText(fileProperties.mMimeTypeText);
}
final TextView size = (TextView) view.findViewById(R.id.size);
size.setText(fileProperties.mFileSizeText);
final TextView mod = (TextView) view.findViewById(R.id.modified);
mod.setText(fileProperties.mLastModifiedText);
view.findViewById(android.R.id.progress).setVisibility(View.GONE);
view.findViewById(R.id.content).setVisibility(View.VISIBLE);
}
}
}
private static final class PropertiesAdapter extends PagerAdapter {
private final LayoutInflater mLayoutInflater;
private final GenericFile mFile;
private final PagerItem[] mItems;
private PropertiesAdapter(@NonNull final Activity context,
@NonNull final GenericFile file) {
mLayoutInflater = context.getLayoutInflater();
mFile = file;
//noinspection ConstantConditions
mItems = new PagerItem[]{
new FilePropertiesPagerItem(mFile, Settings.getInstance(context), context
.getApplicationContext().getResources()),
new FilePermissionsPagerItem(mFile)
};
}
void onStart() {
for (final PagerItem item : mItems) {
item.onStart();
}
}
void onStop() {
for (final PagerItem item : mItems) {
item.onStop();
}
}
@NonNull
PagerItem getItem(final int position) {
return mItems[position];
}
@Override
public Object instantiateItem(final ViewGroup container, final int position) {
final PagerItem item = mItems[position];
final View view = item.onCreateView(mLayoutInflater, container);
container.addView(view);
item.onStart();
return view;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View) object);
}
@Override
public int getCount() {
return mItems.length;
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
}
}