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.camera.CameraActivity; 28 import com.android.gallery3d.anim.StateTransitionAnimation; 29 import com.android.gallery3d.common.Utils; 30 import com.android.gallery3d.util.UsageStatistics; 31 32 import java.util.Stack; 33 34 public 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