Home | History | Annotate | Download | only in ui
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.dumprendertree2.ui;
     18 
     19 import com.android.dumprendertree2.FileFilter;
     20 import com.android.dumprendertree2.FsUtils;
     21 import com.android.dumprendertree2.TestsListActivity;
     22 import com.android.dumprendertree2.R;
     23 import com.android.dumprendertree2.forwarder.ForwarderManager;
     24 
     25 import android.app.Activity;
     26 import android.app.AlertDialog;
     27 import android.app.Dialog;
     28 import android.app.ListActivity;
     29 import android.app.ProgressDialog;
     30 import android.content.DialogInterface;
     31 import android.content.Intent;
     32 import android.content.res.Configuration;
     33 import android.os.Bundle;
     34 import android.os.Environment;
     35 import android.os.Handler;
     36 import android.os.Message;
     37 import android.view.LayoutInflater;
     38 import android.view.Menu;
     39 import android.view.MenuInflater;
     40 import android.view.MenuItem;
     41 import android.view.View;
     42 import android.view.ViewGroup;
     43 import android.widget.AdapterView;
     44 import android.widget.ArrayAdapter;
     45 import android.widget.ImageView;
     46 import android.widget.ListView;
     47 import android.widget.TextView;
     48 import android.widget.Toast;
     49 
     50 import java.io.File;
     51 import java.util.ArrayList;
     52 import java.util.List;
     53 
     54 /**
     55  * An Activity that allows navigating through tests folders and choosing folders or tests to run.
     56  */
     57 public class DirListActivity extends ListActivity {
     58 
     59     private static final String LOG_TAG = "DirListActivity";
     60 
     61     /** TODO: This is just a guess - think of a better way to achieve it */
     62     private static final int MEAN_TITLE_CHAR_SIZE = 13;
     63 
     64     private static final int PROGRESS_DIALOG_DELAY_MS = 200;
     65 
     66     /** Code for the dialog, used in showDialog and onCreateDialog */
     67     private static final int DIALOG_RUN_ABORT_DIR = 0;
     68 
     69     /** Messages codes */
     70     private static final int MSG_LOADED_ITEMS = 0;
     71     private static final int MSG_SHOW_PROGRESS_DIALOG = 1;
     72 
     73     private static final CharSequence NO_RESPONSE_MESSAGE =
     74             "No response from host when getting directory contents. Is the host server running?";
     75 
     76     /** Initialized lazily before first sProgressDialog.show() */
     77     private static ProgressDialog sProgressDialog;
     78 
     79     private ListView mListView;
     80 
     81     /** This is a relative path! */
     82     private String mCurrentDirPath;
     83 
     84     /**
     85      * A thread responsible for loading the contents of the directory from sd card
     86      * and sending them via Message to main thread that then loads them into
     87      * ListView
     88      */
     89     private class LoadListItemsThread extends Thread {
     90         private Handler mHandler;
     91         private String mRelativePath;
     92 
     93         public LoadListItemsThread(String relativePath, Handler handler) {
     94             mRelativePath = relativePath;
     95             mHandler = handler;
     96         }
     97 
     98         @Override
     99         public void run() {
    100             Message msg = mHandler.obtainMessage(MSG_LOADED_ITEMS);
    101             msg.obj = getDirList(mRelativePath);
    102             mHandler.sendMessage(msg);
    103         }
    104     }
    105 
    106     /**
    107      * Very simple object to use inside ListView as an item.
    108      */
    109     private static class ListItem implements Comparable<ListItem> {
    110         private String mRelativePath;
    111         private String mName;
    112         private boolean mIsDirectory;
    113 
    114         public ListItem(String relativePath, boolean isDirectory) {
    115             mRelativePath = relativePath;
    116             mName = new File(relativePath).getName();
    117             mIsDirectory = isDirectory;
    118         }
    119 
    120         public boolean isDirectory() {
    121             return mIsDirectory;
    122         }
    123 
    124         public String getRelativePath() {
    125             return mRelativePath;
    126         }
    127 
    128         public String getName() {
    129             return mName;
    130         }
    131 
    132         @Override
    133         public int compareTo(ListItem another) {
    134             return mRelativePath.compareTo(another.getRelativePath());
    135         }
    136 
    137         @Override
    138         public boolean equals(Object o) {
    139             if (!(o instanceof ListItem)) {
    140                 return false;
    141             }
    142 
    143             return mRelativePath.equals(((ListItem)o).getRelativePath());
    144         }
    145 
    146         @Override
    147         public int hashCode() {
    148             return mRelativePath.hashCode();
    149         }
    150 
    151     }
    152 
    153     /**
    154      * A custom adapter that sets the proper icon and label in the list view.
    155      */
    156     private static class DirListAdapter extends ArrayAdapter<ListItem> {
    157         private Activity mContext;
    158         private ListItem[] mItems;
    159 
    160         public DirListAdapter(Activity context, ListItem[] items) {
    161             super(context, R.layout.dirlist_row, items);
    162 
    163             mContext = context;
    164             mItems = items;
    165         }
    166 
    167         @Override
    168         public View getView(int position, View convertView, ViewGroup parent) {
    169             LayoutInflater inflater = mContext.getLayoutInflater();
    170             View row = inflater.inflate(R.layout.dirlist_row, null);
    171 
    172             TextView label = (TextView)row.findViewById(R.id.label);
    173             label.setText(mItems[position].getName());
    174 
    175             ImageView icon = (ImageView)row.findViewById(R.id.icon);
    176             if (mItems[position].isDirectory()) {
    177                 icon.setImageResource(R.drawable.folder);
    178             } else {
    179                 icon.setImageResource(R.drawable.runtest);
    180             }
    181 
    182             return row;
    183         }
    184     }
    185 
    186     @Override
    187     protected void onCreate(Bundle savedInstanceState) {
    188         super.onCreate(savedInstanceState);
    189 
    190         ForwarderManager.getForwarderManager().start();
    191 
    192         mListView = getListView();
    193 
    194         mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
    195             @Override
    196             public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
    197                 ListItem item = (ListItem)parent.getItemAtPosition(position);
    198 
    199                 if (item.isDirectory()) {
    200                     showDir(item.getRelativePath());
    201                 } else {
    202                     /** Run the test */
    203                     runAllTestsUnder(item.getRelativePath());
    204                 }
    205             }
    206         });
    207 
    208         mListView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
    209             @Override
    210             public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
    211                 ListItem item = (ListItem)parent.getItemAtPosition(position);
    212 
    213                 if (item.isDirectory()) {
    214                     Bundle arguments = new Bundle(1);
    215                     arguments.putString("name", item.getName());
    216                     arguments.putString("relativePath", item.getRelativePath());
    217                     showDialog(DIALOG_RUN_ABORT_DIR, arguments);
    218                 } else {
    219                     /** TODO: Maybe show some info about a test? */
    220                 }
    221 
    222                 return true;
    223             }
    224         });
    225 
    226         /** All the paths are relative to test root dir where possible */
    227         showDir("");
    228     }
    229 
    230     private void runAllTestsUnder(String relativePath) {
    231         Intent intent = new Intent();
    232         intent.setClass(DirListActivity.this, TestsListActivity.class);
    233         intent.setAction(Intent.ACTION_RUN);
    234         intent.putExtra(TestsListActivity.EXTRA_TEST_PATH, relativePath);
    235         startActivity(intent);
    236     }
    237 
    238     @Override
    239     public boolean onCreateOptionsMenu(Menu menu) {
    240         MenuInflater inflater = getMenuInflater();
    241         inflater.inflate(R.menu.gui_menu, menu);
    242         return true;
    243     }
    244 
    245     @Override
    246     public boolean onOptionsItemSelected(MenuItem item) {
    247         switch (item.getItemId()) {
    248             case R.id.run_all:
    249                 runAllTestsUnder(mCurrentDirPath);
    250                 return true;
    251             default:
    252                 return super.onOptionsItemSelected(item);
    253         }
    254     }
    255 
    256     @Override
    257     /**
    258      * Moves to the parent directory if one exists. Does not allow to move above
    259      * the test 'root' directory.
    260      */
    261     public void onBackPressed() {
    262         File currentDirParent = new File(mCurrentDirPath).getParentFile();
    263         if (currentDirParent != null) {
    264             showDir(currentDirParent.getPath());
    265         } else {
    266             showDir("");
    267         }
    268     }
    269 
    270     /**
    271      * Prevents the activity from recreating on change of orientation. The title needs to
    272      * be recalculated.
    273      */
    274     @Override
    275     public void onConfigurationChanged(Configuration newConfig) {
    276         super.onConfigurationChanged(newConfig);
    277 
    278         setTitle(shortenTitle(mCurrentDirPath));
    279     }
    280 
    281     @Override
    282     protected Dialog onCreateDialog(int id, final Bundle args) {
    283         Dialog dialog = null;
    284         AlertDialog.Builder builder = new AlertDialog.Builder(this);
    285 
    286         switch (id) {
    287             case DIALOG_RUN_ABORT_DIR:
    288                 builder.setTitle(getText(R.string.dialog_run_abort_dir_title_prefix) + " " +
    289                         args.getString("name"));
    290                 builder.setMessage(R.string.dialog_run_abort_dir_msg);
    291                 builder.setCancelable(true);
    292 
    293                 builder.setPositiveButton(R.string.dialog_run_abort_dir_ok_button,
    294                         new DialogInterface.OnClickListener() {
    295                     @Override
    296                     public void onClick(DialogInterface dialog, int which) {
    297                         removeDialog(DIALOG_RUN_ABORT_DIR);
    298                         runAllTestsUnder(args.getString("relativePath"));
    299                     }
    300                 });
    301 
    302                 builder.setNegativeButton(R.string.dialog_run_abort_dir_abort_button,
    303                         new DialogInterface.OnClickListener() {
    304                     @Override
    305                     public void onClick(DialogInterface dialog, int which) {
    306                         removeDialog(DIALOG_RUN_ABORT_DIR);
    307                     }
    308                 });
    309 
    310                 dialog = builder.create();
    311                 dialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
    312                     @Override
    313                     public void onCancel(DialogInterface dialog) {
    314                         removeDialog(DIALOG_RUN_ABORT_DIR);
    315                     }
    316                 });
    317                 break;
    318         }
    319 
    320         return dialog;
    321     }
    322 
    323     /**
    324      * Loads the contents of dir into the list view.
    325      *
    326      * @param dirPath
    327      *      directory to load into list view
    328      */
    329     private void showDir(String dirPath) {
    330         mCurrentDirPath = dirPath;
    331 
    332         /** Show progress dialog with a delay */
    333         final Handler delayedDialogHandler = new Handler() {
    334             @Override
    335             public void handleMessage(Message msg) {
    336                 if (msg.what == MSG_SHOW_PROGRESS_DIALOG) {
    337                     if (sProgressDialog == null) {
    338                         sProgressDialog = new ProgressDialog(DirListActivity.this);
    339                         sProgressDialog.setCancelable(false);
    340                         sProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
    341                         sProgressDialog.setTitle(R.string.dialog_progress_title);
    342                         sProgressDialog.setMessage(getText(R.string.dialog_progress_msg));
    343                     }
    344                     sProgressDialog.show();
    345                 }
    346             }
    347         };
    348         Message msgShowDialog = delayedDialogHandler.obtainMessage(MSG_SHOW_PROGRESS_DIALOG);
    349         delayedDialogHandler.sendMessageDelayed(msgShowDialog, PROGRESS_DIALOG_DELAY_MS);
    350 
    351         /** Delegate loading contents from SD card to a new thread */
    352         new LoadListItemsThread(mCurrentDirPath, new Handler() {
    353             @Override
    354             public void handleMessage(Message msg) {
    355                 if (msg.what == MSG_LOADED_ITEMS) {
    356                     setTitle(shortenTitle(mCurrentDirPath));
    357                     delayedDialogHandler.removeMessages(MSG_SHOW_PROGRESS_DIALOG);
    358                     if (sProgressDialog != null) {
    359                         sProgressDialog.dismiss();
    360                     }
    361                     if (msg.obj == null) {
    362                         Toast.makeText(DirListActivity.this, NO_RESPONSE_MESSAGE,
    363                                 Toast.LENGTH_LONG).show();
    364                     } else {
    365                         setListAdapter(new DirListAdapter(DirListActivity.this,
    366                                 (ListItem[])msg.obj));
    367                     }
    368                 }
    369             }
    370         }).start();
    371     }
    372 
    373     /**
    374      * TODO: find a neat way to determine number of characters that fit in the title
    375      * bar.
    376      * */
    377     private String shortenTitle(String title) {
    378         if (title.equals("")) {
    379             return "Tests' root dir:";
    380         }
    381         int charCount = mListView.getWidth() / MEAN_TITLE_CHAR_SIZE;
    382 
    383         if (title.length() > charCount) {
    384             return "..." + title.substring(title.length() - charCount);
    385         } else {
    386             return title;
    387         }
    388     }
    389 
    390     /**
    391      * Return the array with contents of the given directory.
    392      * First it contains the subfolders, then the files. Both sorted
    393      * alphabetically.
    394      *
    395      * The dirPath is relative.
    396      */
    397     private ListItem[] getDirList(String dirPath) {
    398         List<ListItem> subDirs = new ArrayList<ListItem>();
    399         List<ListItem> subFiles = new ArrayList<ListItem>();
    400 
    401         List<String> dirRelativePaths = FsUtils.getLayoutTestsDirContents(dirPath, false, true);
    402         if (dirRelativePaths == null) {
    403             return null;
    404         }
    405         for (String dirRelativePath : dirRelativePaths) {
    406             if (FileFilter.isTestDir(new File(dirRelativePath).getName())) {
    407                 subDirs.add(new ListItem(dirRelativePath, true));
    408             }
    409         }
    410 
    411         List<String> testRelativePaths = FsUtils.getLayoutTestsDirContents(dirPath, false, false);
    412         if (testRelativePaths == null) {
    413             return null;
    414         }
    415         for (String testRelativePath : testRelativePaths) {
    416             if (FileFilter.isTestFile(new File(testRelativePath).getName())) {
    417                 subFiles.add(new ListItem(testRelativePath, false));
    418             }
    419         }
    420 
    421         /** Concatenate the two lists */
    422         subDirs.addAll(subFiles);
    423 
    424         return subDirs.toArray(new ListItem[subDirs.size()]);
    425     }
    426 }
    427