| /* |
| * Copyright (C) 2010 The Android Open Source 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.android.dumprendertree2.ui; |
| |
| import com.android.dumprendertree2.FileFilter; |
| import com.android.dumprendertree2.FsUtils; |
| import com.android.dumprendertree2.TestsListActivity; |
| import com.android.dumprendertree2.R; |
| import com.android.dumprendertree2.forwarder.ForwarderManager; |
| |
| import android.app.Activity; |
| import android.app.AlertDialog; |
| import android.app.Dialog; |
| import android.app.ListActivity; |
| import android.app.ProgressDialog; |
| import android.content.DialogInterface; |
| import android.content.Intent; |
| import android.content.res.Configuration; |
| import android.os.Bundle; |
| import android.os.Environment; |
| import android.os.Handler; |
| import android.os.Message; |
| import android.view.LayoutInflater; |
| import android.view.Menu; |
| import android.view.MenuInflater; |
| import android.view.MenuItem; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.widget.AdapterView; |
| import android.widget.ArrayAdapter; |
| import android.widget.ImageView; |
| import android.widget.ListView; |
| import android.widget.TextView; |
| import android.widget.Toast; |
| |
| import java.io.File; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| /** |
| * An Activity that allows navigating through tests folders and choosing folders or tests to run. |
| */ |
| public class DirListActivity extends ListActivity { |
| |
| private static final String LOG_TAG = "DirListActivity"; |
| |
| /** TODO: This is just a guess - think of a better way to achieve it */ |
| private static final int MEAN_TITLE_CHAR_SIZE = 13; |
| |
| private static final int PROGRESS_DIALOG_DELAY_MS = 200; |
| |
| /** Code for the dialog, used in showDialog and onCreateDialog */ |
| private static final int DIALOG_RUN_ABORT_DIR = 0; |
| |
| /** Messages codes */ |
| private static final int MSG_LOADED_ITEMS = 0; |
| private static final int MSG_SHOW_PROGRESS_DIALOG = 1; |
| |
| private static final CharSequence NO_RESPONSE_MESSAGE = |
| "No response from host when getting directory contents. Is the host server running?"; |
| |
| /** Initialized lazily before first sProgressDialog.show() */ |
| private static ProgressDialog sProgressDialog; |
| |
| private ListView mListView; |
| |
| /** This is a relative path! */ |
| private String mCurrentDirPath; |
| |
| /** |
| * A thread responsible for loading the contents of the directory from sd card |
| * and sending them via Message to main thread that then loads them into |
| * ListView |
| */ |
| private class LoadListItemsThread extends Thread { |
| private Handler mHandler; |
| private String mRelativePath; |
| |
| public LoadListItemsThread(String relativePath, Handler handler) { |
| mRelativePath = relativePath; |
| mHandler = handler; |
| } |
| |
| @Override |
| public void run() { |
| Message msg = mHandler.obtainMessage(MSG_LOADED_ITEMS); |
| msg.obj = getDirList(mRelativePath); |
| mHandler.sendMessage(msg); |
| } |
| } |
| |
| /** |
| * Very simple object to use inside ListView as an item. |
| */ |
| private static class ListItem implements Comparable<ListItem> { |
| private String mRelativePath; |
| private String mName; |
| private boolean mIsDirectory; |
| |
| public ListItem(String relativePath, boolean isDirectory) { |
| mRelativePath = relativePath; |
| mName = new File(relativePath).getName(); |
| mIsDirectory = isDirectory; |
| } |
| |
| public boolean isDirectory() { |
| return mIsDirectory; |
| } |
| |
| public String getRelativePath() { |
| return mRelativePath; |
| } |
| |
| public String getName() { |
| return mName; |
| } |
| |
| @Override |
| public int compareTo(ListItem another) { |
| return mRelativePath.compareTo(another.getRelativePath()); |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| if (!(o instanceof ListItem)) { |
| return false; |
| } |
| |
| return mRelativePath.equals(((ListItem)o).getRelativePath()); |
| } |
| |
| @Override |
| public int hashCode() { |
| return mRelativePath.hashCode(); |
| } |
| |
| } |
| |
| /** |
| * A custom adapter that sets the proper icon and label in the list view. |
| */ |
| private static class DirListAdapter extends ArrayAdapter<ListItem> { |
| private Activity mContext; |
| private ListItem[] mItems; |
| |
| public DirListAdapter(Activity context, ListItem[] items) { |
| super(context, R.layout.dirlist_row, items); |
| |
| mContext = context; |
| mItems = items; |
| } |
| |
| @Override |
| public View getView(int position, View convertView, ViewGroup parent) { |
| LayoutInflater inflater = mContext.getLayoutInflater(); |
| View row = inflater.inflate(R.layout.dirlist_row, null); |
| |
| TextView label = (TextView)row.findViewById(R.id.label); |
| label.setText(mItems[position].getName()); |
| |
| ImageView icon = (ImageView)row.findViewById(R.id.icon); |
| if (mItems[position].isDirectory()) { |
| icon.setImageResource(R.drawable.folder); |
| } else { |
| icon.setImageResource(R.drawable.runtest); |
| } |
| |
| return row; |
| } |
| } |
| |
| @Override |
| protected void onCreate(Bundle savedInstanceState) { |
| super.onCreate(savedInstanceState); |
| |
| ForwarderManager.getForwarderManager().start(); |
| |
| mListView = getListView(); |
| |
| mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { |
| @Override |
| public void onItemClick(AdapterView<?> parent, View view, int position, long id) { |
| ListItem item = (ListItem)parent.getItemAtPosition(position); |
| |
| if (item.isDirectory()) { |
| showDir(item.getRelativePath()); |
| } else { |
| /** Run the test */ |
| runAllTestsUnder(item.getRelativePath()); |
| } |
| } |
| }); |
| |
| mListView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() { |
| @Override |
| public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { |
| ListItem item = (ListItem)parent.getItemAtPosition(position); |
| |
| if (item.isDirectory()) { |
| Bundle arguments = new Bundle(1); |
| arguments.putString("name", item.getName()); |
| arguments.putString("relativePath", item.getRelativePath()); |
| showDialog(DIALOG_RUN_ABORT_DIR, arguments); |
| } else { |
| /** TODO: Maybe show some info about a test? */ |
| } |
| |
| return true; |
| } |
| }); |
| |
| /** All the paths are relative to test root dir where possible */ |
| showDir(""); |
| } |
| |
| private void runAllTestsUnder(String relativePath) { |
| Intent intent = new Intent(); |
| intent.setClass(DirListActivity.this, TestsListActivity.class); |
| intent.setAction(Intent.ACTION_RUN); |
| intent.putExtra(TestsListActivity.EXTRA_TEST_PATH, relativePath); |
| startActivity(intent); |
| } |
| |
| @Override |
| public boolean onCreateOptionsMenu(Menu menu) { |
| MenuInflater inflater = getMenuInflater(); |
| inflater.inflate(R.menu.gui_menu, menu); |
| return true; |
| } |
| |
| @Override |
| public boolean onOptionsItemSelected(MenuItem item) { |
| switch (item.getItemId()) { |
| case R.id.run_all: |
| runAllTestsUnder(mCurrentDirPath); |
| return true; |
| default: |
| return super.onOptionsItemSelected(item); |
| } |
| } |
| |
| @Override |
| /** |
| * Moves to the parent directory if one exists. Does not allow to move above |
| * the test 'root' directory. |
| */ |
| public void onBackPressed() { |
| File currentDirParent = new File(mCurrentDirPath).getParentFile(); |
| if (currentDirParent != null) { |
| showDir(currentDirParent.getPath()); |
| } else { |
| showDir(""); |
| } |
| } |
| |
| /** |
| * Prevents the activity from recreating on change of orientation. The title needs to |
| * be recalculated. |
| */ |
| @Override |
| public void onConfigurationChanged(Configuration newConfig) { |
| super.onConfigurationChanged(newConfig); |
| |
| setTitle(shortenTitle(mCurrentDirPath)); |
| } |
| |
| @Override |
| protected Dialog onCreateDialog(int id, final Bundle args) { |
| Dialog dialog = null; |
| AlertDialog.Builder builder = new AlertDialog.Builder(this); |
| |
| switch (id) { |
| case DIALOG_RUN_ABORT_DIR: |
| builder.setTitle(getText(R.string.dialog_run_abort_dir_title_prefix) + " " + |
| args.getString("name")); |
| builder.setMessage(R.string.dialog_run_abort_dir_msg); |
| builder.setCancelable(true); |
| |
| builder.setPositiveButton(R.string.dialog_run_abort_dir_ok_button, |
| new DialogInterface.OnClickListener() { |
| @Override |
| public void onClick(DialogInterface dialog, int which) { |
| removeDialog(DIALOG_RUN_ABORT_DIR); |
| runAllTestsUnder(args.getString("relativePath")); |
| } |
| }); |
| |
| builder.setNegativeButton(R.string.dialog_run_abort_dir_abort_button, |
| new DialogInterface.OnClickListener() { |
| @Override |
| public void onClick(DialogInterface dialog, int which) { |
| removeDialog(DIALOG_RUN_ABORT_DIR); |
| } |
| }); |
| |
| dialog = builder.create(); |
| dialog.setOnCancelListener(new DialogInterface.OnCancelListener() { |
| @Override |
| public void onCancel(DialogInterface dialog) { |
| removeDialog(DIALOG_RUN_ABORT_DIR); |
| } |
| }); |
| break; |
| } |
| |
| return dialog; |
| } |
| |
| /** |
| * Loads the contents of dir into the list view. |
| * |
| * @param dirPath |
| * directory to load into list view |
| */ |
| private void showDir(String dirPath) { |
| mCurrentDirPath = dirPath; |
| |
| /** Show progress dialog with a delay */ |
| final Handler delayedDialogHandler = new Handler() { |
| @Override |
| public void handleMessage(Message msg) { |
| if (msg.what == MSG_SHOW_PROGRESS_DIALOG) { |
| if (sProgressDialog == null) { |
| sProgressDialog = new ProgressDialog(DirListActivity.this); |
| sProgressDialog.setCancelable(false); |
| sProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER); |
| sProgressDialog.setTitle(R.string.dialog_progress_title); |
| sProgressDialog.setMessage(getText(R.string.dialog_progress_msg)); |
| } |
| sProgressDialog.show(); |
| } |
| } |
| }; |
| Message msgShowDialog = delayedDialogHandler.obtainMessage(MSG_SHOW_PROGRESS_DIALOG); |
| delayedDialogHandler.sendMessageDelayed(msgShowDialog, PROGRESS_DIALOG_DELAY_MS); |
| |
| /** Delegate loading contents from SD card to a new thread */ |
| new LoadListItemsThread(mCurrentDirPath, new Handler() { |
| @Override |
| public void handleMessage(Message msg) { |
| if (msg.what == MSG_LOADED_ITEMS) { |
| setTitle(shortenTitle(mCurrentDirPath)); |
| delayedDialogHandler.removeMessages(MSG_SHOW_PROGRESS_DIALOG); |
| if (sProgressDialog != null) { |
| sProgressDialog.dismiss(); |
| } |
| if (msg.obj == null) { |
| Toast.makeText(DirListActivity.this, NO_RESPONSE_MESSAGE, |
| Toast.LENGTH_LONG).show(); |
| } else { |
| setListAdapter(new DirListAdapter(DirListActivity.this, |
| (ListItem[])msg.obj)); |
| } |
| } |
| } |
| }).start(); |
| } |
| |
| /** |
| * TODO: find a neat way to determine number of characters that fit in the title |
| * bar. |
| * */ |
| private String shortenTitle(String title) { |
| if (title.equals("")) { |
| return "Tests' root dir:"; |
| } |
| int charCount = mListView.getWidth() / MEAN_TITLE_CHAR_SIZE; |
| |
| if (title.length() > charCount) { |
| return "..." + title.substring(title.length() - charCount); |
| } else { |
| return title; |
| } |
| } |
| |
| /** |
| * Return the array with contents of the given directory. |
| * First it contains the subfolders, then the files. Both sorted |
| * alphabetically. |
| * |
| * The dirPath is relative. |
| */ |
| private ListItem[] getDirList(String dirPath) { |
| List<ListItem> subDirs = new ArrayList<ListItem>(); |
| List<ListItem> subFiles = new ArrayList<ListItem>(); |
| |
| List<String> dirRelativePaths = FsUtils.getLayoutTestsDirContents(dirPath, false, true); |
| if (dirRelativePaths == null) { |
| return null; |
| } |
| for (String dirRelativePath : dirRelativePaths) { |
| if (FileFilter.isTestDir(new File(dirRelativePath).getName())) { |
| subDirs.add(new ListItem(dirRelativePath, true)); |
| } |
| } |
| |
| List<String> testRelativePaths = FsUtils.getLayoutTestsDirContents(dirPath, false, false); |
| if (testRelativePaths == null) { |
| return null; |
| } |
| for (String testRelativePath : testRelativePaths) { |
| if (FileFilter.isTestFile(new File(testRelativePath).getName())) { |
| subFiles.add(new ListItem(testRelativePath, false)); |
| } |
| } |
| |
| /** Concatenate the two lists */ |
| subDirs.addAll(subFiles); |
| |
| return subDirs.toArray(new ListItem[subDirs.size()]); |
| } |
| } |