Home | History | Annotate | Download | only in wiktionary
      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.example.android.wiktionary;
     18 
     19 import com.example.android.wiktionary.SimpleWikiHelper.ApiException;
     20 import com.example.android.wiktionary.SimpleWikiHelper.ParseException;
     21 
     22 import android.app.Activity;
     23 import android.app.AlertDialog;
     24 import android.app.SearchManager;
     25 import android.content.Intent;
     26 import android.net.Uri;
     27 import android.os.AsyncTask;
     28 import android.os.Bundle;
     29 import android.os.SystemClock;
     30 import android.text.TextUtils;
     31 import android.text.format.DateUtils;
     32 import android.util.Log;
     33 import android.view.KeyEvent;
     34 import android.view.Menu;
     35 import android.view.MenuInflater;
     36 import android.view.MenuItem;
     37 import android.view.View;
     38 import android.view.animation.Animation;
     39 import android.view.animation.AnimationUtils;
     40 import android.view.animation.Animation.AnimationListener;
     41 import android.webkit.WebView;
     42 import android.widget.ProgressBar;
     43 import android.widget.TextView;
     44 
     45 import java.util.Stack;
     46 
     47 /**
     48  * Activity that lets users browse through Wiktionary content. This is just the
     49  * user interface, and all API communication and parsing is handled in
     50  * {@link ExtendedWikiHelper}.
     51  */
     52 public class LookupActivity extends Activity implements AnimationListener {
     53     private static final String TAG = "LookupActivity";
     54 
     55     private View mTitleBar;
     56     private TextView mTitle;
     57     private ProgressBar mProgress;
     58     private WebView mWebView;
     59 
     60     private Animation mSlideIn;
     61     private Animation mSlideOut;
     62 
     63     /**
     64      * History stack of previous words browsed in this session. This is
     65      * referenced when the user taps the "back" key, to possibly intercept and
     66      * show the last-visited entry, instead of closing the activity.
     67      */
     68     private Stack<String> mHistory = new Stack<String>();
     69 
     70     private String mEntryTitle;
     71 
     72     /**
     73      * Keep track of last time user tapped "back" hard key. When pressed more
     74      * than once within {@link #BACK_THRESHOLD}, we treat let the back key fall
     75      * through and close the app.
     76      */
     77     private long mLastPress = -1;
     78 
     79     private static final long BACK_THRESHOLD = DateUtils.SECOND_IN_MILLIS / 2;
     80 
     81     /**
     82      * {@inheritDoc}
     83      */
     84     @Override
     85     public void onCreate(Bundle savedInstanceState) {
     86         super.onCreate(savedInstanceState);
     87 
     88         setContentView(R.layout.lookup);
     89 
     90         // Load animations used to show/hide progress bar
     91         mSlideIn = AnimationUtils.loadAnimation(this, R.anim.slide_in);
     92         mSlideOut = AnimationUtils.loadAnimation(this, R.anim.slide_out);
     93 
     94         // Listen for the "in" animation so we make the progress bar visible
     95         // only after the sliding has finished.
     96         mSlideIn.setAnimationListener(this);
     97 
     98         mTitleBar = findViewById(R.id.title_bar);
     99         mTitle = (TextView) findViewById(R.id.title);
    100         mProgress = (ProgressBar) findViewById(R.id.progress);
    101         mWebView = (WebView) findViewById(R.id.webview);
    102 
    103         // Make the view transparent to show background
    104         mWebView.setBackgroundColor(0);
    105 
    106         // Prepare User-Agent string for wiki actions
    107         ExtendedWikiHelper.prepareUserAgent(this);
    108 
    109         // Handle incoming intents as possible searches or links
    110         onNewIntent(getIntent());
    111     }
    112 
    113     /**
    114      * Intercept the back-key to try walking backwards along our word history
    115      * stack. If we don't have any remaining history, the key behaves normally
    116      * and closes this activity.
    117      */
    118     @Override
    119     public boolean onKeyDown(int keyCode, KeyEvent event) {
    120         // Handle back key as long we have a history stack
    121         if (keyCode == KeyEvent.KEYCODE_BACK && !mHistory.empty()) {
    122 
    123             // Compare against last pressed time, and if user hit multiple times
    124             // in quick succession, we should consider bailing out early.
    125             long currentPress = SystemClock.uptimeMillis();
    126             if (currentPress - mLastPress < BACK_THRESHOLD) {
    127                 return super.onKeyDown(keyCode, event);
    128             }
    129             mLastPress = currentPress;
    130 
    131             // Pop last entry off stack and start loading
    132             String lastEntry = mHistory.pop();
    133             startNavigating(lastEntry, false);
    134 
    135             return true;
    136         }
    137 
    138         // Otherwise fall through to parent
    139         return super.onKeyDown(keyCode, event);
    140     }
    141 
    142     /**
    143      * Start navigating to the given word, pushing any current word onto the
    144      * history stack if requested. The navigation happens on a background thread
    145      * and updates the GUI when finished.
    146      *
    147      * @param word The dictionary word to navigate to.
    148      * @param pushHistory If true, push the current word onto history stack.
    149      */
    150     private void startNavigating(String word, boolean pushHistory) {
    151         // Push any current word onto the history stack
    152         if (!TextUtils.isEmpty(mEntryTitle) && pushHistory) {
    153             mHistory.add(mEntryTitle);
    154         }
    155 
    156         // Start lookup for new word in background
    157         new LookupTask().execute(word);
    158     }
    159 
    160     /**
    161      * {@inheritDoc}
    162      */
    163     @Override
    164     public boolean onCreateOptionsMenu(Menu menu) {
    165         MenuInflater inflater = getMenuInflater();
    166         inflater.inflate(R.menu.lookup, menu);
    167         return true;
    168     }
    169 
    170     /**
    171      * {@inheritDoc}
    172      */
    173     @Override
    174     public boolean onOptionsItemSelected(MenuItem item) {
    175         switch (item.getItemId()) {
    176             case R.id.lookup_search: {
    177                 onSearchRequested();
    178                 return true;
    179             }
    180             case R.id.lookup_random: {
    181                 startNavigating(null, true);
    182                 return true;
    183             }
    184             case R.id.lookup_about: {
    185                 showAbout();
    186                 return true;
    187             }
    188         }
    189         return false;
    190     }
    191 
    192     /**
    193      * Show an about dialog that cites data sources.
    194      */
    195     protected void showAbout() {
    196         // Inflate the about message contents
    197         View messageView = getLayoutInflater().inflate(R.layout.about, null, false);
    198 
    199         // When linking text, force to always use default color. This works
    200         // around a pressed color state bug.
    201         TextView textView = (TextView) messageView.findViewById(R.id.about_credits);
    202         int defaultColor = textView.getTextColors().getDefaultColor();
    203         textView.setTextColor(defaultColor);
    204 
    205         AlertDialog.Builder builder = new AlertDialog.Builder(this);
    206         builder.setIcon(R.drawable.app_icon);
    207         builder.setTitle(R.string.app_name);
    208         builder.setView(messageView);
    209         builder.create();
    210         builder.show();
    211     }
    212 
    213     /**
    214      * Because we're singleTop, we handle our own new intents. These usually
    215      * come from the {@link SearchManager} when a search is requested, or from
    216      * internal links the user clicks on.
    217      */
    218     @Override
    219     public void onNewIntent(Intent intent) {
    220         final String action = intent.getAction();
    221         if (Intent.ACTION_SEARCH.equals(action)) {
    222             // Start query for incoming search request
    223             String query = intent.getStringExtra(SearchManager.QUERY);
    224             startNavigating(query, true);
    225 
    226         } else if (Intent.ACTION_VIEW.equals(action)) {
    227             // Treat as internal link only if valid Uri and host matches
    228             Uri data = intent.getData();
    229             if (data != null && ExtendedWikiHelper.WIKI_LOOKUP_HOST
    230                     .equals(data.getHost())) {
    231                 String query = data.getPathSegments().get(0);
    232                 startNavigating(query, true);
    233             }
    234 
    235         } else {
    236             // If not recognized, then start showing random word
    237             startNavigating(null, true);
    238         }
    239     }
    240 
    241     /**
    242      * Set the title for the current entry.
    243      */
    244     protected void setEntryTitle(String entryText) {
    245         mEntryTitle = entryText;
    246         mTitle.setText(mEntryTitle);
    247     }
    248 
    249     /**
    250      * Set the content for the current entry. This will update our
    251      * {@link WebView} to show the requested content.
    252      */
    253     protected void setEntryContent(String entryContent) {
    254         mWebView.loadDataWithBaseURL(ExtendedWikiHelper.WIKI_AUTHORITY, entryContent,
    255                 ExtendedWikiHelper.MIME_TYPE, ExtendedWikiHelper.ENCODING, null);
    256     }
    257 
    258     /**
    259      * Background task to handle Wiktionary lookups. This correctly shows and
    260      * hides the loading animation from the GUI thread before starting a
    261      * background query to the Wiktionary API. When finished, it transitions
    262      * back to the GUI thread where it updates with the newly-found entry.
    263      */
    264     private class LookupTask extends AsyncTask<String, String, String> {
    265         /**
    266          * Before jumping into background thread, start sliding in the
    267          * {@link ProgressBar}. We'll only show it once the animation finishes.
    268          */
    269         @Override
    270         protected void onPreExecute() {
    271             mTitleBar.startAnimation(mSlideIn);
    272         }
    273 
    274         /**
    275          * Perform the background query using {@link ExtendedWikiHelper}, which
    276          * may return an error message as the result.
    277          */
    278         @Override
    279         protected String doInBackground(String... args) {
    280             String query = args[0];
    281             String parsedText = null;
    282 
    283             try {
    284                 // If query word is null, assume request for random word
    285                 if (query == null) {
    286                     query = ExtendedWikiHelper.getRandomWord();
    287                 }
    288 
    289                 if (query != null) {
    290                     // Push our requested word to the title bar
    291                     publishProgress(query);
    292                     String wikiText = ExtendedWikiHelper.getPageContent(query, true);
    293                     parsedText = ExtendedWikiHelper.formatWikiText(wikiText);
    294                 }
    295             } catch (ApiException e) {
    296                 Log.e(TAG, "Problem making wiktionary request", e);
    297             } catch (ParseException e) {
    298                 Log.e(TAG, "Problem making wiktionary request", e);
    299             }
    300 
    301             if (parsedText == null) {
    302                 parsedText = getString(R.string.empty_result);
    303             }
    304 
    305             return parsedText;
    306         }
    307 
    308         /**
    309          * Our progress update pushes a title bar update.
    310          */
    311         @Override
    312         protected void onProgressUpdate(String... args) {
    313             String searchWord = args[0];
    314             setEntryTitle(searchWord);
    315         }
    316 
    317         /**
    318          * When finished, push the newly-found entry content into our
    319          * {@link WebView} and hide the {@link ProgressBar}.
    320          */
    321         @Override
    322         protected void onPostExecute(String parsedText) {
    323             mTitleBar.startAnimation(mSlideOut);
    324             mProgress.setVisibility(View.INVISIBLE);
    325 
    326             setEntryContent(parsedText);
    327         }
    328     }
    329 
    330     /**
    331      * Make the {@link ProgressBar} visible when our in-animation finishes.
    332      */
    333     public void onAnimationEnd(Animation animation) {
    334         mProgress.setVisibility(View.VISIBLE);
    335     }
    336 
    337     public void onAnimationRepeat(Animation animation) {
    338         // Not interested if the animation repeats
    339     }
    340 
    341     public void onAnimationStart(Animation animation) {
    342         // Not interested when the animation starts
    343     }
    344 }
    345