Home | History | Annotate | Download | only in builder
      1 /*
      2  * Copyright (C) 2009 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.gesture.builder;
     18 
     19 import android.app.Dialog;
     20 import android.app.AlertDialog;
     21 import android.app.ListActivity;
     22 import android.os.Bundle;
     23 import android.os.AsyncTask;
     24 import android.os.Environment;
     25 import android.view.View;
     26 import android.view.ContextMenu;
     27 import android.view.MenuItem;
     28 import android.view.LayoutInflater;
     29 import android.view.ViewGroup;
     30 import android.gesture.GestureLibrary;
     31 import android.gesture.Gesture;
     32 import android.gesture.GestureLibraries;
     33 import android.widget.TextView;
     34 import android.widget.EditText;
     35 import android.widget.AdapterView;
     36 import android.widget.Toast;
     37 import android.widget.ArrayAdapter;
     38 import android.content.DialogInterface;
     39 import android.content.Context;
     40 import android.content.Intent;
     41 import android.content.res.Resources;
     42 import android.text.TextUtils;
     43 import android.graphics.Bitmap;
     44 import android.graphics.drawable.Drawable;
     45 import android.graphics.drawable.BitmapDrawable;
     46 
     47 import java.util.Map;
     48 import java.util.Collections;
     49 import java.util.HashMap;
     50 import java.util.Comparator;
     51 import java.util.Set;
     52 import java.io.File;
     53 
     54 public class GestureBuilderActivity extends ListActivity {
     55     private static final int STATUS_SUCCESS = 0;
     56     private static final int STATUS_CANCELLED = 1;
     57     private static final int STATUS_NO_STORAGE = 2;
     58     private static final int STATUS_NOT_LOADED = 3;
     59 
     60     private static final int MENU_ID_RENAME = 1;
     61     private static final int MENU_ID_REMOVE = 2;
     62 
     63     private static final int DIALOG_RENAME_GESTURE = 1;
     64 
     65     private static final int REQUEST_NEW_GESTURE = 1;
     66 
     67     // Type: long (id)
     68     private static final String GESTURES_INFO_ID = "gestures.info_id";
     69 
     70     private final File mStoreFile = new File(Environment.getExternalStorageDirectory(), "gestures");
     71 
     72     private final Comparator<NamedGesture> mSorter = new Comparator<NamedGesture>() {
     73         public int compare(NamedGesture object1, NamedGesture object2) {
     74             return object1.name.compareTo(object2.name);
     75         }
     76     };
     77 
     78     private static GestureLibrary sStore;
     79 
     80     private GesturesAdapter mAdapter;
     81     private GesturesLoadTask mTask;
     82     private TextView mEmpty;
     83 
     84     private Dialog mRenameDialog;
     85     private EditText mInput;
     86     private NamedGesture mCurrentRenameGesture;
     87 
     88     @Override
     89     protected void onCreate(Bundle savedInstanceState) {
     90         super.onCreate(savedInstanceState);
     91 
     92         setContentView(R.layout.gestures_list);
     93 
     94         mAdapter = new GesturesAdapter(this);
     95         setListAdapter(mAdapter);
     96 
     97         if (sStore == null) {
     98             sStore = GestureLibraries.fromFile(mStoreFile);
     99         }
    100         mEmpty = (TextView) findViewById(android.R.id.empty);
    101         loadGestures();
    102 
    103         registerForContextMenu(getListView());
    104     }
    105 
    106     static GestureLibrary getStore() {
    107         return sStore;
    108     }
    109 
    110     @SuppressWarnings({"UnusedDeclaration"})
    111     public void reloadGestures(View v) {
    112         loadGestures();
    113     }
    114 
    115     @SuppressWarnings({"UnusedDeclaration"})
    116     public void addGesture(View v) {
    117         Intent intent = new Intent(this, CreateGestureActivity.class);
    118         startActivityForResult(intent, REQUEST_NEW_GESTURE);
    119     }
    120 
    121     @Override
    122     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    123         super.onActivityResult(requestCode, resultCode, data);
    124 
    125         if (resultCode == RESULT_OK) {
    126             switch (requestCode) {
    127                 case REQUEST_NEW_GESTURE:
    128                     loadGestures();
    129                     break;
    130             }
    131         }
    132     }
    133 
    134     private void loadGestures() {
    135         if (mTask != null && mTask.getStatus() != GesturesLoadTask.Status.FINISHED) {
    136             mTask.cancel(true);
    137         }
    138         mTask = (GesturesLoadTask) new GesturesLoadTask().execute();
    139     }
    140 
    141     @Override
    142     protected void onDestroy() {
    143         super.onDestroy();
    144 
    145         if (mTask != null && mTask.getStatus() != GesturesLoadTask.Status.FINISHED) {
    146             mTask.cancel(true);
    147             mTask = null;
    148         }
    149 
    150         cleanupRenameDialog();
    151     }
    152 
    153     private void checkForEmpty() {
    154         if (mAdapter.getCount() == 0) {
    155             mEmpty.setText(R.string.gestures_empty);
    156         }
    157     }
    158 
    159     @Override
    160     protected void onSaveInstanceState(Bundle outState) {
    161         super.onSaveInstanceState(outState);
    162 
    163         if (mCurrentRenameGesture != null) {
    164             outState.putLong(GESTURES_INFO_ID, mCurrentRenameGesture.gesture.getID());
    165         }
    166     }
    167 
    168     @Override
    169     protected void onRestoreInstanceState(Bundle state) {
    170         super.onRestoreInstanceState(state);
    171 
    172         long id = state.getLong(GESTURES_INFO_ID, -1);
    173         if (id != -1) {
    174             final Set<String> entries = sStore.getGestureEntries();
    175 out:        for (String name : entries) {
    176                 for (Gesture gesture : sStore.getGestures(name)) {
    177                     if (gesture.getID() == id) {
    178                         mCurrentRenameGesture = new NamedGesture();
    179                         mCurrentRenameGesture.name = name;
    180                         mCurrentRenameGesture.gesture = gesture;
    181                         break out;
    182                     }
    183                 }
    184             }
    185         }
    186     }
    187 
    188     @Override
    189     public void onCreateContextMenu(ContextMenu menu, View v,
    190             ContextMenu.ContextMenuInfo menuInfo) {
    191 
    192         super.onCreateContextMenu(menu, v, menuInfo);
    193 
    194         AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo;
    195         menu.setHeaderTitle(((TextView) info.targetView).getText());
    196 
    197         menu.add(0, MENU_ID_RENAME, 0, R.string.gestures_rename);
    198         menu.add(0, MENU_ID_REMOVE, 0, R.string.gestures_delete);
    199     }
    200 
    201     @Override
    202     public boolean onContextItemSelected(MenuItem item) {
    203         final AdapterView.AdapterContextMenuInfo menuInfo = (AdapterView.AdapterContextMenuInfo)
    204                 item.getMenuInfo();
    205         final NamedGesture gesture = (NamedGesture) menuInfo.targetView.getTag();
    206 
    207         switch (item.getItemId()) {
    208             case MENU_ID_RENAME:
    209                 renameGesture(gesture);
    210                 return true;
    211             case MENU_ID_REMOVE:
    212                 deleteGesture(gesture);
    213                 return true;
    214         }
    215 
    216         return super.onContextItemSelected(item);
    217     }
    218 
    219     private void renameGesture(NamedGesture gesture) {
    220         mCurrentRenameGesture = gesture;
    221         showDialog(DIALOG_RENAME_GESTURE);
    222     }
    223 
    224     @Override
    225     protected Dialog onCreateDialog(int id) {
    226         if (id == DIALOG_RENAME_GESTURE) {
    227             return createRenameDialog();
    228         }
    229         return super.onCreateDialog(id);
    230     }
    231 
    232     @Override
    233     protected void onPrepareDialog(int id, Dialog dialog) {
    234         super.onPrepareDialog(id, dialog);
    235         if (id == DIALOG_RENAME_GESTURE) {
    236             mInput.setText(mCurrentRenameGesture.name);
    237         }
    238     }
    239 
    240     private Dialog createRenameDialog() {
    241         final View layout = View.inflate(this, R.layout.dialog_rename, null);
    242         mInput = (EditText) layout.findViewById(R.id.name);
    243         ((TextView) layout.findViewById(R.id.label)).setText(R.string.gestures_rename_label);
    244 
    245         AlertDialog.Builder builder = new AlertDialog.Builder(this);
    246         builder.setIcon(0);
    247         builder.setTitle(getString(R.string.gestures_rename_title));
    248         builder.setCancelable(true);
    249         builder.setOnCancelListener(new Dialog.OnCancelListener() {
    250             public void onCancel(DialogInterface dialog) {
    251                 cleanupRenameDialog();
    252             }
    253         });
    254         builder.setNegativeButton(getString(R.string.cancel_action),
    255             new Dialog.OnClickListener() {
    256                 public void onClick(DialogInterface dialog, int which) {
    257                     cleanupRenameDialog();
    258                 }
    259             }
    260         );
    261         builder.setPositiveButton(getString(R.string.rename_action),
    262             new Dialog.OnClickListener() {
    263                 public void onClick(DialogInterface dialog, int which) {
    264                     changeGestureName();
    265                 }
    266             }
    267         );
    268         builder.setView(layout);
    269         return builder.create();
    270     }
    271 
    272     private void changeGestureName() {
    273         final String name = mInput.getText().toString();
    274         if (!TextUtils.isEmpty(name)) {
    275             final NamedGesture renameGesture = mCurrentRenameGesture;
    276             final GesturesAdapter adapter = mAdapter;
    277             final int count = adapter.getCount();
    278 
    279             // Simple linear search, there should not be enough items to warrant
    280             // a more sophisticated search
    281             for (int i = 0; i < count; i++) {
    282                 final NamedGesture gesture = adapter.getItem(i);
    283                 if (gesture.gesture.getID() == renameGesture.gesture.getID()) {
    284                     sStore.removeGesture(gesture.name, gesture.gesture);
    285                     gesture.name = mInput.getText().toString();
    286                     sStore.addGesture(gesture.name, gesture.gesture);
    287                     break;
    288                 }
    289             }
    290 
    291             adapter.notifyDataSetChanged();
    292         }
    293         mCurrentRenameGesture = null;
    294     }
    295 
    296     private void cleanupRenameDialog() {
    297         if (mRenameDialog != null) {
    298             mRenameDialog.dismiss();
    299             mRenameDialog = null;
    300         }
    301         mCurrentRenameGesture = null;
    302     }
    303 
    304     private void deleteGesture(NamedGesture gesture) {
    305         sStore.removeGesture(gesture.name, gesture.gesture);
    306         sStore.save();
    307 
    308         final GesturesAdapter adapter = mAdapter;
    309         adapter.setNotifyOnChange(false);
    310         adapter.remove(gesture);
    311         adapter.sort(mSorter);
    312         checkForEmpty();
    313         adapter.notifyDataSetChanged();
    314 
    315         Toast.makeText(this, R.string.gestures_delete_success, Toast.LENGTH_SHORT).show();
    316     }
    317 
    318     private class GesturesLoadTask extends AsyncTask<Void, NamedGesture, Integer> {
    319         private int mThumbnailSize;
    320         private int mThumbnailInset;
    321         private int mPathColor;
    322 
    323         @Override
    324         protected void onPreExecute() {
    325             super.onPreExecute();
    326 
    327             final Resources resources = getResources();
    328             mPathColor = resources.getColor(R.color.gesture_color);
    329             mThumbnailInset = (int) resources.getDimension(R.dimen.gesture_thumbnail_inset);
    330             mThumbnailSize = (int) resources.getDimension(R.dimen.gesture_thumbnail_size);
    331 
    332             findViewById(R.id.addButton).setEnabled(false);
    333             findViewById(R.id.reloadButton).setEnabled(false);
    334 
    335             mAdapter.setNotifyOnChange(false);
    336             mAdapter.clear();
    337         }
    338 
    339         @Override
    340         protected Integer doInBackground(Void... params) {
    341             if (isCancelled()) return STATUS_CANCELLED;
    342             if (!Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
    343                 return STATUS_NO_STORAGE;
    344             }
    345 
    346             final GestureLibrary store = sStore;
    347 
    348             if (store.load()) {
    349                 for (String name : store.getGestureEntries()) {
    350                     if (isCancelled()) break;
    351 
    352                     for (Gesture gesture : store.getGestures(name)) {
    353                         final Bitmap bitmap = gesture.toBitmap(mThumbnailSize, mThumbnailSize,
    354                                 mThumbnailInset, mPathColor);
    355                         final NamedGesture namedGesture = new NamedGesture();
    356                         namedGesture.gesture = gesture;
    357                         namedGesture.name = name;
    358 
    359                         mAdapter.addBitmap(namedGesture.gesture.getID(), bitmap);
    360                         publishProgress(namedGesture);
    361                     }
    362                 }
    363 
    364                 return STATUS_SUCCESS;
    365             }
    366 
    367             return STATUS_NOT_LOADED;
    368         }
    369 
    370         @Override
    371         protected void onProgressUpdate(NamedGesture... values) {
    372             super.onProgressUpdate(values);
    373 
    374             final GesturesAdapter adapter = mAdapter;
    375             adapter.setNotifyOnChange(false);
    376 
    377             for (NamedGesture gesture : values) {
    378                 adapter.add(gesture);
    379             }
    380 
    381             adapter.sort(mSorter);
    382             adapter.notifyDataSetChanged();
    383         }
    384 
    385         @Override
    386         protected void onPostExecute(Integer result) {
    387             super.onPostExecute(result);
    388 
    389             if (result == STATUS_NO_STORAGE) {
    390                 getListView().setVisibility(View.GONE);
    391                 mEmpty.setVisibility(View.VISIBLE);
    392                 mEmpty.setText(getString(R.string.gestures_error_loading,
    393                         mStoreFile.getAbsolutePath()));
    394             } else {
    395                 findViewById(R.id.addButton).setEnabled(true);
    396                 findViewById(R.id.reloadButton).setEnabled(true);
    397                 checkForEmpty();
    398             }
    399         }
    400     }
    401 
    402     static class NamedGesture {
    403         String name;
    404         Gesture gesture;
    405     }
    406 
    407     private class GesturesAdapter extends ArrayAdapter<NamedGesture> {
    408         private final LayoutInflater mInflater;
    409         private final Map<Long, Drawable> mThumbnails = Collections.synchronizedMap(
    410                 new HashMap<Long, Drawable>());
    411 
    412         public GesturesAdapter(Context context) {
    413             super(context, 0);
    414             mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    415         }
    416 
    417         void addBitmap(Long id, Bitmap bitmap) {
    418             mThumbnails.put(id, new BitmapDrawable(bitmap));
    419         }
    420 
    421         @Override
    422         public View getView(int position, View convertView, ViewGroup parent) {
    423             if (convertView == null) {
    424                 convertView = mInflater.inflate(R.layout.gestures_item, parent, false);
    425             }
    426 
    427             final NamedGesture gesture = getItem(position);
    428             final TextView label = (TextView) convertView;
    429 
    430             label.setTag(gesture);
    431             label.setText(gesture.name);
    432             label.setCompoundDrawablesWithIntrinsicBounds(mThumbnails.get(gesture.gesture.getID()),
    433                     null, null, null);
    434 
    435             return convertView;
    436         }
    437     }
    438 }
    439