1 /*
2  * Copyright (C) 2010 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.gallery3d.app;
18 
19 import android.app.Activity;
20 import android.content.Intent;
21 import android.content.res.Configuration;
22 import android.os.Bundle;
23 import android.os.Parcelable;
24 import android.view.Menu;
25 import android.view.MenuItem;
26 
27 import com.android.gallery3d.anim.StateTransitionAnimation;
28 import com.android.gallery3d.common.Utils;
29 import com.android.gallery3d.util.UsageStatistics;
30 
31 import java.util.Stack;
32 
33 public class StateManager {
34     @SuppressWarnings("unused")
35     private static final String TAG = "StateManager";
36     private boolean mIsResumed = false;
37 
38     private static final String KEY_MAIN = "activity-state";
39     private static final String KEY_DATA = "data";
40     private static final String KEY_STATE = "bundle";
41     private static final String KEY_CLASS = "class";
42 
43     private AbstractGalleryActivity mActivity;
44     private Stack<StateEntry> mStack = new Stack<StateEntry>();
45     private ActivityState.ResultEntry mResult;
46 
StateManager(AbstractGalleryActivity activity)47     public StateManager(AbstractGalleryActivity activity) {
48         mActivity = activity;
49     }
50 
startState(Class<? extends ActivityState> klass, Bundle data)51     public void startState(Class<? extends ActivityState> klass,
52             Bundle data) {
53         Log.v(TAG, "startState " + klass);
54         ActivityState state = null;
55         try {
56             state = klass.newInstance();
57         } catch (Exception e) {
58             throw new AssertionError(e);
59         }
60         if (!mStack.isEmpty()) {
61             ActivityState top = getTopState();
62             top.transitionOnNextPause(top.getClass(), klass,
63                     StateTransitionAnimation.Transition.Incoming);
64             if (mIsResumed) top.onPause();
65         }
66 
67         UsageStatistics.onContentViewChanged(
68                 UsageStatistics.COMPONENT_GALLERY,
69                 klass.getSimpleName());
70         state.initialize(mActivity, data);
71 
72         mStack.push(new StateEntry(data, state));
73         state.onCreate(data, null);
74         if (mIsResumed) state.resume();
75     }
76 
startStateForResult(Class<? extends ActivityState> klass, int requestCode, Bundle data)77     public void startStateForResult(Class<? extends ActivityState> klass,
78             int requestCode, Bundle data) {
79         Log.v(TAG, "startStateForResult " + klass + ", " + requestCode);
80         ActivityState state = null;
81         try {
82             state = klass.newInstance();
83         } catch (Exception e) {
84             throw new AssertionError(e);
85         }
86         state.initialize(mActivity, data);
87         state.mResult = new ActivityState.ResultEntry();
88         state.mResult.requestCode = requestCode;
89 
90         if (!mStack.isEmpty()) {
91             ActivityState as = getTopState();
92             as.transitionOnNextPause(as.getClass(), klass,
93                     StateTransitionAnimation.Transition.Incoming);
94             as.mReceivedResults = state.mResult;
95             if (mIsResumed) as.onPause();
96         } else {
97             mResult = state.mResult;
98         }
99         UsageStatistics.onContentViewChanged(UsageStatistics.COMPONENT_GALLERY,
100                 klass.getSimpleName());
101         mStack.push(new StateEntry(data, state));
102         state.onCreate(data, null);
103         if (mIsResumed) state.resume();
104     }
105 
createOptionsMenu(Menu menu)106     public boolean createOptionsMenu(Menu menu) {
107         if (mStack.isEmpty()) {
108             return false;
109         } else {
110             return getTopState().onCreateActionBar(menu);
111         }
112     }
113 
onConfigurationChange(Configuration config)114     public void onConfigurationChange(Configuration config) {
115         for (StateEntry entry : mStack) {
116             entry.activityState.onConfigurationChanged(config);
117         }
118     }
119 
resume()120     public void resume() {
121         if (mIsResumed) return;
122         mIsResumed = true;
123         if (!mStack.isEmpty()) getTopState().resume();
124     }
125 
pause()126     public void pause() {
127         if (!mIsResumed) return;
128         mIsResumed = false;
129         if (!mStack.isEmpty()) getTopState().onPause();
130     }
131 
notifyActivityResult(int requestCode, int resultCode, Intent data)132     public void notifyActivityResult(int requestCode, int resultCode, Intent data) {
133         getTopState().onStateResult(requestCode, resultCode, data);
134     }
135 
clearActivityResult()136     public void clearActivityResult() {
137         if (!mStack.isEmpty()) {
138             getTopState().clearStateResult();
139         }
140     }
141 
getStateCount()142     public int getStateCount() {
143         return mStack.size();
144     }
145 
itemSelected(MenuItem item)146     public boolean itemSelected(MenuItem item) {
147         if (!mStack.isEmpty()) {
148             if (getTopState().onItemSelected(item)) return true;
149             if (item.getItemId() == android.R.id.home) {
150                 if (mStack.size() > 1) {
151                     getTopState().onBackPressed();
152                 }
153                 return true;
154             }
155         }
156         return false;
157     }
158 
onBackPressed()159     public void onBackPressed() {
160         if (!mStack.isEmpty()) {
161             getTopState().onBackPressed();
162         }
163     }
164 
finishState(ActivityState state)165     void finishState(ActivityState state) {
166         finishState(state, true);
167     }
168 
clearTasks()169     public void clearTasks() {
170         // Remove all the states that are on top of the bottom PhotoPage state
171         while (mStack.size() > 1) {
172             mStack.pop().activityState.onDestroy();
173         }
174     }
175 
finishState(ActivityState state, boolean fireOnPause)176     void finishState(ActivityState state, boolean fireOnPause) {
177         // The finish() request could be rejected (only happens under Monkey),
178         // If it is rejected, we won't close the last page.
179         if (mStack.size() == 1) {
180             Activity activity = (Activity) mActivity.getAndroidContext();
181             if (mResult != null) {
182                 activity.setResult(mResult.resultCode, mResult.resultData);
183             }
184             activity.finish();
185             if (!activity.isFinishing()) {
186                 Log.w(TAG, "finish is rejected, keep the last state");
187                 return;
188             }
189             Log.v(TAG, "no more state, finish activity");
190         }
191 
192         Log.v(TAG, "finishState " + state);
193         if (state != mStack.peek().activityState) {
194             if (state.isDestroyed()) {
195                 Log.d(TAG, "The state is already destroyed");
196                 return;
197             } else {
198                 throw new IllegalArgumentException("The stateview to be finished"
199                         + " is not at the top of the stack: " + state + ", "
200                         + mStack.peek().activityState);
201             }
202         }
203 
204         // Remove the top state.
205         mStack.pop();
206         state.mIsFinishing = true;
207         ActivityState top = !mStack.isEmpty() ? mStack.peek().activityState : null;
208         if (mIsResumed && fireOnPause) {
209             if (top != null) {
210                 state.transitionOnNextPause(state.getClass(), top.getClass(),
211                         StateTransitionAnimation.Transition.Outgoing);
212             }
213             state.onPause();
214         }
215         mActivity.getGLRoot().setContentPane(null);
216         state.onDestroy();
217 
218         if (top != null && mIsResumed) top.resume();
219         if (top != null) {
220             UsageStatistics.onContentViewChanged(UsageStatistics.COMPONENT_GALLERY,
221                     top.getClass().getSimpleName());
222         }
223     }
224 
switchState(ActivityState oldState, Class<? extends ActivityState> klass, Bundle data)225     public void switchState(ActivityState oldState,
226             Class<? extends ActivityState> klass, Bundle data) {
227         Log.v(TAG, "switchState " + oldState + ", " + klass);
228         if (oldState != mStack.peek().activityState) {
229             throw new IllegalArgumentException("The stateview to be finished"
230                     + " is not at the top of the stack: " + oldState + ", "
231                     + mStack.peek().activityState);
232         }
233         // Remove the top state.
234         mStack.pop();
235         if (!data.containsKey(PhotoPage.KEY_APP_BRIDGE)) {
236             // Do not do the fade out stuff when we are switching camera modes
237             oldState.transitionOnNextPause(oldState.getClass(), klass,
238                     StateTransitionAnimation.Transition.Incoming);
239         }
240         if (mIsResumed) oldState.onPause();
241         oldState.onDestroy();
242 
243         // Create new state.
244         ActivityState state = null;
245         try {
246             state = klass.newInstance();
247         } catch (Exception e) {
248             throw new AssertionError(e);
249         }
250         state.initialize(mActivity, data);
251         mStack.push(new StateEntry(data, state));
252         state.onCreate(data, null);
253         if (mIsResumed) state.resume();
254         UsageStatistics.onContentViewChanged(UsageStatistics.COMPONENT_GALLERY,
255                 klass.getSimpleName());
256     }
257 
destroy()258     public void destroy() {
259         Log.v(TAG, "destroy");
260         while (!mStack.isEmpty()) {
261             mStack.pop().activityState.onDestroy();
262         }
263         mStack.clear();
264     }
265 
266     @SuppressWarnings("unchecked")
restoreFromState(Bundle inState)267     public void restoreFromState(Bundle inState) {
268         Log.v(TAG, "restoreFromState");
269         Parcelable list[] = inState.getParcelableArray(KEY_MAIN);
270         ActivityState topState = null;
271         for (Parcelable parcelable : list) {
272             Bundle bundle = (Bundle) parcelable;
273             Class<? extends ActivityState> klass =
274                     (Class<? extends ActivityState>) bundle.getSerializable(KEY_CLASS);
275 
276             Bundle data = bundle.getBundle(KEY_DATA);
277             Bundle state = bundle.getBundle(KEY_STATE);
278 
279             ActivityState activityState;
280             try {
281                 Log.v(TAG, "restoreFromState " + klass);
282                 activityState = klass.newInstance();
283             } catch (Exception e) {
284                 throw new AssertionError(e);
285             }
286             activityState.initialize(mActivity, data);
287             activityState.onCreate(data, state);
288             mStack.push(new StateEntry(data, activityState));
289             topState = activityState;
290         }
291         if (topState != null) {
292             UsageStatistics.onContentViewChanged(UsageStatistics.COMPONENT_GALLERY,
293                     topState.getClass().getSimpleName());
294         }
295     }
296 
saveState(Bundle outState)297     public void saveState(Bundle outState) {
298         Log.v(TAG, "saveState");
299 
300         Parcelable list[] = new Parcelable[mStack.size()];
301         int i = 0;
302         for (StateEntry entry : mStack) {
303             Bundle bundle = new Bundle();
304             bundle.putSerializable(KEY_CLASS, entry.activityState.getClass());
305             bundle.putBundle(KEY_DATA, entry.data);
306             Bundle state = new Bundle();
307             entry.activityState.onSaveState(state);
308             bundle.putBundle(KEY_STATE, state);
309             Log.v(TAG, "saveState " + entry.activityState.getClass());
310             list[i++] = bundle;
311         }
312         outState.putParcelableArray(KEY_MAIN, list);
313     }
314 
hasStateClass(Class<? extends ActivityState> klass)315     public boolean hasStateClass(Class<? extends ActivityState> klass) {
316         for (StateEntry entry : mStack) {
317             if (klass.isInstance(entry.activityState)) {
318                 return true;
319             }
320         }
321         return false;
322     }
323 
getTopState()324     public ActivityState getTopState() {
325         Utils.assertTrue(!mStack.isEmpty());
326         return mStack.peek().activityState;
327     }
328 
329     private static class StateEntry {
330         public Bundle data;
331         public ActivityState activityState;
332 
StateEntry(Bundle data, ActivityState state)333         public StateEntry(Bundle data, ActivityState state) {
334             this.data = data;
335             this.activityState = state;
336         }
337     }
338 }
339