/** * Copyright © 2011,2013 Konstantin Livitski * * This file is part of n-Puzzle application. n-Puzzle application is free * software; you can redistribute it and/or modify it under the terms of the GNU * General Public License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * n-Puzzle application contains adaptations of artwork covered by the Creative * Commons Attribution-ShareAlike 3.0 Unported license. Please refer to the * NOTICE.md file at the root of this distribution or repository for licensing * terms that apply to that artwork. * * n-Puzzle application is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License along with * n-Puzzle application; if not, see the LICENSE/gpl.txt file of this distribution * or visit <http://www.gnu.org/licenses>. */ package name.livitski.games.puzzle.android; import java.util.Formatter; import java.util.concurrent.Callable; import java.util.concurrent.Future; import android.app.AlertDialog; import android.app.AlertDialog.Builder; import android.app.Dialog; import android.app.ProgressDialog; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences.Editor; import android.content.res.Resources; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.widget.RelativeLayout; /** * Provides common methods to the application's activities. */ public abstract class Activity extends android.app.Activity { public Activity() { } public Activity(int contentViewResource) { this.contentView = contentViewResource; } public Activity(View contentView) { this.contentView = contentView; } @Override public void setContentView(int layoutResID) { if (null == contentView || !contentView.equals(layoutResID)) forceSetContentView(layoutResID); } @Override public void setContentView(View view, LayoutParams params) { ViewGroup frame = (ViewGroup)findViewById(R.id.viewgroup_app_frame); if (frame.getChildAt(frameViewCount) != null) frame.removeViewAt(frameViewCount); addContentToFrame(frame, view, params); } @Override public void setContentView(View view) { if (view != contentView) forceSetContentView(view); } @Override public void setTitle(CharSequence title) { Formatter formatter = new Formatter() .format(title.toString(), getResources().getString(R.string.app_name)); super.setTitle(formatter.toString()); } @Override public void setTitle(int titleId) { Resources resources = getResources(); super.setTitle(resources.getString(titleId, resources.getString(R.string.app_name))); } /** * Displays a progress dialog with a message. * @param message message to display * @param max end of the progress range * or <code>null</code> to show an indeterminate dialog * @return the handler to receive progress messages * from the worker thread * @throws IllegalStateException if there already is an * active progress dialog */ public Handler progress(String message, Integer max) { if (null != progressHandler) throw new IllegalStateException("Cannot show progress of operation \"" + message + "\" while another operation is in progress."); final ProgressDialog dialog = getProgressDialog(); dialog.setMessage(message); if (null == max) { dialog.setIndeterminate(true); dialog.setProgressStyle(ProgressDialog.STYLE_SPINNER); } else { dialog.setIndeterminate(false); dialog.setMax(max); dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); } progressHandler = new ProgressHandler(); showDialog(PROGRESS_DIALOG); return progressHandler; } /** * Displays dialog with a message and single confirmation button. */ public void alert(String message) { alert = message; showDialog(ALERT_DIALOG); } /** * Displays dialog with a message and yes/no buttons. */ public void choice(String message) { choice = message; showDialog(CHOICE_DIALOG); } @Override public void finish() { finished = true; super.finish(); } @Override public Application getApplicationContext() { return (Application)super.getApplication(); } /** * @return last {@link #onPause() paused} subclass, or * <code>null</code> if no subclass has yet been paused * @throws ClassNotFoundException if saved activity class is * no longer available or saved value was corrupted * @throws ClassCastException if saved activity class is * not a subclass of {@link Activity} * @throws RuntimeException if there is an error reading * preferences file */ @SuppressWarnings("unchecked") public Class<? extends Activity> getLastPausedActivity() throws ClassNotFoundException { String className = getSharedPreferences(SHARED_PREFS, MODE_PRIVATE) .getString(LAST_ACTIVITY, null); if (null == className) return null; Class<?> clazz = Class.forName(className); if (!Activity.class.isAssignableFrom(clazz)) throw new ClassCastException(className); return (Class<? extends Activity>)clazz; } protected Future<?> submitBackgroundTask(Runnable task) { return getApplicationContext().submitBackgroundTask(task); } protected <T> Future<T> submitBackgroundTask(Callable<T> task) { return getApplicationContext().submitBackgroundTask(task); } /** * Override to react to a standard dialog's cancellation. */ protected void onDialogCancel(int dialogId) { } /** * Override to react to user's input in a standard dialog. */ protected void onDialogResponse(int dialogId, int response) { } /** * Override to react to the completion of background task that * used a {@link #progress(String, Integer) progress dialog}. */ protected void onCompletion() { } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Application app = getApplicationContext(); app.onActivityCreate(this); finished = false; super.setContentView(R.layout.frame); ViewGroup frame = (ViewGroup)findViewById(R.id.viewgroup_app_frame); frameViewCount = frame.getChildCount(); if (contentView instanceof Integer) forceSetContentView((Integer)contentView); else if (contentView instanceof View) forceSetContentView((View)contentView); findViewById(R.id.text_app_footer_author).setOnClickListener(LISTENER_CLICK_AUTHOR); findViewById(R.id.text_app_footer_license).setOnClickListener(LISTENER_CLICK_LICENSE); } @Override protected void onDestroy() { super.onDestroy(); Application app = getApplicationContext(); app.onActivityDestroy(this); } @Override protected void onPause() { super.onPause(); try { Editor settings = getSharedPreferences(SHARED_PREFS, MODE_PRIVATE).edit(); if (finished) settings.remove(LAST_ACTIVITY); else settings.putString(LAST_ACTIVITY, this.getClass().getName()); settings.commit(); } catch (Exception e) { Log.w(LOG_TAG, "Could not update the " + LAST_ACTIVITY + " preference in file " + SHARED_PREFS, e); } } @Override protected Dialog onCreateDialog(final int id) { DialogInterface.OnClickListener onClickListener = new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { dismissDialog(id); onDialogResponse(id, which); } }; DialogInterface.OnCancelListener onCancelListener = new DialogInterface.OnCancelListener() { public void onCancel(DialogInterface dialog) { dismissDialog(id); onDialogCancel(id); } }; if (id == PROGRESS_DIALOG) { final ProgressDialog dialog = getProgressDialog(); dialog.setOnCancelListener(onCancelListener); return dialog; } Builder builder = new AlertDialog.Builder(this) .setTitle(R.string.app_name) .setMessage("") .setOnCancelListener(onCancelListener); switch (id) { case ALERT_DIALOG: builder.setNeutralButton(R.string.ok_button, onClickListener); break; case CHOICE_DIALOG: builder .setPositiveButton(R.string.yes, onClickListener) .setNegativeButton(R.string.no, onClickListener); break; default: return super.onCreateDialog(id); } return builder.create(); } protected ProgressDialog getProgressDialog() { if (null == progressDialog) { progressDialog = new ProgressDialog(this) { @Override protected void onStop() { super.onStop(); progressHandler = null; } }; progressDialog.setTitle(R.string.app_name); progressDialog.setMessage(""); } return progressDialog; } @Override protected void onPrepareDialog(int id, Dialog dialog) { switch (id) { case ALERT_DIALOG: ((AlertDialog)dialog).setMessage(alert); break; case CHOICE_DIALOG: ((AlertDialog)dialog).setMessage(choice); break; default: super.onPrepareDialog(id, dialog); } } protected static final int ALERT_DIALOG = Integer.MAX_VALUE - 10; protected static final int CHOICE_DIALOG = Integer.MAX_VALUE - 9; protected static final int PROGRESS_DIALOG = Integer.MAX_VALUE - 8; protected final OnClickListener LISTENER_CLICK_AUTHOR = new OnClickListener() { @Override public void onClick(View v) { Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(URL_AUTHOR)); startActivity(i); return; } }; protected final OnClickListener LISTENER_CLICK_LICENSE = new OnClickListener() { @Override public void onClick(View v) { Intent i = new Intent(Activity.this, LicensePages.class); startActivity(i); return; } }; protected class ProgressHandler extends Handler { @Override public void handleMessage(Message msg) { if (progressDialog.isIndeterminate() || progressDialog.getMax() <= msg.arg1) { onCompletion(); dismissDialog(PROGRESS_DIALOG); } else { if (msg.arg2 > 0) progressDialog.setSecondaryProgress(msg.arg2); if (msg.arg1 >= 0) progressDialog.setProgress(msg.arg1); } } } private void forceSetContentView(int layoutResID) { ViewGroup frame = (ViewGroup)findViewById(R.id.viewgroup_app_frame); if (frame.getChildAt(frameViewCount) != null) frame.removeViewAt(frameViewCount); View view = View.inflate(this, layoutResID, null); addContentToFrame(frame, view, null); } private void forceSetContentView(View view) { ViewGroup frame = (ViewGroup)findViewById(R.id.viewgroup_app_frame); if (frame.getChildAt(frameViewCount) != null) frame.removeViewAt(frameViewCount); if (null != view) addContentToFrame(frame, view, null); } private void addContentToFrame(ViewGroup frame, View view, LayoutParams params) { RelativeLayout.LayoutParams frameParams; if (null == params) frameParams = new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); else if (params instanceof RelativeLayout.LayoutParams) frameParams = (RelativeLayout.LayoutParams)params; else frameParams = new RelativeLayout.LayoutParams(params); frameParams.addRule(RelativeLayout.BELOW, R.id.text_app_title); frameParams.addRule(RelativeLayout.ABOVE, R.id.text_app_footer); frame.addView(view, frameViewCount, frameParams); } private static final String LOG_TAG = "Activity"; private static final String SHARED_PREFS = LOG_TAG + ".prefs"; private static final String LAST_ACTIVITY = "LastActivity"; private static final String URL_AUTHOR = "http://www.livitski.com"; private ProgressDialog progressDialog; private ProgressHandler progressHandler; private String alert; private String choice; private Object contentView; private boolean finished; private int frameViewCount; }