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