/*
* Copyright (C) 2012 The CyanogenMod Project
*
* 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.cyanogenmod.filemanager.ui.dialogs;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.AsyncTask;
import android.text.Layout;
import android.text.method.ScrollingMovementMethod;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import com.cyanogenmod.filemanager.R;
import com.cyanogenmod.filemanager.commands.AsyncResultExecutable;
import com.cyanogenmod.filemanager.commands.ExecExecutable;
import com.cyanogenmod.filemanager.model.FileSystemObject;
import com.cyanogenmod.filemanager.ui.ThemeManager;
import com.cyanogenmod.filemanager.ui.ThemeManager.Theme;
import com.cyanogenmod.filemanager.util.DialogHelper;
import com.cyanogenmod.filemanager.util.FixedQueue;
import com.cyanogenmod.filemanager.util.FixedQueue.EmptyQueueException;
import java.util.List;
/**
* A class that wraps a dialog for display the output generation for an
* {@link ExecExecutable}.
*/
public class ExecutionDialog implements DialogInterface.OnClickListener {
/**
* @hide
*/
final Context mContext;
/**
* @hide
*/
final AlertDialog mDialog;
/**
* @hide
*/
final TextView mTvOutput;
/**
* @hide
*/
final TextView mTvTime;
/**
* @hide
*/
final TextView mTvExitCode;
// For cancel the operation
private AsyncResultExecutable mCmd;
/**
* @hide
*/
boolean mFinished;
private long mStartTime;
/**
* @hide
*/
final Object mSync = new Object();
/**
* @hide
*/
FixedQueue<String> mQueue;
private final int maxLines;
private final int maxChars;
// The drawing task
private final AsyncTask<Void, String, Void> mConsoleDrawTask =
new AsyncTask<Void, String, Void>() {
@Override
protected Void doInBackground(Void... params) {
while (!ExecutionDialog.this.mFinished) {
// Extract the message
try {
while (!ExecutionDialog.this.mQueue.isEmpty()) {
// Extract all items from the queue
List<String> l = ExecutionDialog.this.mQueue.peekAll();
StringBuilder sb = new StringBuilder();
for (String s : l) {
sb.append(s);
sb.append("\n"); //$NON-NLS-1$
}
// Extract the message and redraw
publishProgress(extractMsg());
// Don't kill the processor
try {
Thread.yield();
Thread.sleep(250L);
} catch (Throwable _throw) {/**NON BLOCK**/}
}
} catch (Exception e) {/**NON BLOCK**/}
}
return null;
}
@Override
protected void onProgressUpdate(String... values) {
drawMessage(values[0], false);
}
};
/**
* Constructor of <code>ExecutionDialog</code>.
*
* @param context The current context
* @param fso The file system object to execute
*/
public ExecutionDialog(final Context context, final FileSystemObject fso) {
super();
// Limits
this.maxLines = context.getResources().getInteger(R.integer.console_max_lines);
this.maxChars = context.getResources().getInteger(R.integer.console_max_chars_per_line);
//Save the context
this.mContext = context;
this.mFinished = false;
this.mQueue = new FixedQueue<String>(this.maxLines);
//Create the layout
LayoutInflater li =
(LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
ViewGroup layout = (ViewGroup)li.inflate(R.layout.execution_dialog, null);
View tvScriptNameLabel = layout.findViewById(R.id.execution_script_name_label);
TextView tvScriptName = (TextView)layout.findViewById(R.id.execution_script_name);
tvScriptName.setText(fso.getFullPath());
View tvTimeLabel = layout.findViewById(R.id.execution_time_label);
this.mTvTime = (TextView)layout.findViewById(R.id.execution_time);
this.mTvTime.setText("-"); //$NON-NLS-1$
View tvExitCodeLabel = layout.findViewById(R.id.execution_exitcode_label);
this.mTvExitCode = (TextView)layout.findViewById(R.id.execution_exitcode);
this.mTvExitCode.setText("-"); //$NON-NLS-1$
this.mTvOutput = (TextView)layout.findViewById(R.id.execution_output);
this.mTvOutput.setMovementMethod(new ScrollingMovementMethod());
// Apply the current theme
Theme theme = ThemeManager.getCurrentTheme(context);
theme.setBackgroundDrawable(context, layout, "background_drawable"); //$NON-NLS-1$
theme.setTextColor(context, (TextView)tvScriptNameLabel, "text_color"); //$NON-NLS-1$
theme.setTextColor(context, tvScriptName, "text_color"); //$NON-NLS-1$
theme.setTextColor(context, (TextView)tvTimeLabel, "text_color"); //$NON-NLS-1$
theme.setTextColor(context, this.mTvTime, "text_color"); //$NON-NLS-1$
theme.setTextColor(context, (TextView)tvExitCodeLabel, "text_color"); //$NON-NLS-1$
theme.setTextColor(context, this.mTvExitCode, "text_color"); //$NON-NLS-1$
theme.setBackgroundColor(context, this.mTvOutput, "console_bg_color"); //$NON-NLS-1$
theme.setTextColor(context, this.mTvOutput, "console_fg_color"); //$NON-NLS-1$
//Create the dialog
String title = context.getString(R.string.execution_console_title);
this.mDialog = DialogHelper.createDialog(
context,
0,
title,
layout);
this.mDialog.setButton(
DialogInterface.BUTTON_NEUTRAL, context.getString(android.R.string.cancel), this);
// Start the drawing task
this.mConsoleDrawTask.execute();
}
/**
* Method that sets the console
* @param cmd the mCmd to set
*/
public void setCmd(AsyncResultExecutable cmd) {
this.mCmd = cmd;
// Enable cancel the script after 3 seconds.
this.mTvOutput.postDelayed(new Runnable() {
@Override
public void run() {
ExecutionDialog.this.mDialog.setCancelable(true);
ExecutionDialog.this.mDialog.getButton(
DialogInterface.BUTTON_NEUTRAL).setEnabled(true);
}
}, 5000L);
}
/**
* Method that shows the dialog.
*/
public void show() {
DialogHelper.delegateDialogShow(this.mContext, this.mDialog);
this.mDialog.setCancelable(false);
this.mDialog.getButton(DialogInterface.BUTTON_NEUTRAL).setEnabled(false);
}
/**
* Method that dismiss the dialog.
*/
public void dismiss() {
this.mDialog.dismiss();
}
/**
* Method invoked when the execution starts
*/
public void onStart() {
// Initialize execution
this.mStartTime = System.currentTimeMillis();
}
/**
* Method invoked when the execution ends
*
* @param exitCode The exit code of the execution
*/
public void onEnd(final int exitCode) {
// Cancel the drawing task
try {
this.mFinished = true;
this.mConsoleDrawTask.cancel(false);
} catch (Exception e) {/**NON BLOK**/}
long endTime = System.currentTimeMillis();
final String diff = String.valueOf((endTime - this.mStartTime) / 1000);
// Enable the ok button
this.mTvOutput.post(new Runnable() {
@Override
public void run() {
try {
// Draw the data one more time, and clean the queue (no more needed)
drawMessage(extractMsg(), true);
ExecutionDialog.this.mQueue.removeAll();
} catch (EmptyQueueException eqex) {/**NON BLOCK**/}
// Set the time and exit code
ExecutionDialog.this.mTvTime.setText(
ExecutionDialog.this.mContext.getString(
R.string.execution_console_script_execution_time_text, diff));
ExecutionDialog.this.mTvExitCode.setText(String.valueOf(exitCode));
// Enable the Ok button
ExecutionDialog.this.mDialog.setCancelable(true);
Button button =
ExecutionDialog.this.mDialog.getButton(DialogInterface.BUTTON_NEUTRAL);
button.setText(R.string.ok);
button.setEnabled(true);
}
});
}
/**
* Method that append data to the console output
*
* @param msg The message to append
*/
public void onAppendData(final String msg) {
if (msg != null && msg.length() > 0) {
// Split the messages in lines
String[] lines = msg.split("\n"); //$NON-NLS-1$
for (String line : lines) {
// Don't allow lines with more that x characters
if (line.length() > this.maxChars) {
while (line.length() > this.maxChars) {
String partial = line.substring(0, Math.min(line.length(), this.maxChars));
line = line.substring(Math.min(line.length(), this.maxChars));
// The partial
this.mQueue.insert(partial);
}
if (line.length() > 0) {
// Insert the rest of the line
this.mQueue.insert(line);
}
} else {
// Insert the line
this.mQueue.insert(line);
}
}
}
}
/**
* {@inheritDoc}
*/
@Override
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case DialogInterface.BUTTON_NEUTRAL:
// Cancel the program?
try {
if (this.mCmd != null && !this.mFinished) {
if (this.mCmd.isCancellable() && !this.mCmd.isCancelled()) {
this.mCmd.cancel();
}
}
} catch (Exception e) {/**NON BLOCK**/}
this.mDialog.dismiss();
break;
default:
break;
}
}
/**
* Method that extracts the message from the queue
*
* @param String The message
* @throws EmptyQueueException If there are not message in the queue
* @hide
*/
String extractMsg() throws EmptyQueueException {
// Extract all items from the queue
List<String> l = this.mQueue.peekAll();
StringBuilder sb = new StringBuilder();
for (String s : l) {
sb.append(s);
sb.append("\n"); //$NON-NLS-1$
}
return sb.toString();
}
/**
* Method that draw the message
*
* @param msg The message
* @param scroll Scroll to bottom
* @hide
*/
void drawMessage(String msg, boolean scroll) {
// Any message?
if (msg != null && msg.length() > 0) {
final TextView tv = ExecutionDialog.this.mTvOutput;
tv.setText(msg);
// Scroll to bottom
if (scroll) {
final Layout layout = tv.getLayout();
if (layout != null) {
int scrollDelta =
layout.getLineBottom(
tv.getLineCount() - 1) - tv.getScrollY() - tv.getHeight();
if (scrollDelta > 0) {
tv.scrollBy(0, scrollDelta);
}
}
} else {
tv.scrollBy(0, 0);
}
}
}
}