Home | History | Annotate | Download | only in dialog
      1 /*
      2  * Copyright (C) 2012 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.contacts.dialog;
     18 
     19 import android.app.Dialog;
     20 import android.app.DialogFragment;
     21 import android.app.FragmentManager;
     22 import android.app.ProgressDialog;
     23 import android.content.DialogInterface;
     24 import android.os.Bundle;
     25 import android.os.Handler;
     26 
     27 /**
     28  * Indeterminate progress dialog wrapped up in a DialogFragment to work even when the device
     29  * orientation is changed. Currently, only supports adding a title and/or message to the progress
     30  * dialog.  There is an additional parameter of the minimum amount of time to display the progress
     31  * dialog even after a call to dismiss the dialog {@link #dismiss()} or
     32  * {@link #dismissAllowingStateLoss()}.
     33  * <p>
     34  * To create and show the progress dialog, use
     35  * {@link #show(FragmentManager, CharSequence, CharSequence, long)} and retain the reference to the
     36  * IndeterminateProgressDialog instance.
     37  * <p>
     38  * To dismiss the dialog, use {@link #dismiss()} or {@link #dismissAllowingStateLoss()} on the
     39  * instance.  The instance returned by
     40  * {@link #show(FragmentManager, CharSequence, CharSequence, long)} is guaranteed to be valid
     41  * after a device orientation change because the {@link #setRetainInstance(boolean)} is called
     42  * internally with true.
     43  */
     44 public class IndeterminateProgressDialog extends DialogFragment {
     45     private static final String TAG = "IndeterminateProgress";
     46 
     47     private CharSequence mTitle;
     48     private CharSequence mMessage;
     49     private long mMinDisplayTime;
     50     private long mShowTime = 0;
     51     private boolean mActivityReady = false;
     52     private Dialog mOldDialog;
     53     private final Handler mHandler = new Handler();
     54     private boolean mCalledSuperDismiss = false;
     55     private boolean mAllowStateLoss;
     56     private final Runnable mDismisser = new Runnable() {
     57         @Override
     58         public void run() {
     59             superDismiss();
     60         }
     61     };
     62 
     63     /**
     64      * Creates and shows an indeterminate progress dialog.  Once the progress dialog is shown, it
     65      * will be shown for at least the minDisplayTime (in milliseconds), so that the progress dialog
     66      * does not flash in and out to quickly.
     67      */
     68     public static IndeterminateProgressDialog show(FragmentManager fragmentManager,
     69             CharSequence title, CharSequence message, long minDisplayTime) {
     70         IndeterminateProgressDialog dialogFragment = new IndeterminateProgressDialog();
     71         dialogFragment.mTitle = title;
     72         dialogFragment.mMessage = message;
     73         dialogFragment.mMinDisplayTime = minDisplayTime;
     74         dialogFragment.show(fragmentManager, TAG);
     75         dialogFragment.mShowTime = System.currentTimeMillis();
     76         dialogFragment.setCancelable(false);
     77 
     78         return dialogFragment;
     79     }
     80 
     81     @Override
     82     public void onCreate(Bundle savedInstanceState) {
     83         super.onCreate(savedInstanceState);
     84         setRetainInstance(true);
     85     }
     86 
     87     @Override
     88     public Dialog onCreateDialog(Bundle savedInstanceState) {
     89         // Create the progress dialog and set its properties
     90         final ProgressDialog dialog = new ProgressDialog(getActivity());
     91         dialog.setIndeterminate(true);
     92         dialog.setIndeterminateDrawable(null);
     93         dialog.setTitle(mTitle);
     94         dialog.setMessage(mMessage);
     95 
     96         return dialog;
     97     }
     98 
     99     @Override
    100     public void onStart() {
    101         super.onStart();
    102         mActivityReady = true;
    103 
    104         // Check if superDismiss() had been called before.  This can happen if in a long
    105         // running operation, the user hits the home button and closes this fragment's activity.
    106         // Upon returning, we want to dismiss this progress dialog fragment.
    107         if (mCalledSuperDismiss) {
    108             superDismiss();
    109         }
    110     }
    111 
    112     @Override
    113     public void onStop() {
    114         super.onStop();
    115         mActivityReady = false;
    116     }
    117 
    118     /**
    119      * There is a race condition that is not handled properly by the DialogFragment class.
    120      * If we don't check that this onDismiss callback isn't for the old progress dialog from before
    121      * the device orientation change, then this will cause the newly created dialog after the
    122      * orientation change to be dismissed immediately.
    123      */
    124     @Override
    125     public void onDismiss(DialogInterface dialog) {
    126         if (mOldDialog != null && mOldDialog == dialog) {
    127             // This is the callback from the old progress dialog that was already dismissed before
    128             // the device orientation change, so just ignore it.
    129             return;
    130         }
    131         super.onDismiss(dialog);
    132     }
    133 
    134     /**
    135      * Save the old dialog that is about to get destroyed in case this is due to a change
    136      * in device orientation.  This will allow us to intercept the callback to
    137      * {@link #onDismiss(DialogInterface)} in case the callback happens after a new progress dialog
    138      * instance was created.
    139      */
    140     @Override
    141     public void onDestroyView() {
    142         mOldDialog = getDialog();
    143         super.onDestroyView();
    144     }
    145 
    146     /**
    147      * This tells the progress dialog to dismiss itself after guaranteeing to be shown for the
    148      * specified time in {@link #show(FragmentManager, CharSequence, CharSequence, long)}.
    149      */
    150     @Override
    151     public void dismiss() {
    152         mAllowStateLoss = false;
    153         dismissWhenReady();
    154     }
    155 
    156     /**
    157      * This tells the progress dialog to dismiss itself (with state loss) after guaranteeing to be
    158      * shown for the specified time in
    159      * {@link #show(FragmentManager, CharSequence, CharSequence, long)}.
    160      */
    161     @Override
    162     public void dismissAllowingStateLoss() {
    163         mAllowStateLoss = true;
    164         dismissWhenReady();
    165     }
    166 
    167     /**
    168      * Tells the progress dialog to dismiss itself after guaranteeing that the dialog had been
    169      * showing for at least the minimum display time as set in
    170      * {@link #show(FragmentManager, CharSequence, CharSequence, long)}.
    171      */
    172     private void dismissWhenReady() {
    173         // Compute how long the dialog has been showing
    174         final long shownTime = System.currentTimeMillis() - mShowTime;
    175         if (shownTime >= mMinDisplayTime) {
    176             // dismiss immediately
    177             mHandler.post(mDismisser);
    178         } else {
    179             // Need to wait some more, so compute the amount of time to sleep.
    180             final long sleepTime = mMinDisplayTime - shownTime;
    181             mHandler.postDelayed(mDismisser, sleepTime);
    182         }
    183     }
    184 
    185     /**
    186      * Actually dismiss the dialog fragment.
    187      */
    188     private void superDismiss() {
    189         mCalledSuperDismiss = true;
    190         if (mActivityReady) {
    191             // The fragment is either in onStart or past it, but has not gotten to onStop yet.
    192             // It is safe to dismiss this dialog fragment.
    193             if (mAllowStateLoss) {
    194                 super.dismissAllowingStateLoss();
    195             } else {
    196                 super.dismiss();
    197             }
    198         }
    199         // If mActivityReady is false, then this dialog fragment has already passed the onStop
    200         // state. This can happen if the user hit the 'home' button before this dialog fragment was
    201         // dismissed or if there is a configuration change.
    202         // In the event that this dialog fragment is re-attached and reaches onStart (e.g.,
    203         // because the user returns to this fragment's activity or the device configuration change
    204         // has re-attached this dialog fragment), because the mCalledSuperDismiss flag was set to
    205         // true, this dialog fragment will be dismissed within onStart.  So, there's nothing else
    206         // that needs to be done.
    207     }
    208 }
    209