| /* |
| * Copyright (C) 2012 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.contacts.dialog; |
| |
| import android.app.Dialog; |
| import android.app.DialogFragment; |
| import android.app.FragmentManager; |
| import android.app.ProgressDialog; |
| import android.content.DialogInterface; |
| import android.os.Bundle; |
| import android.os.Handler; |
| |
| /** |
| * Indeterminate progress dialog wrapped up in a DialogFragment to work even when the device |
| * orientation is changed. Currently, only supports adding a title and/or message to the progress |
| * dialog. There is an additional parameter of the minimum amount of time to display the progress |
| * dialog even after a call to dismiss the dialog {@link #dismiss()} or |
| * {@link #dismissAllowingStateLoss()}. |
| * <p> |
| * To create and show the progress dialog, use |
| * {@link #show(FragmentManager, CharSequence, CharSequence, long)} and retain the reference to the |
| * IndeterminateProgressDialog instance. |
| * <p> |
| * To dismiss the dialog, use {@link #dismiss()} or {@link #dismissAllowingStateLoss()} on the |
| * instance. The instance returned by |
| * {@link #show(FragmentManager, CharSequence, CharSequence, long)} is guaranteed to be valid |
| * after a device orientation change because the {@link #setRetainInstance(boolean)} is called |
| * internally with true. |
| */ |
| public class IndeterminateProgressDialog extends DialogFragment { |
| private static final String TAG = IndeterminateProgressDialog.class.getSimpleName(); |
| |
| private CharSequence mTitle; |
| private CharSequence mMessage; |
| private long mMinDisplayTime; |
| private long mShowTime = 0; |
| private boolean mActivityReady = false; |
| private Dialog mOldDialog; |
| private final Handler mHandler = new Handler(); |
| private boolean mCalledSuperDismiss = false; |
| private boolean mAllowStateLoss; |
| private final Runnable mDismisser = new Runnable() { |
| @Override |
| public void run() { |
| superDismiss(); |
| } |
| }; |
| |
| /** |
| * Creates and shows an indeterminate progress dialog. Once the progress dialog is shown, it |
| * will be shown for at least the minDisplayTime (in milliseconds), so that the progress dialog |
| * does not flash in and out to quickly. |
| */ |
| public static IndeterminateProgressDialog show(FragmentManager fragmentManager, |
| CharSequence title, CharSequence message, long minDisplayTime) { |
| IndeterminateProgressDialog dialogFragment = new IndeterminateProgressDialog(); |
| dialogFragment.mTitle = title; |
| dialogFragment.mMessage = message; |
| dialogFragment.mMinDisplayTime = minDisplayTime; |
| dialogFragment.show(fragmentManager, TAG); |
| dialogFragment.mShowTime = System.currentTimeMillis(); |
| dialogFragment.setCancelable(false); |
| |
| return dialogFragment; |
| } |
| |
| @Override |
| public void onCreate(Bundle savedInstanceState) { |
| super.onCreate(savedInstanceState); |
| setRetainInstance(true); |
| } |
| |
| @Override |
| public Dialog onCreateDialog(Bundle savedInstanceState) { |
| // Create the progress dialog and set its properties |
| final ProgressDialog dialog = new ProgressDialog(getActivity()); |
| dialog.setIndeterminate(true); |
| dialog.setIndeterminateDrawable(null); |
| dialog.setTitle(mTitle); |
| dialog.setMessage(mMessage); |
| |
| return dialog; |
| } |
| |
| @Override |
| public void onStart() { |
| super.onStart(); |
| mActivityReady = true; |
| |
| // Check if superDismiss() had been called before. This can happen if in a long |
| // running operation, the user hits the home button and closes this fragment's activity. |
| // Upon returning, we want to dismiss this progress dialog fragment. |
| if (mCalledSuperDismiss) { |
| superDismiss(); |
| } |
| } |
| |
| @Override |
| public void onStop() { |
| super.onStop(); |
| mActivityReady = false; |
| } |
| |
| /** |
| * There is a race condition that is not handled properly by the DialogFragment class. |
| * If we don't check that this onDismiss callback isn't for the old progress dialog from before |
| * the device orientation change, then this will cause the newly created dialog after the |
| * orientation change to be dismissed immediately. |
| */ |
| @Override |
| public void onDismiss(DialogInterface dialog) { |
| if (mOldDialog != null && mOldDialog == dialog) { |
| // This is the callback from the old progress dialog that was already dismissed before |
| // the device orientation change, so just ignore it. |
| return; |
| } |
| super.onDismiss(dialog); |
| } |
| |
| /** |
| * Save the old dialog that is about to get destroyed in case this is due to a change |
| * in device orientation. This will allow us to intercept the callback to |
| * {@link #onDismiss(DialogInterface)} in case the callback happens after a new progress dialog |
| * instance was created. |
| */ |
| @Override |
| public void onDestroyView() { |
| mOldDialog = getDialog(); |
| super.onDestroyView(); |
| } |
| |
| /** |
| * This tells the progress dialog to dismiss itself after guaranteeing to be shown for the |
| * specified time in {@link #show(FragmentManager, CharSequence, CharSequence, long)}. |
| */ |
| @Override |
| public void dismiss() { |
| mAllowStateLoss = false; |
| dismissWhenReady(); |
| } |
| |
| /** |
| * This tells the progress dialog to dismiss itself (with state loss) after guaranteeing to be |
| * shown for the specified time in |
| * {@link #show(FragmentManager, CharSequence, CharSequence, long)}. |
| */ |
| @Override |
| public void dismissAllowingStateLoss() { |
| mAllowStateLoss = true; |
| dismissWhenReady(); |
| } |
| |
| /** |
| * Tells the progress dialog to dismiss itself after guaranteeing that the dialog had been |
| * showing for at least the minimum display time as set in |
| * {@link #show(FragmentManager, CharSequence, CharSequence, long)}. |
| */ |
| private void dismissWhenReady() { |
| // Compute how long the dialog has been showing |
| final long shownTime = System.currentTimeMillis() - mShowTime; |
| if (shownTime >= mMinDisplayTime) { |
| // dismiss immediately |
| mHandler.post(mDismisser); |
| } else { |
| // Need to wait some more, so compute the amount of time to sleep. |
| final long sleepTime = mMinDisplayTime - shownTime; |
| mHandler.postDelayed(mDismisser, sleepTime); |
| } |
| } |
| |
| /** |
| * Actually dismiss the dialog fragment. |
| */ |
| private void superDismiss() { |
| mCalledSuperDismiss = true; |
| if (mActivityReady) { |
| // The fragment is either in onStart or past it, but has not gotten to onStop yet. |
| // It is safe to dismiss this dialog fragment. |
| if (mAllowStateLoss) { |
| super.dismissAllowingStateLoss(); |
| } else { |
| super.dismiss(); |
| } |
| } |
| // If mActivityReady is false, then this dialog fragment has already passed the onStop |
| // state. This can happen if the user hit the 'home' button before this dialog fragment was |
| // dismissed or if there is a configuration change. |
| // In the event that this dialog fragment is re-attached and reaches onStart (e.g., |
| // because the user returns to this fragment's activity or the device configuration change |
| // has re-attached this dialog fragment), because the mCalledSuperDismiss flag was set to |
| // true, this dialog fragment will be dismissed within onStart. So, there's nothing else |
| // that needs to be done. |
| } |
| } |