blob: 2fe059f4ae95cc847d2cf7648f7f9f502911b2cf [file] [log] [blame]
Chiao Chenga6b4c792012-10-31 15:18:29 -07001/*
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
17package com.android.contacts.common.dialog;
18
19import android.app.Dialog;
Brian Attwellc2e912c2014-10-27 12:29:44 -070020import android.app.DialogFragment;
Chiao Chenga6b4c792012-10-31 15:18:29 -070021import android.app.FragmentManager;
22import android.app.ProgressDialog;
23import android.content.DialogInterface;
24import android.os.Bundle;
25import 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 */
Brian Attwellc2e912c2014-10-27 12:29:44 -070044public class IndeterminateProgressDialog extends DialogFragment {
Chiao Chenga6b4c792012-10-31 15:18:29 -070045 private static final String TAG = IndeterminateProgressDialog.class.getSimpleName();
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}