/*
* Copyright (C) 2011 Virginia Tech Department of Computer Science
*
* 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 sofia.app;
import sofia.app.internal.PersistenceManager;
import sofia.app.internal.ScreenMixin;
import sofia.app.internal.SofiaLayoutInflater;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
// -------------------------------------------------------------------------
/**
* The {@code Screen} class represents a single screen in an Android
* application. A {@code Screen} is a subclass of Android's notion of an
* {@link Activity}, which manages a user interface and acts as a "controller"
* for the events that occur in that GUI.
*
* @author Tony Allevato
*/
public abstract class Screen
extends Activity
implements ScreenMethods
{
//~ Fields ................................................................
private ScreenMixin mixin;
private SofiaLayoutInflater layoutInflater;
//~ Constructors ..........................................................
// ----------------------------------------------------------
/**
* Do not directly create instances of the {@code Screen} class; They are
* created for you by the operating system.
*/
public Screen()
{
mixin = new ScreenMixin(this);
}
//~ Methods ..............................................................
// ----------------------------------------------------------
/**
* Called before {@code initialize()} during the screen creation process.
* Most users typically will not need to override this method; it is
* intended for Sofia's own subclasses of {@link Screen} so that they can
* provide additional functionality before {@code initialize()}, and then
* users can override {@code initialize()} without being required to call
* the superclass implementation.
*/
protected void beforeInitialize()
{
// Do nothing.
}
// ----------------------------------------------------------
/**
* Called when the screen has been created and is about to be displayed on
* the device.
*/
public void initialize()
{
// Do nothing.
}
// ----------------------------------------------------------
/**
* Called once the screen has been created and made visible. Most users
* will not need to override this method; it is provided so that Sofia's
* own {@code Screen} subclasses can do additional initialization after the
* user's own {@code initialize()} method has executed, if necessary.
*/
protected void afterInitialize()
{
// Do nothing.
}
// ----------------------------------------------------------
/**
* <p>
* Determines whether the user's {@link #initialize()} method should be
* postponed until layout of the views has already occurred.
* </p><p>
* In most cases, the default behavior of false is preferred, which will
* cause {@link #initialize()} to be called during the activity's
* {@link #onCreate(Bundle)} method, where the view hierarchy of the
* activity can be created. But some specialized subclasses of
* {@code Screen}, like {@link ShapeScreen}, postpone the call to
* {@code initialize()} until later, after its {@code ShapeView} has
* already been laid out, so that users can access the width and height of
* the view in order lay out shapes using that information in calculations.
* </p>
*
* @return true if {@link #initialize()} should be postponed until the
* screen's views are already laid out, or false to call it immediately
* in {@link #onCreate(Bundle)}
*/
protected boolean doInitializeAfterLayout()
{
return false;
}
// ----------------------------------------------------------
@Override
public LayoutInflater getLayoutInflater()
{
return (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
}
// ----------------------------------------------------------
/**
* This method is overridden to replace the default Android layout inflater
* with one that supports Sofia's enhancements.
*/
@Override
public Object getSystemService(String service)
{
if (LAYOUT_INFLATER_SERVICE.equals(service))
{
if (layoutInflater == null)
{
LayoutInflater inflater =
(LayoutInflater) super.getSystemService(service);
layoutInflater = new SofiaLayoutInflater(inflater, this, this);
}
return layoutInflater;
}
else
{
return super.getSystemService(service);
}
}
// ----------------------------------------------------------
/**
* This method is called after an attempted was made to inflate the
* screen's layout. Most users will not need to call or override this
* method; it is provided for Sofia's own subclasses of {@code Screen} to
* support custom behavior depending on whether a user layout was provided
* or not.
*
* @return true if a layout was found and inflated, otherwise false
*/
protected void afterLayoutInflated(boolean inflated)
{
// Do nothing.
}
// ----------------------------------------------------------
/**
* Called when the activity is created.
*
* @param savedInstanceState instance data previously saved by this
* activity
*/
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
mixin.restoreInstanceState(savedInstanceState);
// Grab the input arguments, if there were any.
final Object[] args = mixin.getScreenArguments(getIntent());
beforeInitialize();
afterLayoutInflated(mixin.tryToInflateLayout());
if (!doInitializeAfterLayout())
{
mixin.invokeInitialize(args);
}
// Post a call to afterInitialize() in the message queue so that it
// gets called as soon as possible after the screen has been made
// visible.
getWindow().getDecorView().post(new Runnable()
{
public void run()
{
if (doInitializeAfterLayout())
{
mixin.invokeInitialize(args);
}
afterInitialize();
}
});
}
// ----------------------------------------------------------
@Override
protected void onStop()
{
PersistenceManager.getInstance().savePersistentContext(this);
super.onStop();
}
// ----------------------------------------------------------
@Override
protected void onResume()
{
super.onResume();
mixin.runResumeInjections();
}
// ----------------------------------------------------------
@Override
protected void onPause()
{
mixin.runPauseInjections();
super.onPause();
}
// ----------------------------------------------------------
@Override
protected void onDestroy()
{
mixin.runDestroyInjections();
super.onDestroy();
}
// ----------------------------------------------------------
@Override
protected void onSaveInstanceState(Bundle bundle)
{
mixin.saveInstanceState(bundle);
super.onSaveInstanceState(bundle);
}
// ----------------------------------------------------------
@Override
public boolean onCreateOptionsMenu(Menu menu)
{
return mixin.onCreateOptionsMenu(menu)
|| super.onCreateOptionsMenu(menu);
}
// ----------------------------------------------------------
@Override
public boolean onMenuItemSelected(int featureId, MenuItem item)
{
return mixin.onMenuItemSelected(featureId, item)
|| super.onMenuItemSelected(featureId, item);
}
// ----------------------------------------------------------
/**
* Not intended to be called by users; this method is public as an
* implementation detail.
*/
public ScreenMixin getScreenMixin()
{
return mixin;
}
// ----------------------------------------------------------
/**
* Prints an informational message to the system log, tagged with the
* "User Log" tag so that it can be easily identified in the LogCat view.
*
* @param message the message to log
*/
public void log(String message)
{
Log.i("User Log", message);
}
// ----------------------------------------------------------
/**
* Displays an alert dialog and waits for the user to dismiss it.
*
* @param title the title to display in the dialog
* @param message the message to display in the dialog
*/
public void showAlertDialog(String title, String message)
{
mixin.showAlertDialog(title, message);
}
// ----------------------------------------------------------
/**
* Displays a confirmation dialog and waits for the user to select an
* option.
*
* @param title the title to display in the dialog
* @param message the message to display in the dialog
* @return true if the user clicked the "Yes" option; false if the user
* clicked the "No" option or cancelled the dialog (for example, by
* pressing the Back button)
*/
public boolean showConfirmationDialog(String title, String message)
{
return mixin.showConfirmationDialog(title, message);
}
// ----------------------------------------------------------
/**
* Display a popup list to the user and waits for them to select an item
* from it. Items in the list will be rendered simply by calling the
* {@link Object#toString()} method. To control the item renderer used to
* display the list, see
* {@link #selectItemFromList(String, List, ItemRenderer)}.
*
* @param <Item> the type of items in the list, which is inferred from the
* {@code list} parameter
* @param title the title of the popup dialog
* @param list the list of items to display in the popup
* @return the item that was selected from the list, or null if the dialog
* was cancelled
*/
/*public <Item> Item selectItemFromList(
String title, List<? extends Item> list)
{
return selectItemFromList(title, list, new SimpleItemRenderer());
}*/
// ----------------------------------------------------------
/**
* Display a popup list to the user and waits for them to select an item
* from it.
*
* @param <Item> the type of items in the list, which is inferred from the
* {@code list} parameter
* @param title the title of the popup dialog
* @param list the list of items to display in the popup
* @param itemRenderer the item renderer to use to display each item
* @return the item that was selected from the list, or null if the dialog
* was cancelled
*/
/*public <Item> Item selectItemFromList(
String title,
List<? extends Item> list,
ItemRenderer itemRenderer)
{
return internals.selectItemFromList(title, list, itemRenderer);
}*/
// ----------------------------------------------------------
/**
* Starts the activity with the specified intent. This method will not
* return until the new activity is dismissed by the user.
*
* @param intent an {@link Intent} that describes the activity to start
* @param returnMethod the name of the method to call when the activity
* returns
*/
public void presentActivity(Intent intent, String returnMethod)
{
mixin.presentActivity(intent, returnMethod);
}
// ----------------------------------------------------------
/**
* Starts the activity represented by the specified {@code Screen} subclass
* and slides it into view. This method returns immediately even as the
* screen is being presented, but at that point a new screen has taken
* over the user's attention and the old one might be discarded from memory
* at any time. Therefore, users should typically not do any important
* computation after calling this method.
*
* @param screenClass the subclass of {@code Screen} that will be displayed
* @param args the arguments to be sent to the new screen's
* {@code initialize} method
*/
public void presentScreen(
Class<? extends Activity> screenClass, Object... args)
{
mixin.presentScreen(screenClass, args);
}
// ----------------------------------------------------------
/**
* Call this method when the current screen is finished and should be
* closed. The specified value will be passed back to the previous screen
* and returned from the {@link #presentScreen(Class, Object...)} call that
* originally presented this screen.
*
* @param result the value to pass back to the previous screen
*/
public void finish(Object result)
{
mixin.finish(result);
}
// ----------------------------------------------------------
/**
* Called when a sub-activity returns yielding a result. Subclasses can
* override this method if they want to handle sub-activities in the
* traditional way, but they <b>must</b> call the superclass implementation
* in order to make sure that Sofia's built-in methods and choosers work
* correctly.
*
* @param requestCode
* @param resultCode
* @param data
*/
@Override
protected void onActivityResult(
int requestCode, int resultCode, Intent data)
{
mixin.handleOnActivityResult(requestCode, resultCode, data);
super.onActivityResult(requestCode, resultCode, data);
}
}