/*
* Copyright (C) 2011 The original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package com.zapta.apps.maniana.main;
import javax.annotation.Nullable;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.Window;
import com.zapta.apps.maniana.controller.MainActivityStartupKind;
import com.zapta.apps.maniana.model.PageKind;
import com.zapta.apps.maniana.persistence.ModelPersistence;
import com.zapta.apps.maniana.persistence.ModelReadingResult;
import com.zapta.apps.maniana.settings.Font;
import com.zapta.apps.maniana.util.LogUtil;
/**
* The main activity of this app.
*
* @author Tal Dayan
*/
public class MainActivity extends Activity {
/** A light weight snapshot of the activity state transfered across orientation change. */
private static class RetainedState {
public final PageKind currentPageKind;
private RetainedState(PageKind currentPageKind) {
this.currentPageKind = currentPageKind;
}
}
private MainActivityState mState;
@Nullable
private RetainedState mRetainedState;
/** Used to pass resume action from onNewIntent() to onResume(). */
private MainActivityResumeAction mResumeAction = MainActivityResumeAction.NONE;
/** Contains the intent that triggered mResumeAction. Null FF action = NONE. */
@Nullable
private Intent mResumeIntent = null;
/** Called by the Android framework to initialize the activity. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// TODO: This is a hack. Move an an actual config change listener in Application class.
Font.onConfigChanged();
mRetainedState = (RetainedState) getLastNonConfigurationInstance();
this.requestWindowFeature(Window.FEATURE_NO_TITLE);
// App context Ties all the app pieces together.
mState = new MainActivityState(this);
// Load model from file
final ModelReadingResult modelLoadResult = ModelPersistence.readModelFile(mState.context(),
mState.model());
final MainActivityStartupKind startupKind;
switch (modelLoadResult.outcome) {
case FILE_READ_OK: {
final int oldVersionCode = modelLoadResult.metadata.writerVersionCode;
final int newVersionCode = mState.services().getAppVersionCode();
final boolean isSameVersion = (oldVersionCode == newVersionCode);
final boolean isSilentUpgrade = isSilentUpgrade(oldVersionCode, newVersionCode);
startupKind = isSameVersion ? MainActivityStartupKind.NORMAL
: (isSilentUpgrade ? MainActivityStartupKind.NEW_VERSION_SILENT
: MainActivityStartupKind.NEW_VERSION_ANNOUNCE);
break;
}
case FILE_NOT_FOUND: {
// Model should be empty here
startupKind = MainActivityStartupKind.NEW_USER;
// Prevent moving item from Tomorow to Today.
mState.model().setLastPushDateStamp(mState.dateTracker().getDateStampString());
break;
}
default:
LogUtil.error("Unknown model loading outcome: " + modelLoadResult.outcome);
// Falling through intentionally.
case FILE_HAS_ERRORS:
mState.model().clear();
mState.model().setLastPushDateStamp(mState.dateTracker().getDateStampString());
startupKind = MainActivityStartupKind.MODEL_DATA_ERROR;
}
// Inform the view about the model data change
mState.view().updatePages();
// Set top view of this activity
setContentView(mState.view().getRootView());
// Track resume action from the launch intent
trackResumeAction(getIntent());
// Tell the controller the app was just created.
mState.controller().onMainActivityCreated(startupKind);
}
/** Is this a minor upgrade that should supress the startup message? */
private static final boolean isSilentUpgrade(int oldVersionCode, int newVersionCode) {
// Return here true if combination of old and new version code does not warrant
// bothering some users with the What's New popup.
// By default, upgrdes are not silent.
return false;
}
/** Called by the framework when the activity is destroyed. */
@Override
public void onDestroy() {
super.onDestroy();
// Tell the controller the app is being destroyed.
mState.controller().onMainActivityDestroy();
// Make sure we release the preferences listener.
mState.prefTracker().release();
}
/** Called by the framework when this activity is paused. */
@Override
protected void onPause() {
super.onPause();
mResumeIntent = null;
mResumeAction = MainActivityResumeAction.NONE;
// Inform the controller.
mState.controller().onMainActivityPause();
}
/** Called by the framework when this activity is resumed. */
@Override
protected void onResume() {
super.onResume();
// Get the action for this resume
final Intent thisResumeIntent = mResumeIntent;
final MainActivityResumeAction thisResumeAction;
// On ICS and above, orientation change result in the activity destroyed and recreated.
// In this case, use the retained state to preserve the original page.
if (mRetainedState != null) {
thisResumeAction = mRetainedState.currentPageKind.isTomorrow() ? MainActivityResumeAction.FORCE_TOMORROW_PAGE
: MainActivityResumeAction.FORCE_TODAY_PAGE;
} else {
thisResumeAction = mResumeAction;
}
mResumeIntent = null;
mResumeAction = MainActivityResumeAction.NONE;
mRetainedState = null;
// Inform the controller
mState.controller().onMainActivityResume(thisResumeAction, thisResumeIntent);
}
@Override
@Nullable
public Object onRetainNonConfigurationInstance() {
// NOTE: Gingerbread handles orientation changes well, including preserving popups, as is.
// Later versions of Android restart the activity on orientation change and require explicit
// state retention.
return (android.os.Build.VERSION.SDK_INT < 13) ? null : new RetainedState(mState.view()
.getCurrentPageKind());
}
// NOTE: this is not called when a popup menu is actie.
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
boolean eventHandled = false;
if (event.getRepeatCount() == 0) {
switch (keyCode) {
case KeyEvent.KEYCODE_BACK:
eventHandled = mState.controller().onBackButton();
break;
case KeyEvent.KEYCODE_MENU:
eventHandled = mState.controller().onMenuButton();
break;
}
}
return eventHandled || super.onKeyDown(keyCode, event);
}
/** Delegates sub sctivities result to the controller. */
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
mState.controller().onActivityResult(requestCode, resultCode, intent);
}
/**
* Called when the activity receives an intent. Used to detect launches from list widget action
* buttons.
*/
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
trackResumeAction(intent);
}
/** Update the resume action from the given launch intent. */
private final void trackResumeAction(Intent launchIntent) {
mResumeIntent = launchIntent;
mResumeAction = MainActivityResumeAction.fromIntent(mState, launchIntent);
}
}