package com.gettingmobile.goodnews;
import android.app.Dialog;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.database.sqlite.SQLiteDatabase;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.WindowManager;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.TextView;
import com.actionbarsherlock.app.ActionBar;
import com.actionbarsherlock.view.Menu;
import com.actionbarsherlock.view.MenuItem;
import com.gettingmobile.android.app.actions.*;
import com.gettingmobile.android.app.settings.AbstractSettings;
import com.gettingmobile.android.app.settings.OnSettingChangeListener;
import com.gettingmobile.goodnews.home.HomeActivity;
import com.gettingmobile.goodnews.settings.SettingsHandler;
import com.gettingmobile.goodnews.settings.SettingsIntentFactory;
import com.gettingmobile.goodnews.settings.ViewSettingsActivity;
import com.gettingmobile.goodnews.tip.TipGroupAction;
import com.gettingmobile.goodnews.tip.TipManager;
import com.gettingmobile.goodnews.util.ThemeUtil;
import com.gettingmobile.google.reader.*;
import com.github.rtyley.android.sherlock.roboguice.activity.RoboSherlockFragmentActivity;
import com.google.inject.Inject;
public abstract class Activity extends RoboSherlockFragmentActivity
implements ActionController<Integer>, ActionContext<Application>, View.OnClickListener, OnCheckedChangeListener {
public static final String EXTRA_KEY_BASE = "com.gettingmobile.goodnews.";
public static final String EXTRA_KEY_ELEMENT_ID = EXTRA_KEY_BASE + "ELEMENT_ID";
public static final String EXTRA_KEY_ELEMENT_KEY = EXTRA_KEY_BASE + "ELEMENT_KEY";
public static final String EXTRA_KEY_ELEMENT_TITLE = EXTRA_KEY_BASE + "ELEMENT_TITLE";
public static final String EXTRA_KEY_ELEMENT_IS_STATE_OR_LABEL = EXTRA_KEY_BASE + "ELEMENT_IS_STATE_OR_LABEL";
public static final String EXTRA_KEY_LAUNCHED_BY_SHORTCUT = EXTRA_KEY_BASE + "LAUNCHED_BY_SHORTCUT";
protected static final String LOG_TAG = "goodnews.Application";
private ActionContextActivityAccessor<Application, Activity> accessor = null;
protected static final int PANEL_CONTENT_LOADING = 0;
protected static final int PANEL_NO_CONTENT = 1;
protected static final int PANEL_CONTENT = 2;
private boolean recreated = false;
private ViewOperation<?> viewLoader = null;
private final OnViewOperationFinishedListener viewLoadedListener = new OnViewLoadedListener();
private ViewOperation<?> viewUpdater = null;
private final OnViewOperationFinishedListener viewUpdatedListener = new OnViewUpdatedListener();
private boolean windowSettingsChanged = false;
private boolean hasContent = false;
private View panelContentLoading = null;
private View panelNoContent = null;
private View panelContent = null;
private Menu optionsMenus = null;
private boolean firstResume = true;
private final BusyActionController<Integer> actionController = new BusyActionController<Integer>(this);
private final int tipGroupId;
private final OnSettingChangeListener windowSettingsChangeListener = new OnSettingChangeListener() {
@Override
public void onSettingChanged(AbstractSettings settings, SharedPreferences sharedPreferences, String s) {
onWindowSettingsChanged();
}
};
@Inject
protected SettingsIntentFactory settingsIntentFactory = null;
public Activity() {
this(TipManager.NO_TIPS);
}
public Activity(int tipGroupId) {
this.tipGroupId = tipGroupId;
}
public final void setViewLoader(ViewOperation<?> viewLoader) {
this.viewLoader = viewLoader;
}
public final void setViewUpdater(ViewOperation<?> viewUpdater) {
this.viewUpdater = viewUpdater;
}
public boolean hasContent() {
return hasContent;
}
@Override
public android.app.Activity getActivity() {
return accessor != null ? accessor.getActivity() : this;
}
@Override
public Application getApp() {
return (Application) getApplication();
}
public boolean isLargeScreen() {
final int size = getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK;
return size >= Configuration.SCREENLAYOUT_SIZE_LARGE;
}
public ThemeUtil getThemeUtil() {
return getApp().getThemeUtil();
}
protected SQLiteDatabase getDb() {
return getApp().getDbHelper().getDatabase();
}
protected SQLiteDatabase getReadOnlyDb() {
return getApp().getDbHelper().getReadOnlyDatabase();
}
public ActivityAccessor<Activity> getAccessor() {
return accessor;
}
protected boolean showActionBarIcon() {
return true;
}
protected boolean showActionBarIconAsUp() {
return true;
}
protected boolean isRecreated() {
return recreated;
}
/*
* life cycle management
*/
protected void onCreate() {
// nothing to be done by default
}
protected void onPostCreateRestoreSavedInstanceState(Bundle savedInstanceState) {
// nothing to be done by default
}
@Override
protected void onCreate(Bundle savedInstanceState) {
Log.d(LOG_TAG, "onCreate(" + savedInstanceState + ")");
super.onCreate(savedInstanceState);
initWindowSettings();
/*
* allow creation for sub classes
*/
onCreate();
/*
* initialize action bar
*/
getSupportActionBar().setDisplayOptions(
showActionBarIcon() || getIntentLaunchedByShortcut() ? ActionBar.DISPLAY_SHOW_HOME : 0,
ActionBar.DISPLAY_SHOW_HOME);
getSupportActionBar().setDisplayOptions(
showActionBarIconAsUp() ? ActionBar.DISPLAY_HOME_AS_UP : 0,
ActionBar.DISPLAY_HOME_AS_UP);
onRegisterActions();
if (savedInstanceState != null) {
onPostCreateRestoreSavedInstanceState(savedInstanceState);
}
panelContentLoading = findViewById(R.id.content_loading);
panelNoContent = findViewById(R.id.no_content);
if (panelNoContent != null) {
panelNoContent.setOnClickListener(this);
}
panelContent = findViewById(R.id.content);
/*
* handle configuration changes
*/
getApp().getSettings().registerChangeListener("theme", windowSettingsChangeListener);
getApp().getSettings().registerChangeListener("actionbar_unsplit", windowSettingsChangeListener);
final Object nonConfigurationInstance = getLastCustomNonConfigurationInstance();
if (nonConfigurationInstance != null) {
recreated = true;
handleNonConfigurationInstance(nonConfigurationInstance);
} else {
recreated = false;
accessor = new ActionContextActivityAccessor<Application, Activity>(this);
}
loadView();
}
protected boolean shouldUpdateOnResume() {
return true;
}
@Override
protected void onResume() {
if (windowSettingsChanged) {
applyWindowSettingsChanges();
}
invalidateOptionsMenu();
super.onResume();
if (firstResume) {
firstResume = false;
} else if (shouldUpdateOnResume()) {
updateView();
}
}
@Override
protected void onDestroy() {
getApp().getSettings().unregisterChangeListener("theme", windowSettingsChangeListener);
super.onDestroy();
}
/*
* theme handling
*/
protected void onWindowSettingsChanged() {
windowSettingsChanged = true;
}
protected void applyWindowSettingsChanges() {
final Intent intent = getIntent();
finish();
startActivity(intent);
}
private void initWindowSettings() {
switch (getApp().getSettings().getTheme()) {
case BLACK:
setTheme(R.style.Theme_GoodNews_Black);
break;
case DARK:
setTheme(R.style.Theme_GoodNews_Dark);
break;
case LIGHT:
setTheme(R.style.Theme_GoodNews_Light);
break;
default:
setTheme(R.style.Theme_GoodNews_White);
}
if (!getApp().getSettings().shouldUnsplitActionBar()) {
getSherlock().setUiOptions(ActivityInfo.UIOPTION_SPLIT_ACTION_BAR_WHEN_NARROW,
ActivityInfo.UIOPTION_SPLIT_ACTION_BAR_WHEN_NARROW);
}
}
/*
* action handling
*/
protected void onRegisterActions() {
if (tipGroupId != TipManager.NO_TIPS) {
registerAction(R.id.menu_tip, new TipGroupAction(tipGroupId, false));
}
}
@Override
public void registerAction(Integer key, Action<?> action) {
actionController.registerAction(key, action);
initAction(key, action);
}
@Override
public <A extends Action<?>> A registerAction(Integer key, Class<A> actionClass) {
final A action = actionController.registerAction(key, actionClass);
initAction(key, action);
return action;
}
@SuppressWarnings("unchecked")
private void initAction(final Integer key, Action<?> action) {
if (action instanceof BusyAction<?>) {
final BusyAction<?> ba = (BusyAction<?>) action;
ba.setListener(new BusyActionListener() {
@Override
public void onActionStarted(BusyAction<?> action) {
invalidateOptionsMenu();
}
@Override
public void onActionStopped(BusyAction<?> action) {
invalidateOptionsMenu();
}
});
markActionBusy(key, ba.getState((ActionContext) this) == BusyAction.BUSY);
}
}
@Override
public void unregisterAction(Integer key) {
actionController.unregisterAction(key);
}
public boolean fireAction(Integer key) {
return actionController.fireAction(key);
}
protected void markActionBusy(final Integer key, boolean busy) {
if (optionsMenus != null) {
final MenuItem item = optionsMenus.findItem(key);
if (item != null) {
if (busy) {
if (item.getActionView() == null) {
item.setActionView(R.layout.action_busy);
final Action<?> action = actionController.getAction(key);
if (action instanceof BusyAction) {
item.getActionView().setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
actionController.stopAction(key);
}
});
}
}
} else {
item.setActionView(null);
}
}
}
}
protected abstract int getOptionsMenuResourceId();
protected abstract Class<? extends SettingsHandler> getViewSettingsHandlerClass();
@Override
public boolean onCreateOptionsMenu(Menu menu) {
optionsMenus = menu;
getSupportMenuInflater().inflate(getOptionsMenuResourceId(), menu);
onInitOptionsMenu(menu);
return true;
}
protected void onInitOptionsMenu(Menu menu) {
menu.findItem(R.id.menu_view_preferences).setIntent(
ViewSettingsActivity.createIntent(this, getViewSettingsHandlerClass()));
menu.findItem(R.id.menu_preferences).setIntent(settingsIntentFactory.createStandardIntent());
}
@Override
@SuppressWarnings("unchecked")
public boolean onPrepareOptionsMenu(Menu menu) {
/*
* handle actions automatically
*/
for (int i = 0; i < menu.size(); ++i) {
final MenuItem item = menu.getItem(i);
final Action<?> action = actionController.getAction(item.getItemId());
if (action != null) {
switch (action.getState((ActionContext) this)) {
case Action.ENABLED:
item.setVisible(true);
item.setEnabled(true);
break;
case Action.DISABLED:
item.setVisible(true);
item.setEnabled(false);
break;
case Action.GONE:
item.setVisible(false);
item.setEnabled(false);
break;
case BusyAction.BUSY:
item.setVisible(true);
item.setEnabled(true);
default:
// no action -- do not touch
}
if (action instanceof BusyAction) {
markActionBusy(item.getItemId(),
(action.getState((ActionContext) this) == BusyAction.BUSY));
}
}
}
return super.onPrepareOptionsMenu(menu);
}
private void goHome() {
Intent intent = new Intent(this, HomeActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
}
protected void goBack() {
if (getIntentLaunchedByShortcut())
goHome();
else
finish();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (!fireAction(item.getItemId())) {
if (item.getItemId() == android.R.id.home) {
goBack();
} else {
return super.onOptionsItemSelected(item);
}
}
return true;
}
@Override
public void onClick(View view) {
fireAction(view.getId());
}
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
// do nothing by default
}
protected void onPreFetchData() {
// do nothing by default
}
public void loadView() {
final String title = getIntentElementTitle();
if (title != null) {
setTitle(getIntentElementTitle());
}
if (viewLoader != null) {
viewLoader.run(viewLoadedListener);
}
}
protected boolean onViewLoaded() {
showUnshownTipsIfApplicable();
return true;
}
public void updateView() {
if (viewUpdater != null) {
viewUpdater.run(viewUpdatedListener);
}
}
protected boolean onViewUpdated() {
showUnshownTipsIfApplicable();
return true;
}
public void showUnshownTipsIfApplicable() {
TipGroupAction.showTips(getApp(), this, getApp().getTipManager(), tipGroupId, true);
}
protected void onShowedContentLoadingPanel() {
// nothing by default
}
protected void onShowedNoContentPanel() {
// nothing by default
}
protected void onShowedContentPanel() {
// nothing by default
}
protected void showPanel(int panel) {
if (panelContentLoading != null) {
panelContentLoading.setVisibility(panel == PANEL_CONTENT_LOADING ? View.VISIBLE : View.GONE);
}
if (panelNoContent != null) {
panelNoContent.setVisibility(panel == PANEL_NO_CONTENT ? View.VISIBLE : View.GONE);
}
if (panelContent != null) {
panelContent.setVisibility(panel == PANEL_CONTENT ? View.VISIBLE : View.GONE);
}
/*
* callbacks
*/
switch (panel) {
case PANEL_CONTENT_LOADING:
onShowedContentLoadingPanel();
break;
case PANEL_NO_CONTENT:
onShowedNoContentPanel();
break;
case PANEL_CONTENT:
onShowedContentPanel();
break;
}
}
public static Intent createIntent(Class<? extends Activity> activityClass, Context context, Element element) {
final Intent intent = new Intent(context, activityClass);
intent.putExtra(EXTRA_KEY_ELEMENT_ID, element.getId().getId());
intent.putExtra(EXTRA_KEY_ELEMENT_KEY, element.getKey());
intent.putExtra(EXTRA_KEY_ELEMENT_TITLE, element.getTitle());
return intent;
}
private Intent createIntent(Class<? extends Activity> activityClass, Element element) {
return createIntent(activityClass, this, element);
}
protected void startActivity(Class<? extends Activity> activityClass, Element element) {
startActivity(createIntent(activityClass, element));
}
public static Intent createIntent(Class<? extends Activity> activityClass, Context context, Tag tag) {
final Intent intent = createIntent(activityClass, context, (Element) tag);
intent.putExtra(EXTRA_KEY_ELEMENT_IS_STATE_OR_LABEL,
!tag.isFeedFolder() && !ItemState.READING_LIST.getId().equals(tag.getId()));
return intent;
}
protected void startActivity(Class<? extends Activity> activityClass, Tag tag) {
startActivity(createIntent(activityClass, this, tag));
}
public ElementId getIntentElementId() {
return new ElementId(getIntent().getExtras().getString(EXTRA_KEY_ELEMENT_ID));
}
protected long getIntentElementKey() {
return getIntent().getExtras().getLong(EXTRA_KEY_ELEMENT_KEY, -1);
}
protected void setIntentElementKey(long elementKey) {
final Bundle extras = getIntent().getExtras();
if (extras != null) {
extras.putLong(EXTRA_KEY_ELEMENT_KEY, elementKey);
}
}
public String getIntentElementTitle() {
final Bundle extras = getIntent().getExtras();
return extras != null ? extras.getString(EXTRA_KEY_ELEMENT_TITLE) : null;
}
public boolean getIntentElementIsStateOrLabel() {
return getIntent().getExtras().getBoolean(EXTRA_KEY_ELEMENT_IS_STATE_OR_LABEL, false);
}
public boolean getIntentElementIsFolder() {
return getIntentElementId().getType() == ElementType.LABEL && !getIntentElementIsStateOrLabel();
}
public boolean getIntentLaunchedByShortcut() {
final Bundle extras = getIntent().getExtras();
return extras != null && extras.getBoolean(EXTRA_KEY_LAUNCHED_BY_SHORTCUT, false);
}
/*
* dialog handling
*/
protected void showDialogOrIgnore(int id) {
try {
showDialog(id);
} catch (WindowManager.BadTokenException ex) {
Log.e(LOG_TAG, "Failed to show dialog " + id + " due to invalid window token", ex);
}
}
@Override
protected Dialog onCreateDialog(int id) {
final Dialog dlg = accessor.onCreateDialog(id);
return dlg != null ? dlg : super.onCreateDialog(id);
}
@Override
public void showWaitDialog() {
try {
accessor.showWaitDialog();
} catch (WindowManager.BadTokenException ex) {
Log.i(LOG_TAG, "Failed to show wait dialog due to invalid window.");
}
}
@Override
public void dismissWaitDialog() {
accessor.dismissWaitDialog();
}
protected void setNoContentMsg(int msgId) {
final TextView noContentMsgView = (TextView) findViewById(R.id.no_content_msg);
noContentMsgView.setText(msgId);
}
/*
* handle configuration change
*/
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
}
@Override
public Object onRetainCustomNonConfigurationInstance() {
return this;
}
protected void handleNonConfigurationInstance(Object instance) {
final Activity prevInstance = (Activity) instance;
accessor = prevInstance.accessor;
accessor.setActivity(this);
}
/*
* inner classes
*/
public static interface OnViewOperationFinishedListener {
boolean onViewOperationFinished();
}
public abstract class ViewOperation<Result> {
public void run(OnViewOperationFinishedListener listener) {
//noinspection unchecked
new ViewOperationTask<Result>(this, listener).execute();
}
protected void onPreExecute() {
Log.d(getClass().getName(), "loading/updating content");
showPanel(PANEL_CONTENT_LOADING);
}
@SuppressWarnings("UnusedParameters")
protected Result doInBackground(Boolean... ignore) {
final SQLiteDatabase readOnlyDb = getReadOnlyDb();
onPreFetchData();
return fetchData(readOnlyDb);
}
protected void onPostExecute(Result result, OnViewOperationFinishedListener listener) {
Log.d(getClass().getName(), "loading/updating done");
hasContent = updateView(result);
boolean updatePanel = true;
if (listener != null) {
updatePanel = listener.onViewOperationFinished();
}
if (updatePanel) {
showPanel(hasContent ? PANEL_CONTENT : PANEL_NO_CONTENT);
}
}
protected void onPreFetchData() {
Activity.this.onPreFetchData();
}
protected abstract Result fetchData(SQLiteDatabase readOnlyDb);
protected abstract boolean updateView(Result result);
}
class ViewOperationTask<Result> extends AsyncTask<Boolean, Integer, Result> {
private final ViewOperation<Result> op;
private final OnViewOperationFinishedListener listener;
public ViewOperationTask(ViewOperation<Result> op, OnViewOperationFinishedListener listener) {
this.op = op;
this.listener = listener;
}
@Override
protected void onPreExecute() {
op.onPreExecute();
}
@Override
protected Result doInBackground(Boolean... params) {
return op.doInBackground();
}
@Override
protected void onPostExecute(Result result) {
op.onPostExecute(result, listener);
}
}
class OnViewLoadedListener implements OnViewOperationFinishedListener {
@Override
public boolean onViewOperationFinished() {
return onViewLoaded();
}
}
class OnViewUpdatedListener implements OnViewOperationFinishedListener {
@Override
public boolean onViewOperationFinished() {
return onViewUpdated();
}
}
class LongOperationTask<Param, Result> extends AsyncTask<Param, Integer, Result> {
private final LongOperation<Param, Result> op;
public LongOperationTask(LongOperation<Param, Result> op) {
this.op = op;
}
@Override
protected void onPreExecute() {
op.onPreExecute();
}
@Override
protected Result doInBackground(Param... params) {
return op.run(params);
}
@Override
protected void onPostExecute(Result result) {
op.onPostExecute(result);
}
}
public abstract class LongOperation<Param, Result> {
protected boolean isAsynchronous() {
return true;
}
public void execute(Param... params) {
if (isAsynchronous()) {
new LongOperationTask<Param, Result>(this).execute(params);
} else {
onPreExecute();
final Result result = run(params);
onPostExecute(result);
}
}
protected abstract Result run(Param... params);
protected void onPreExecute() {
if (isAsynchronous()) {
showWaitDialog();
}
}
@SuppressWarnings("UnusedParameters")
protected void onPostExecute(Result result) {
if (isAsynchronous()) {
dismissWaitDialog();
}
}
}
public abstract class LongDbOperation<Param, Result> extends LongOperation<Param, Result> {
@Override
protected Result run(Param... params) {
return run(getDb(), params);
}
protected abstract Result run(SQLiteDatabase db, Param... params);
}
}