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 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