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
17package com.android.gallery3d.app;
18
19import android.app.Activity;
20import android.content.Intent;
21import android.content.res.Configuration;
22import android.os.Bundle;
23import android.os.Parcelable;
24import android.view.Menu;
25import android.view.MenuItem;
26
27import com.android.gallery3d.anim.StateTransitionAnimation;
28import com.android.gallery3d.common.Utils;
29import com.android.gallery3d.util.UsageStatistics;
30
31import java.util.Stack;
32
33public 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
47    public StateManager(AbstractGalleryActivity activity) {
48        mActivity = activity;
49    }
50
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
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
106    public boolean createOptionsMenu(Menu menu) {
107        if (mStack.isEmpty()) {
108            return false;
109        } else {
110            return getTopState().onCreateActionBar(menu);
111        }
112    }
113
114    public void onConfigurationChange(Configuration config) {
115        for (StateEntry entry : mStack) {
116            entry.activityState.onConfigurationChanged(config);
117        }
118    }
119
120    public void resume() {
121        if (mIsResumed) return;
122        mIsResumed = true;
123        if (!mStack.isEmpty()) getTopState().resume();
124    }
125
126    public void pause() {
127        if (!mIsResumed) return;
128        mIsResumed = false;
129        if (!mStack.isEmpty()) getTopState().onPause();
130    }
131
132    public void notifyActivityResult(int requestCode, int resultCode, Intent data) {
133        getTopState().onStateResult(requestCode, resultCode, data);
134    }
135
136    public void clearActivityResult() {
137        if (!mStack.isEmpty()) {
138            getTopState().clearStateResult();
139        }
140    }
141
142    public int getStateCount() {
143        return mStack.size();
144    }
145
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
159    public void onBackPressed() {
160        if (!mStack.isEmpty()) {
161            getTopState().onBackPressed();
162        }
163    }
164
165    void finishState(ActivityState state) {
166        finishState(state, true);
167    }
168
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
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
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
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")
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
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
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
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
333        public StateEntry(Bundle data, ActivityState state) {
334            this.data = data;
335            this.activityState = state;
336        }
337    }
338}
339